@absolutejs/voice 0.0.22-beta.472 → 0.0.22-beta.473
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/index.d.ts +2 -0
- package/dist/index.js +413 -0
- package/dist/monitor.d.ts +138 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -225,4 +225,6 @@ export { buildVoiceProofPackInput, buildVoiceProofPack, buildVoiceProofPackFromO
|
|
|
225
225
|
export type { VoiceProofPack, VoiceProofPackBuildContext, VoiceProofPackBuildContextOptions, VoiceProofPackBuildTiming, VoiceProofPackEvidence, VoiceProofPackInput, VoiceProofPackInputBuilderLoaderInput, VoiceProofPackInputBuilderOperationsLoaderInput, VoiceProofPackInputBuilderOptions, VoiceProofPackInputBuilderSupportBundle, VoiceProofPackRefreshState, VoiceProofPackRefreshStatus, VoiceProofPackRoutesOptions, VoiceProofPackSection, VoiceProofPackSourceValue, VoiceProofPackStatus, VoiceProofPackStaleWhileRefreshSource, VoiceProofPackStaleWhileRefreshSourceOptions, VoiceProofPackWriteResult, VoiceProofRefreshSnapshot, VoiceProofRefreshSnapshotOptions, } from "./proofPack";
|
|
226
226
|
export { buildVoiceMultilingualProofReadinessCheck, renderVoiceMultilingualProofMarkdown, runVoiceMultilingualProof, } from "./multilingualProof";
|
|
227
227
|
export type { VoiceMultilingualLanguageCode, VoiceMultilingualProofAdapterEntry, VoiceMultilingualProofAdapterReport, VoiceMultilingualProofDefaultThresholds, VoiceMultilingualProofLanguageMetrics, VoiceMultilingualProofLanguageReport, VoiceMultilingualProofLanguageThresholds, VoiceMultilingualProofOptions, VoiceMultilingualProofReadinessCheck, VoiceMultilingualProofReadinessOptions, VoiceMultilingualProofReport, } from "./multilingualProof";
|
|
228
|
+
export { buildVoiceMonitorPlan, createVoiceInMemoryMonitorRegistry, createVoiceLiveMonitorRoutes, createVoiceMonitorSession, } from "./monitor";
|
|
229
|
+
export type { VoiceMonitorAudioEvent, VoiceMonitorAudioSource, VoiceMonitorAuthenticate, VoiceMonitorAuthenticateInput, VoiceMonitorControlAck, VoiceMonitorControlHandler, VoiceMonitorControlHandlerInput, VoiceMonitorControlMessage, VoiceMonitorMutableRegistry, VoiceMonitorPlan, VoiceMonitorPlanInput, VoiceMonitorRegistry, VoiceMonitorRegistryRegisterInput, VoiceLiveMonitorRoutesOptions, VoiceMonitorSessionRecord, } from "./monitor";
|
|
228
230
|
export * from "./types";
|
package/dist/index.js
CHANGED
|
@@ -44454,6 +44454,415 @@ var buildVoiceMultilingualProofReadinessCheck = (report, options = {}) => {
|
|
|
44454
44454
|
value: failedAdapters.length
|
|
44455
44455
|
};
|
|
44456
44456
|
};
|
|
44457
|
+
// src/monitor.ts
|
|
44458
|
+
import { Elysia as Elysia70 } from "elysia";
|
|
44459
|
+
var buildAudioFanout = () => {
|
|
44460
|
+
const handlers = new Set;
|
|
44461
|
+
return {
|
|
44462
|
+
emit: (event) => {
|
|
44463
|
+
for (const handler of handlers)
|
|
44464
|
+
handler(event);
|
|
44465
|
+
},
|
|
44466
|
+
onAudio: (handler) => {
|
|
44467
|
+
handlers.add(handler);
|
|
44468
|
+
return () => {
|
|
44469
|
+
handlers.delete(handler);
|
|
44470
|
+
};
|
|
44471
|
+
}
|
|
44472
|
+
};
|
|
44473
|
+
};
|
|
44474
|
+
var buildCloseFanout = () => {
|
|
44475
|
+
const handlers = new Set;
|
|
44476
|
+
return {
|
|
44477
|
+
emitClose: (reason) => {
|
|
44478
|
+
for (const handler of handlers)
|
|
44479
|
+
handler(reason);
|
|
44480
|
+
},
|
|
44481
|
+
onClose: (handler) => {
|
|
44482
|
+
handlers.add(handler);
|
|
44483
|
+
return () => {
|
|
44484
|
+
handlers.delete(handler);
|
|
44485
|
+
};
|
|
44486
|
+
}
|
|
44487
|
+
};
|
|
44488
|
+
};
|
|
44489
|
+
var createVoiceMonitorSession = (input) => {
|
|
44490
|
+
const audio = buildAudioFanout();
|
|
44491
|
+
const close = buildCloseFanout();
|
|
44492
|
+
return {
|
|
44493
|
+
emit: audio.emit,
|
|
44494
|
+
emitClose: close.emitClose,
|
|
44495
|
+
handle: input.handle,
|
|
44496
|
+
metadata: input.metadata,
|
|
44497
|
+
onAudio: audio.onAudio,
|
|
44498
|
+
onClose: close.onClose,
|
|
44499
|
+
sessionId: input.sessionId
|
|
44500
|
+
};
|
|
44501
|
+
};
|
|
44502
|
+
var createVoiceInMemoryMonitorRegistry = () => {
|
|
44503
|
+
const records = new Map;
|
|
44504
|
+
return {
|
|
44505
|
+
emit: (sessionId, event) => {
|
|
44506
|
+
records.get(sessionId)?.emit(event);
|
|
44507
|
+
},
|
|
44508
|
+
emitClose: (sessionId, reason) => {
|
|
44509
|
+
records.get(sessionId)?.emitClose(reason);
|
|
44510
|
+
},
|
|
44511
|
+
get: (sessionId) => records.get(sessionId),
|
|
44512
|
+
list: () => Array.from(records.keys()).map((sessionId) => ({ sessionId })),
|
|
44513
|
+
register: (record) => {
|
|
44514
|
+
const existing = records.get(record.sessionId);
|
|
44515
|
+
if (existing) {
|
|
44516
|
+
throw new Error(`VoiceMonitorRegistry already has a session "${record.sessionId}"; deregister it before re-registering.`);
|
|
44517
|
+
}
|
|
44518
|
+
const wrapped = "emit" in record && typeof record.emit === "function" ? record : createVoiceMonitorSession({
|
|
44519
|
+
handle: record.handle,
|
|
44520
|
+
metadata: record.metadata,
|
|
44521
|
+
sessionId: record.sessionId
|
|
44522
|
+
});
|
|
44523
|
+
if (wrapped !== record) {
|
|
44524
|
+
record.onAudio((event) => wrapped.emit(event));
|
|
44525
|
+
record.onClose((reason) => wrapped.emitClose(reason));
|
|
44526
|
+
}
|
|
44527
|
+
records.set(record.sessionId, wrapped);
|
|
44528
|
+
return () => {
|
|
44529
|
+
records.delete(record.sessionId);
|
|
44530
|
+
wrapped.emitClose("deregistered");
|
|
44531
|
+
};
|
|
44532
|
+
}
|
|
44533
|
+
};
|
|
44534
|
+
};
|
|
44535
|
+
var buildDefaultControlHandler = (type) => {
|
|
44536
|
+
if (type === "transfer") {
|
|
44537
|
+
return async ({ message, session }) => {
|
|
44538
|
+
if (message.type !== "transfer") {
|
|
44539
|
+
return { error: "internal: type mismatch", ok: false, type };
|
|
44540
|
+
}
|
|
44541
|
+
await session.handle.transfer({
|
|
44542
|
+
metadata: message.metadata,
|
|
44543
|
+
reason: message.reason,
|
|
44544
|
+
target: message.target
|
|
44545
|
+
});
|
|
44546
|
+
return { detail: `Transferred to ${message.target}.`, ok: true, type };
|
|
44547
|
+
};
|
|
44548
|
+
}
|
|
44549
|
+
if (type === "hangup") {
|
|
44550
|
+
return async ({ message, session }) => {
|
|
44551
|
+
if (message.type !== "hangup") {
|
|
44552
|
+
return { error: "internal: type mismatch", ok: false, type };
|
|
44553
|
+
}
|
|
44554
|
+
await session.handle.complete();
|
|
44555
|
+
return {
|
|
44556
|
+
detail: message.reason ? `Hangup: ${message.reason}` : "Hangup.",
|
|
44557
|
+
ok: true,
|
|
44558
|
+
type
|
|
44559
|
+
};
|
|
44560
|
+
};
|
|
44561
|
+
}
|
|
44562
|
+
if (type === "escalate") {
|
|
44563
|
+
return async ({ message, session }) => {
|
|
44564
|
+
if (message.type !== "escalate") {
|
|
44565
|
+
return { error: "internal: type mismatch", ok: false, type };
|
|
44566
|
+
}
|
|
44567
|
+
await session.handle.escalate({
|
|
44568
|
+
metadata: message.metadata,
|
|
44569
|
+
reason: message.reason ?? "monitor-requested-escalation"
|
|
44570
|
+
});
|
|
44571
|
+
return { detail: "Escalated.", ok: true, type };
|
|
44572
|
+
};
|
|
44573
|
+
}
|
|
44574
|
+
if (type === "voicemail") {
|
|
44575
|
+
return async ({ message, session }) => {
|
|
44576
|
+
if (message.type !== "voicemail") {
|
|
44577
|
+
return { error: "internal: type mismatch", ok: false, type };
|
|
44578
|
+
}
|
|
44579
|
+
await session.handle.markVoicemail({ metadata: message.metadata });
|
|
44580
|
+
return { detail: "Voicemail marked.", ok: true, type };
|
|
44581
|
+
};
|
|
44582
|
+
}
|
|
44583
|
+
if (type === "no-answer") {
|
|
44584
|
+
return async ({ message, session }) => {
|
|
44585
|
+
if (message.type !== "no-answer") {
|
|
44586
|
+
return { error: "internal: type mismatch", ok: false, type };
|
|
44587
|
+
}
|
|
44588
|
+
await session.handle.markNoAnswer({ metadata: message.metadata });
|
|
44589
|
+
return { detail: "Marked no-answer.", ok: true, type };
|
|
44590
|
+
};
|
|
44591
|
+
}
|
|
44592
|
+
return;
|
|
44593
|
+
};
|
|
44594
|
+
var parseControlMessage = (raw) => {
|
|
44595
|
+
if (!raw || typeof raw !== "object")
|
|
44596
|
+
return;
|
|
44597
|
+
const record = raw;
|
|
44598
|
+
const type = record.type;
|
|
44599
|
+
if (typeof type !== "string")
|
|
44600
|
+
return;
|
|
44601
|
+
if (type === "transfer") {
|
|
44602
|
+
if (typeof record.target !== "string")
|
|
44603
|
+
return;
|
|
44604
|
+
return {
|
|
44605
|
+
metadata: record.metadata,
|
|
44606
|
+
reason: typeof record.reason === "string" ? record.reason : undefined,
|
|
44607
|
+
target: record.target,
|
|
44608
|
+
type
|
|
44609
|
+
};
|
|
44610
|
+
}
|
|
44611
|
+
if (type === "hangup") {
|
|
44612
|
+
return {
|
|
44613
|
+
reason: typeof record.reason === "string" ? record.reason : undefined,
|
|
44614
|
+
type
|
|
44615
|
+
};
|
|
44616
|
+
}
|
|
44617
|
+
if (type === "escalate") {
|
|
44618
|
+
return {
|
|
44619
|
+
metadata: record.metadata,
|
|
44620
|
+
reason: typeof record.reason === "string" ? record.reason : undefined,
|
|
44621
|
+
type
|
|
44622
|
+
};
|
|
44623
|
+
}
|
|
44624
|
+
if (type === "voicemail" || type === "no-answer") {
|
|
44625
|
+
return {
|
|
44626
|
+
metadata: record.metadata,
|
|
44627
|
+
type
|
|
44628
|
+
};
|
|
44629
|
+
}
|
|
44630
|
+
if (type === "mute") {
|
|
44631
|
+
if (typeof record.muted !== "boolean" || record.target !== "assistant" && record.target !== "caller") {
|
|
44632
|
+
return;
|
|
44633
|
+
}
|
|
44634
|
+
return { muted: record.muted, target: record.target, type };
|
|
44635
|
+
}
|
|
44636
|
+
if (type === "say") {
|
|
44637
|
+
if (typeof record.text !== "string")
|
|
44638
|
+
return;
|
|
44639
|
+
return {
|
|
44640
|
+
interrupt: typeof record.interrupt === "boolean" ? record.interrupt : undefined,
|
|
44641
|
+
text: record.text,
|
|
44642
|
+
type
|
|
44643
|
+
};
|
|
44644
|
+
}
|
|
44645
|
+
if (type === "inject") {
|
|
44646
|
+
if (typeof record.text !== "string" || record.role !== "assistant" && record.role !== "system" && record.role !== "user") {
|
|
44647
|
+
return;
|
|
44648
|
+
}
|
|
44649
|
+
return { role: record.role, text: record.text, type };
|
|
44650
|
+
}
|
|
44651
|
+
return;
|
|
44652
|
+
};
|
|
44653
|
+
var DEFAULT_BASE_PATH = "/api/voice/monitor";
|
|
44654
|
+
var DEFAULT_LISTEN_PATH = ":sessionId/listen";
|
|
44655
|
+
var DEFAULT_CONTROL_PATH = ":sessionId/control";
|
|
44656
|
+
var joinPath = (...parts) => parts.filter((part) => part.length > 0).map((part) => part.replace(/^\/+|\/+$/g, "")).filter((part) => part.length > 0).reduce((path, part) => `${path}/${part}`, "");
|
|
44657
|
+
var substituteSessionId = (template, sessionId) => template.replace(":sessionId", encodeURIComponent(sessionId));
|
|
44658
|
+
var buildVoiceMonitorPlan = (input) => {
|
|
44659
|
+
const basePath = input.basePath ?? DEFAULT_BASE_PATH;
|
|
44660
|
+
const listenTemplate = input.listenPath ?? joinPath(basePath, DEFAULT_LISTEN_PATH);
|
|
44661
|
+
const controlTemplate = input.controlPath ?? joinPath(basePath, DEFAULT_CONTROL_PATH);
|
|
44662
|
+
const baseUrl = input.baseUrl.replace(/\/+$/, "");
|
|
44663
|
+
return {
|
|
44664
|
+
controlUrl: `${baseUrl}${substituteSessionId(controlTemplate, input.sessionId)}`,
|
|
44665
|
+
listenUrl: `${baseUrl}${substituteSessionId(listenTemplate, input.sessionId)}`
|
|
44666
|
+
};
|
|
44667
|
+
};
|
|
44668
|
+
var resolveSessionId3 = (ws) => {
|
|
44669
|
+
const params = ws.data?.params;
|
|
44670
|
+
if (!params)
|
|
44671
|
+
return;
|
|
44672
|
+
const value = params.sessionId;
|
|
44673
|
+
if (typeof value !== "string" || value.length === 0)
|
|
44674
|
+
return;
|
|
44675
|
+
return value;
|
|
44676
|
+
};
|
|
44677
|
+
var resolveAuthenticate = async (authenticate, input) => {
|
|
44678
|
+
if (!authenticate)
|
|
44679
|
+
return true;
|
|
44680
|
+
return await authenticate(input);
|
|
44681
|
+
};
|
|
44682
|
+
var createVoiceLiveMonitorRoutes = (options) => {
|
|
44683
|
+
const basePath = options.basePath ?? DEFAULT_BASE_PATH;
|
|
44684
|
+
const listenPath = options.listenPath === undefined ? joinPath(basePath, DEFAULT_LISTEN_PATH) : options.listenPath;
|
|
44685
|
+
const controlPath = options.controlPath === undefined ? joinPath(basePath, DEFAULT_CONTROL_PATH) : options.controlPath;
|
|
44686
|
+
const handlers = {
|
|
44687
|
+
escalate: options.controlHandlers?.escalate ?? buildDefaultControlHandler("escalate"),
|
|
44688
|
+
hangup: options.controlHandlers?.hangup ?? buildDefaultControlHandler("hangup"),
|
|
44689
|
+
inject: options.controlHandlers?.inject,
|
|
44690
|
+
mute: options.controlHandlers?.mute,
|
|
44691
|
+
"no-answer": options.controlHandlers?.["no-answer"] ?? buildDefaultControlHandler("no-answer"),
|
|
44692
|
+
say: options.controlHandlers?.say,
|
|
44693
|
+
transfer: options.controlHandlers?.transfer ?? buildDefaultControlHandler("transfer"),
|
|
44694
|
+
voicemail: options.controlHandlers?.voicemail ?? buildDefaultControlHandler("voicemail")
|
|
44695
|
+
};
|
|
44696
|
+
const app = new Elysia70({ name: "absolutejs-voice-monitor" });
|
|
44697
|
+
const unsubscribers = new WeakMap;
|
|
44698
|
+
if (listenPath !== false && listenPath.length > 0) {
|
|
44699
|
+
app.ws(`/${listenPath.replace(/^\/+/, "")}`, {
|
|
44700
|
+
close: (ws) => {
|
|
44701
|
+
const subs = unsubscribers.get(ws);
|
|
44702
|
+
if (subs) {
|
|
44703
|
+
for (const unsub of subs)
|
|
44704
|
+
unsub();
|
|
44705
|
+
unsubscribers.delete(ws);
|
|
44706
|
+
}
|
|
44707
|
+
},
|
|
44708
|
+
open: async (ws) => {
|
|
44709
|
+
const webSocket = ws;
|
|
44710
|
+
const sessionId = resolveSessionId3(webSocket);
|
|
44711
|
+
if (!sessionId) {
|
|
44712
|
+
webSocket.send(JSON.stringify({
|
|
44713
|
+
error: "missing sessionId in path params",
|
|
44714
|
+
type: "error"
|
|
44715
|
+
}));
|
|
44716
|
+
webSocket.close(4400, "missing sessionId");
|
|
44717
|
+
return;
|
|
44718
|
+
}
|
|
44719
|
+
const authed = await resolveAuthenticate(options.authenticate, {
|
|
44720
|
+
request: webSocket.raw?.request,
|
|
44721
|
+
route: "listen",
|
|
44722
|
+
sessionId
|
|
44723
|
+
});
|
|
44724
|
+
if (!authed) {
|
|
44725
|
+
webSocket.send(JSON.stringify({ error: "unauthorized", type: "error" }));
|
|
44726
|
+
webSocket.close(4401, "unauthorized");
|
|
44727
|
+
return;
|
|
44728
|
+
}
|
|
44729
|
+
const record = options.registry.get(sessionId);
|
|
44730
|
+
if (!record) {
|
|
44731
|
+
webSocket.send(JSON.stringify({
|
|
44732
|
+
error: `session "${sessionId}" not found`,
|
|
44733
|
+
type: "error"
|
|
44734
|
+
}));
|
|
44735
|
+
webSocket.close(4404, "session not found");
|
|
44736
|
+
return;
|
|
44737
|
+
}
|
|
44738
|
+
const subs = [];
|
|
44739
|
+
webSocket.send(JSON.stringify({
|
|
44740
|
+
sessionId,
|
|
44741
|
+
type: "subscribed"
|
|
44742
|
+
}));
|
|
44743
|
+
subs.push(record.onAudio((event) => {
|
|
44744
|
+
webSocket.send(event.chunk);
|
|
44745
|
+
}));
|
|
44746
|
+
subs.push(record.onClose((reason) => {
|
|
44747
|
+
webSocket.send(JSON.stringify({
|
|
44748
|
+
reason,
|
|
44749
|
+
sessionId,
|
|
44750
|
+
type: "session-closed"
|
|
44751
|
+
}));
|
|
44752
|
+
webSocket.close(1000, reason ?? "session-closed");
|
|
44753
|
+
}));
|
|
44754
|
+
unsubscribers.set(ws, subs);
|
|
44755
|
+
}
|
|
44756
|
+
});
|
|
44757
|
+
}
|
|
44758
|
+
if (controlPath !== false && controlPath.length > 0) {
|
|
44759
|
+
app.ws(`/${controlPath.replace(/^\/+/, "")}`, {
|
|
44760
|
+
close: (ws) => {
|
|
44761
|
+
unsubscribers.delete(ws);
|
|
44762
|
+
},
|
|
44763
|
+
message: async (ws, raw) => {
|
|
44764
|
+
const webSocket = ws;
|
|
44765
|
+
const sessionId = resolveSessionId3(webSocket);
|
|
44766
|
+
if (!sessionId) {
|
|
44767
|
+
webSocket.send(JSON.stringify({
|
|
44768
|
+
error: "missing sessionId in path params",
|
|
44769
|
+
ok: false,
|
|
44770
|
+
type: "error"
|
|
44771
|
+
}));
|
|
44772
|
+
return;
|
|
44773
|
+
}
|
|
44774
|
+
const message = parseControlMessage(raw);
|
|
44775
|
+
if (!message) {
|
|
44776
|
+
webSocket.send(JSON.stringify({
|
|
44777
|
+
error: "invalid control message",
|
|
44778
|
+
ok: false,
|
|
44779
|
+
type: "error"
|
|
44780
|
+
}));
|
|
44781
|
+
return;
|
|
44782
|
+
}
|
|
44783
|
+
const record = options.registry.get(sessionId);
|
|
44784
|
+
if (!record) {
|
|
44785
|
+
webSocket.send(JSON.stringify({
|
|
44786
|
+
error: `session "${sessionId}" not found`,
|
|
44787
|
+
ok: false,
|
|
44788
|
+
type: message.type
|
|
44789
|
+
}));
|
|
44790
|
+
return;
|
|
44791
|
+
}
|
|
44792
|
+
const handler = handlers[message.type];
|
|
44793
|
+
if (!handler) {
|
|
44794
|
+
webSocket.send(JSON.stringify({
|
|
44795
|
+
error: `no handler registered for control type "${message.type}"`,
|
|
44796
|
+
ok: false,
|
|
44797
|
+
type: message.type
|
|
44798
|
+
}));
|
|
44799
|
+
return;
|
|
44800
|
+
}
|
|
44801
|
+
try {
|
|
44802
|
+
const ack = await handler({
|
|
44803
|
+
message,
|
|
44804
|
+
raw,
|
|
44805
|
+
session: record
|
|
44806
|
+
});
|
|
44807
|
+
webSocket.send(JSON.stringify(ack));
|
|
44808
|
+
} catch (error) {
|
|
44809
|
+
webSocket.send(JSON.stringify({
|
|
44810
|
+
error: error instanceof Error ? error.message : String(error),
|
|
44811
|
+
ok: false,
|
|
44812
|
+
type: message.type
|
|
44813
|
+
}));
|
|
44814
|
+
}
|
|
44815
|
+
},
|
|
44816
|
+
open: async (ws) => {
|
|
44817
|
+
const webSocket = ws;
|
|
44818
|
+
const sessionId = resolveSessionId3(webSocket);
|
|
44819
|
+
if (!sessionId) {
|
|
44820
|
+
webSocket.send(JSON.stringify({
|
|
44821
|
+
error: "missing sessionId in path params",
|
|
44822
|
+
type: "error"
|
|
44823
|
+
}));
|
|
44824
|
+
webSocket.close(4400, "missing sessionId");
|
|
44825
|
+
return;
|
|
44826
|
+
}
|
|
44827
|
+
const authed = await resolveAuthenticate(options.authenticate, {
|
|
44828
|
+
request: webSocket.raw?.request,
|
|
44829
|
+
route: "control",
|
|
44830
|
+
sessionId
|
|
44831
|
+
});
|
|
44832
|
+
if (!authed) {
|
|
44833
|
+
webSocket.send(JSON.stringify({ error: "unauthorized", type: "error" }));
|
|
44834
|
+
webSocket.close(4401, "unauthorized");
|
|
44835
|
+
return;
|
|
44836
|
+
}
|
|
44837
|
+
const record = options.registry.get(sessionId);
|
|
44838
|
+
if (!record) {
|
|
44839
|
+
webSocket.send(JSON.stringify({
|
|
44840
|
+
error: `session "${sessionId}" not found`,
|
|
44841
|
+
type: "error"
|
|
44842
|
+
}));
|
|
44843
|
+
webSocket.close(4404, "session not found");
|
|
44844
|
+
return;
|
|
44845
|
+
}
|
|
44846
|
+
webSocket.send(JSON.stringify({
|
|
44847
|
+
sessionId,
|
|
44848
|
+
supports: Object.entries(handlers).filter(([, value]) => value !== undefined).map(([key]) => key),
|
|
44849
|
+
type: "ready"
|
|
44850
|
+
}));
|
|
44851
|
+
}
|
|
44852
|
+
});
|
|
44853
|
+
}
|
|
44854
|
+
if (options.htmlPath !== undefined && options.htmlPath !== false) {
|
|
44855
|
+
const path = options.htmlPath;
|
|
44856
|
+
app.get(path, () => {
|
|
44857
|
+
const sessions = options.registry.list().map((entry) => `<li><code>${entry.sessionId}</code></li>`).join("");
|
|
44858
|
+
const body = `<!doctype html><html lang="en"><head><meta charset="utf-8" /><title>Voice Monitor</title><style>body{background:#0b1216;color:#f6f1e7;font-family:ui-sans-serif,system-ui,sans-serif;margin:0;padding:32px}main{margin:auto;max-width:960px}h1{font-size:clamp(2rem,5vw,3.2rem);letter-spacing:-.04em;margin:.2rem 0 1rem}code{background:#171f25;border:1px solid #2c3a44;border-radius:8px;padding:2px 6px}ul{margin:8px 0;padding-left:18px}p.muted{color:#9aa8b2}</style></head><body><main><h1>Voice Monitor</h1><p class="muted">Active sessions registered with this monitor registry.</p><ul>${sessions || "<li><em>None.</em></li>"}</ul><p class="muted">Open <code>${listenPath}</code> and <code>${controlPath}</code> via WebSocket per session for live listen + control.</p></main></body></html>`;
|
|
44859
|
+
return new Response(body, {
|
|
44860
|
+
headers: { "content-type": "text/html; charset=utf-8" }
|
|
44861
|
+
});
|
|
44862
|
+
});
|
|
44863
|
+
}
|
|
44864
|
+
return app;
|
|
44865
|
+
};
|
|
44457
44866
|
export {
|
|
44458
44867
|
writeVoiceProofPack,
|
|
44459
44868
|
writeVoiceMediaPipelineArtifacts,
|
|
@@ -44948,6 +45357,7 @@ export {
|
|
|
44948
45357
|
createVoiceObservabilityExportRoutes,
|
|
44949
45358
|
createVoiceObservabilityExportReplayRoutes,
|
|
44950
45359
|
createVoiceMonitorWebhookNotifier,
|
|
45360
|
+
createVoiceMonitorSession,
|
|
44951
45361
|
createVoiceMonitorRunnerRoutes,
|
|
44952
45362
|
createVoiceMonitorRunner,
|
|
44953
45363
|
createVoiceMonitorRoutes,
|
|
@@ -44967,6 +45377,7 @@ export {
|
|
|
44967
45377
|
createVoiceMediaPipelineRoutes,
|
|
44968
45378
|
createVoiceLiveOpsRoutes,
|
|
44969
45379
|
createVoiceLiveOpsController,
|
|
45380
|
+
createVoiceLiveMonitorRoutes,
|
|
44970
45381
|
createVoiceLiveLatencyRoutes,
|
|
44971
45382
|
createVoiceLinearIssueUpdateSink,
|
|
44972
45383
|
createVoiceLinearIssueSyncSinks,
|
|
@@ -44978,6 +45389,7 @@ export {
|
|
|
44978
45389
|
createVoiceIncidentTimelineRoutes,
|
|
44979
45390
|
createVoiceIncidentBundleRoutes,
|
|
44980
45391
|
createVoiceInMemoryRealCallProfileRecoveryJobStore,
|
|
45392
|
+
createVoiceInMemoryMonitorRegistry,
|
|
44981
45393
|
createVoiceHubSpotTaskUpdateSink,
|
|
44982
45394
|
createVoiceHubSpotTaskSyncSinks,
|
|
44983
45395
|
createVoiceHubSpotTaskSink,
|
|
@@ -45153,6 +45565,7 @@ export {
|
|
|
45153
45565
|
buildVoiceObservabilityArtifactIndex,
|
|
45154
45566
|
buildVoiceMultilingualProofReadinessCheck,
|
|
45155
45567
|
buildVoiceMonitorRunReport,
|
|
45568
|
+
buildVoiceMonitorPlan,
|
|
45156
45569
|
buildVoiceMediaPipelineReport,
|
|
45157
45570
|
buildVoiceMediaPipelineReadinessChecks,
|
|
45158
45571
|
buildVoiceMediaPipelineIncidentEvents,
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { Elysia } from "elysia";
|
|
2
|
+
import type { AudioFormat, VoiceSessionHandle, VoiceSessionRecord } from "./types";
|
|
3
|
+
export type VoiceMonitorAudioSource = "assistant" | "caller" | (string & {});
|
|
4
|
+
export type VoiceMonitorAudioEvent = {
|
|
5
|
+
at: number;
|
|
6
|
+
chunk: Uint8Array;
|
|
7
|
+
format: AudioFormat;
|
|
8
|
+
source: VoiceMonitorAudioSource;
|
|
9
|
+
};
|
|
10
|
+
export type VoiceMonitorSessionRecord = {
|
|
11
|
+
handle: VoiceSessionHandle<unknown, VoiceSessionRecord, unknown>;
|
|
12
|
+
metadata?: Record<string, unknown>;
|
|
13
|
+
onAudio: (handler: (event: VoiceMonitorAudioEvent) => void) => () => void;
|
|
14
|
+
onClose: (handler: (reason?: string) => void) => () => void;
|
|
15
|
+
sessionId: string;
|
|
16
|
+
};
|
|
17
|
+
export type VoiceMonitorRegistry = {
|
|
18
|
+
get: (sessionId: string) => VoiceMonitorSessionRecord | undefined;
|
|
19
|
+
list: () => readonly {
|
|
20
|
+
sessionId: string;
|
|
21
|
+
}[];
|
|
22
|
+
};
|
|
23
|
+
export type VoiceMonitorMutableRegistry = VoiceMonitorRegistry & {
|
|
24
|
+
emit: (sessionId: string, event: VoiceMonitorAudioEvent) => void;
|
|
25
|
+
emitClose: (sessionId: string, reason?: string) => void;
|
|
26
|
+
register: (record: VoiceMonitorSessionRecord) => () => void;
|
|
27
|
+
};
|
|
28
|
+
export type VoiceMonitorRegistryRegisterInput = {
|
|
29
|
+
handle: VoiceSessionHandle<unknown, VoiceSessionRecord, unknown>;
|
|
30
|
+
metadata?: Record<string, unknown>;
|
|
31
|
+
sessionId: string;
|
|
32
|
+
};
|
|
33
|
+
export declare const createVoiceMonitorSession: (input: VoiceMonitorRegistryRegisterInput) => VoiceMonitorSessionRecord & {
|
|
34
|
+
emit: (event: VoiceMonitorAudioEvent) => void;
|
|
35
|
+
emitClose: (reason?: string) => void;
|
|
36
|
+
};
|
|
37
|
+
export declare const createVoiceInMemoryMonitorRegistry: () => VoiceMonitorMutableRegistry;
|
|
38
|
+
export type VoiceMonitorControlMessage = {
|
|
39
|
+
metadata?: Record<string, unknown>;
|
|
40
|
+
reason?: string;
|
|
41
|
+
target: string;
|
|
42
|
+
type: "transfer";
|
|
43
|
+
} | {
|
|
44
|
+
reason?: string;
|
|
45
|
+
type: "hangup";
|
|
46
|
+
} | {
|
|
47
|
+
metadata?: Record<string, unknown>;
|
|
48
|
+
reason?: string;
|
|
49
|
+
type: "escalate";
|
|
50
|
+
} | {
|
|
51
|
+
metadata?: Record<string, unknown>;
|
|
52
|
+
type: "voicemail";
|
|
53
|
+
} | {
|
|
54
|
+
metadata?: Record<string, unknown>;
|
|
55
|
+
type: "no-answer";
|
|
56
|
+
} | {
|
|
57
|
+
muted: boolean;
|
|
58
|
+
target: "assistant" | "caller";
|
|
59
|
+
type: "mute";
|
|
60
|
+
} | {
|
|
61
|
+
interrupt?: boolean;
|
|
62
|
+
text: string;
|
|
63
|
+
type: "say";
|
|
64
|
+
} | {
|
|
65
|
+
role: "assistant" | "system" | "user";
|
|
66
|
+
text: string;
|
|
67
|
+
type: "inject";
|
|
68
|
+
};
|
|
69
|
+
export type VoiceMonitorControlAck = {
|
|
70
|
+
detail?: string;
|
|
71
|
+
ok: true;
|
|
72
|
+
type: VoiceMonitorControlMessage["type"];
|
|
73
|
+
} | {
|
|
74
|
+
error: string;
|
|
75
|
+
ok: false;
|
|
76
|
+
type: VoiceMonitorControlMessage["type"];
|
|
77
|
+
};
|
|
78
|
+
export type VoiceMonitorControlHandlerInput = {
|
|
79
|
+
message: VoiceMonitorControlMessage;
|
|
80
|
+
raw: unknown;
|
|
81
|
+
session: VoiceMonitorSessionRecord;
|
|
82
|
+
};
|
|
83
|
+
export type VoiceMonitorControlHandler = (input: VoiceMonitorControlHandlerInput) => Promise<VoiceMonitorControlAck> | VoiceMonitorControlAck;
|
|
84
|
+
export type VoiceMonitorAuthenticateInput = {
|
|
85
|
+
request: unknown;
|
|
86
|
+
route: "control" | "listen";
|
|
87
|
+
sessionId: string;
|
|
88
|
+
};
|
|
89
|
+
export type VoiceMonitorAuthenticate = (input: VoiceMonitorAuthenticateInput) => boolean | Promise<boolean>;
|
|
90
|
+
export type VoiceLiveMonitorRoutesOptions = {
|
|
91
|
+
authenticate?: VoiceMonitorAuthenticate;
|
|
92
|
+
basePath?: string;
|
|
93
|
+
controlHandlers?: Partial<Record<VoiceMonitorControlMessage["type"], VoiceMonitorControlHandler>>;
|
|
94
|
+
controlPath?: false | string;
|
|
95
|
+
htmlPath?: false | string;
|
|
96
|
+
listenPath?: false | string;
|
|
97
|
+
registry: VoiceMonitorRegistry;
|
|
98
|
+
};
|
|
99
|
+
export type VoiceMonitorPlanInput = {
|
|
100
|
+
basePath?: string;
|
|
101
|
+
baseUrl: string;
|
|
102
|
+
controlPath?: string;
|
|
103
|
+
listenPath?: string;
|
|
104
|
+
sessionId: string;
|
|
105
|
+
};
|
|
106
|
+
export type VoiceMonitorPlan = {
|
|
107
|
+
controlUrl: string;
|
|
108
|
+
listenUrl: string;
|
|
109
|
+
};
|
|
110
|
+
export declare const buildVoiceMonitorPlan: (input: VoiceMonitorPlanInput) => VoiceMonitorPlan;
|
|
111
|
+
export declare const createVoiceLiveMonitorRoutes: (options: VoiceLiveMonitorRoutesOptions) => Elysia<"", {
|
|
112
|
+
decorator: {};
|
|
113
|
+
store: {};
|
|
114
|
+
derive: {};
|
|
115
|
+
resolve: {};
|
|
116
|
+
}, {
|
|
117
|
+
typebox: {};
|
|
118
|
+
error: {};
|
|
119
|
+
}, {
|
|
120
|
+
schema: {};
|
|
121
|
+
standaloneSchema: {};
|
|
122
|
+
macro: {};
|
|
123
|
+
macroFn: {};
|
|
124
|
+
parser: {};
|
|
125
|
+
response: {};
|
|
126
|
+
}, {}, {
|
|
127
|
+
derive: {};
|
|
128
|
+
resolve: {};
|
|
129
|
+
schema: {};
|
|
130
|
+
standaloneSchema: {};
|
|
131
|
+
response: {};
|
|
132
|
+
}, {
|
|
133
|
+
derive: {};
|
|
134
|
+
resolve: {};
|
|
135
|
+
schema: {};
|
|
136
|
+
standaloneSchema: {};
|
|
137
|
+
response: {};
|
|
138
|
+
}>;
|