@absolutejs/voice 0.0.22-beta.86 → 0.0.22-beta.88
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/appKit.d.ts +3 -1
- package/dist/client/opsStatusWidget.d.ts +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +567 -259
- package/dist/productionReadiness.d.ts +103 -0
- package/dist/resilienceRoutes.d.ts +25 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5474,7 +5474,7 @@ var voice = (config) => {
|
|
|
5474
5474
|
}).use(htmxRoutes());
|
|
5475
5475
|
};
|
|
5476
5476
|
// src/appKit.ts
|
|
5477
|
-
import { Elysia as
|
|
5477
|
+
import { Elysia as Elysia14 } from "elysia";
|
|
5478
5478
|
|
|
5479
5479
|
// src/assistantHealth.ts
|
|
5480
5480
|
import { Elysia as Elysia3 } from "elysia";
|
|
@@ -8539,6 +8539,68 @@ var summarizeVoiceRoutingDecision = (events, options = {}) => {
|
|
|
8539
8539
|
const limited = typeof options.limit === "number" && options.limit >= 0 ? routingEvents.slice(0, options.limit) : routingEvents;
|
|
8540
8540
|
return limited[0] ?? null;
|
|
8541
8541
|
};
|
|
8542
|
+
var createEmptyKindSummary = () => ({
|
|
8543
|
+
errorCount: 0,
|
|
8544
|
+
fallbackCount: 0,
|
|
8545
|
+
providers: [],
|
|
8546
|
+
runCount: 0,
|
|
8547
|
+
timeoutCount: 0
|
|
8548
|
+
});
|
|
8549
|
+
var summarizeVoiceRoutingSessions = (events, options = {}) => {
|
|
8550
|
+
const routingEvents = (events.some((event) => ("payload" in event)) ? listVoiceRoutingEvents(events) : [...events]).filter((event) => !options.sessionId || event.sessionId === options.sessionId);
|
|
8551
|
+
const sessions = new Map;
|
|
8552
|
+
for (const event of routingEvents) {
|
|
8553
|
+
const existing = sessions.get(event.sessionId);
|
|
8554
|
+
const summary = existing ?? {
|
|
8555
|
+
errorCount: 0,
|
|
8556
|
+
eventCount: 0,
|
|
8557
|
+
fallbackCount: 0,
|
|
8558
|
+
kinds: {
|
|
8559
|
+
llm: createEmptyKindSummary(),
|
|
8560
|
+
stt: createEmptyKindSummary(),
|
|
8561
|
+
tts: createEmptyKindSummary()
|
|
8562
|
+
},
|
|
8563
|
+
lastEventAt: event.at,
|
|
8564
|
+
sessionId: event.sessionId,
|
|
8565
|
+
startedAt: event.at,
|
|
8566
|
+
status: "healthy",
|
|
8567
|
+
timeoutCount: 0
|
|
8568
|
+
};
|
|
8569
|
+
summary.eventCount += 1;
|
|
8570
|
+
summary.startedAt = Math.min(summary.startedAt, event.at);
|
|
8571
|
+
summary.lastEventAt = Math.max(summary.lastEventAt, event.at);
|
|
8572
|
+
if (event.status === "error") {
|
|
8573
|
+
summary.errorCount += 1;
|
|
8574
|
+
}
|
|
8575
|
+
if (event.status === "fallback") {
|
|
8576
|
+
summary.fallbackCount += 1;
|
|
8577
|
+
}
|
|
8578
|
+
if (event.timedOut) {
|
|
8579
|
+
summary.timeoutCount += 1;
|
|
8580
|
+
}
|
|
8581
|
+
const kind = summary.kinds[event.kind];
|
|
8582
|
+
kind.runCount += 1;
|
|
8583
|
+
if (event.status === "error") {
|
|
8584
|
+
kind.errorCount += 1;
|
|
8585
|
+
}
|
|
8586
|
+
if (event.status === "fallback") {
|
|
8587
|
+
kind.fallbackCount += 1;
|
|
8588
|
+
}
|
|
8589
|
+
if (event.timedOut) {
|
|
8590
|
+
kind.timeoutCount += 1;
|
|
8591
|
+
}
|
|
8592
|
+
if (event.provider && !kind.providers.includes(event.provider)) {
|
|
8593
|
+
kind.providers.push(event.provider);
|
|
8594
|
+
}
|
|
8595
|
+
if (!kind.latest || event.at > kind.latest.at) {
|
|
8596
|
+
kind.latest = event;
|
|
8597
|
+
}
|
|
8598
|
+
summary.status = summary.errorCount > 0 || summary.timeoutCount > 0 ? "degraded" : summary.fallbackCount > 0 ? "fallback" : "healthy";
|
|
8599
|
+
sessions.set(event.sessionId, summary);
|
|
8600
|
+
}
|
|
8601
|
+
const sorted = [...sessions.values()].sort((left, right) => right.lastEventAt - left.lastEventAt);
|
|
8602
|
+
return typeof options.limit === "number" && options.limit >= 0 ? sorted.slice(0, options.limit) : sorted;
|
|
8603
|
+
};
|
|
8542
8604
|
var createVoiceRoutingDecisionSummary = async (options) => {
|
|
8543
8605
|
const events = await options.store.list({
|
|
8544
8606
|
sessionId: options.sessionId,
|
|
@@ -8618,6 +8680,41 @@ var renderTimeline2 = (events) => {
|
|
|
8618
8680
|
</article>
|
|
8619
8681
|
`).join("")}</div>`;
|
|
8620
8682
|
};
|
|
8683
|
+
var renderSessionKind = (kind, summary) => {
|
|
8684
|
+
const latest = summary.latest;
|
|
8685
|
+
const provider = latest?.provider ?? summary.providers[0] ?? "none";
|
|
8686
|
+
const status = latest?.status ?? "idle";
|
|
8687
|
+
const fallback = latest?.fallbackProvider && latest.fallbackProvider !== provider ? ` -> ${latest.fallbackProvider}` : "";
|
|
8688
|
+
return `<div>
|
|
8689
|
+
<dt>${escapeHtml10(kind.toUpperCase())}</dt>
|
|
8690
|
+
<dd>${escapeHtml10(provider)}${escapeHtml10(fallback)}</dd>
|
|
8691
|
+
<small>${escapeHtml10(status)} \xB7 ${summary.runCount} event${summary.runCount === 1 ? "" : "s"} \xB7 ${summary.errorCount} error${summary.errorCount === 1 ? "" : "s"} \xB7 ${summary.fallbackCount} fallback${summary.fallbackCount === 1 ? "" : "s"}</small>
|
|
8692
|
+
</div>`;
|
|
8693
|
+
};
|
|
8694
|
+
var renderSessionSummaries = (sessions) => {
|
|
8695
|
+
if (sessions.length === 0) {
|
|
8696
|
+
return '<p class="muted">No call-level routing summaries yet. Run a voice session or provider simulation.</p>';
|
|
8697
|
+
}
|
|
8698
|
+
return `<div class="session-grid">${sessions.slice(0, 12).map((session) => `
|
|
8699
|
+
<article class="card session ${escapeHtml10(session.status)}">
|
|
8700
|
+
<div class="card-header">
|
|
8701
|
+
<strong>${escapeHtml10(session.sessionId)}</strong>
|
|
8702
|
+
<span>${escapeHtml10(session.status)}</span>
|
|
8703
|
+
</div>
|
|
8704
|
+
<p>
|
|
8705
|
+
<span class="pill">${session.eventCount} routing events</span>
|
|
8706
|
+
<span class="pill">${session.fallbackCount} fallbacks</span>
|
|
8707
|
+
<span class="pill">${session.errorCount} errors</span>
|
|
8708
|
+
<span class="pill">${session.timeoutCount} timeouts</span>
|
|
8709
|
+
</p>
|
|
8710
|
+
<dl>
|
|
8711
|
+
${renderSessionKind("llm", session.kinds.llm)}
|
|
8712
|
+
${renderSessionKind("stt", session.kinds.stt)}
|
|
8713
|
+
${renderSessionKind("tts", session.kinds.tts)}
|
|
8714
|
+
</dl>
|
|
8715
|
+
</article>
|
|
8716
|
+
`).join("")}</div>`;
|
|
8717
|
+
};
|
|
8621
8718
|
var renderSimulationControls = (kind, simulation) => {
|
|
8622
8719
|
if (!simulation) {
|
|
8623
8720
|
return "";
|
|
@@ -8656,6 +8753,7 @@ var renderVoiceResilienceHTML = (input) => {
|
|
|
8656
8753
|
section, .card { background: rgba(19, 22, 27, 0.92); border: 1px solid #27272a; border-radius: 20px; padding: 20px; }
|
|
8657
8754
|
.hero { background: linear-gradient(135deg, rgba(14, 165, 233, 0.18), rgba(245, 158, 11, 0.12)); }
|
|
8658
8755
|
.grid, .provider-grid { display: grid; gap: 14px; grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
|
8756
|
+
.session-grid { display: grid; gap: 14px; grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
8659
8757
|
.timeline { display: grid; gap: 12px; }
|
|
8660
8758
|
.card-header { align-items: center; display: flex; gap: 12px; justify-content: space-between; }
|
|
8661
8759
|
.card-header strong { font-size: 1.05rem; }
|
|
@@ -8667,8 +8765,9 @@ var renderVoiceResilienceHTML = (input) => {
|
|
|
8667
8765
|
.pill { background: #0f1217; border: 1px solid #3f3f46; border-radius: 999px; color: #d4d4d8; display: inline-flex; margin: 3px 4px 3px 0; padding: 5px 9px; }
|
|
8668
8766
|
.danger { border-color: rgba(239, 68, 68, 0.75); color: #fecaca; }
|
|
8669
8767
|
.event.error { border-color: rgba(239, 68, 68, 0.7); }
|
|
8670
|
-
.event.fallback { border-color: rgba(245, 158, 11, 0.7); }
|
|
8671
|
-
.event.success, .provider.healthy { border-color: rgba(34, 197, 94, 0.5); }
|
|
8768
|
+
.event.fallback, .session.fallback { border-color: rgba(245, 158, 11, 0.7); }
|
|
8769
|
+
.event.success, .provider.healthy, .session.healthy { border-color: rgba(34, 197, 94, 0.5); }
|
|
8770
|
+
.session.degraded { border-color: rgba(239, 68, 68, 0.7); }
|
|
8672
8771
|
.provider.suppressed, .provider.degraded, .provider.rate-limited { border-color: rgba(239, 68, 68, 0.7); }
|
|
8673
8772
|
.provider.recoverable { border-color: rgba(59, 130, 246, 0.7); }
|
|
8674
8773
|
button { background: #f59e0b; border: 0; border-radius: 999px; color: #111827; cursor: pointer; font-weight: 800; padding: 10px 14px; }
|
|
@@ -8676,7 +8775,7 @@ var renderVoiceResilienceHTML = (input) => {
|
|
|
8676
8775
|
.simulate-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 12px; }
|
|
8677
8776
|
.simulate-output { background: #050505; border: 1px solid #27272a; border-radius: 14px; color: #d4d4d8; overflow: auto; padding: 12px; white-space: pre-wrap; }
|
|
8678
8777
|
a { color: #f59e0b; }
|
|
8679
|
-
@media (max-width: 850px) { .grid, .provider-grid, dl { grid-template-columns: 1fr; } }
|
|
8778
|
+
@media (max-width: 850px) { .grid, .provider-grid, .session-grid, dl { grid-template-columns: 1fr; } }
|
|
8680
8779
|
</style>
|
|
8681
8780
|
</head>
|
|
8682
8781
|
<body>
|
|
@@ -8693,6 +8792,11 @@ var renderVoiceResilienceHTML = (input) => {
|
|
|
8693
8792
|
<article class="card metric"><span>Errors</span><strong>${summary.errors}</strong></article>
|
|
8694
8793
|
<article class="card metric"><span>Timeouts</span><strong>${summary.timeouts}</strong></article>
|
|
8695
8794
|
</section>
|
|
8795
|
+
<section>
|
|
8796
|
+
<h2>Call-level routing summaries</h2>
|
|
8797
|
+
<p class="muted">A compact per-call view of which LLM, STT, and TTS providers handled the session, including fallback and timeout counts.</p>
|
|
8798
|
+
${renderSessionSummaries(input.routingSessions)}
|
|
8799
|
+
</section>
|
|
8696
8800
|
<section>
|
|
8697
8801
|
<h2>LLM provider health</h2>
|
|
8698
8802
|
${renderProviderCards("LLM", input.llmProviderHealth)}
|
|
@@ -8790,13 +8894,15 @@ var createVoiceResilienceRoutes = (options) => {
|
|
|
8790
8894
|
const events = await options.store.list();
|
|
8791
8895
|
const sttEvents = events.filter((event) => event.payload.kind === "stt");
|
|
8792
8896
|
const ttsEvents = events.filter((event) => event.payload.kind === "tts");
|
|
8897
|
+
const routingEvents = listVoiceRoutingEvents(events);
|
|
8793
8898
|
const data = {
|
|
8794
8899
|
links: options.links,
|
|
8795
8900
|
llmProviderHealth: await summarizeVoiceProviderHealth({
|
|
8796
8901
|
events,
|
|
8797
8902
|
providers: options.llmProviders ?? []
|
|
8798
8903
|
}),
|
|
8799
|
-
routingEvents
|
|
8904
|
+
routingEvents,
|
|
8905
|
+
routingSessions: summarizeVoiceRoutingSessions(routingEvents),
|
|
8800
8906
|
sttProviderHealth: await summarizeVoiceProviderHealth({
|
|
8801
8907
|
events: sttEvents,
|
|
8802
8908
|
providers: options.sttProviders ?? []
|
|
@@ -9297,6 +9403,365 @@ var createVoiceProviderCapabilityRoutes = (options) => {
|
|
|
9297
9403
|
return routes;
|
|
9298
9404
|
};
|
|
9299
9405
|
|
|
9406
|
+
// src/productionReadiness.ts
|
|
9407
|
+
import { Elysia as Elysia13 } from "elysia";
|
|
9408
|
+
|
|
9409
|
+
// src/telephony/matrix.ts
|
|
9410
|
+
import { Elysia as Elysia12 } from "elysia";
|
|
9411
|
+
|
|
9412
|
+
// src/telephony/contract.ts
|
|
9413
|
+
var DEFAULT_REQUIREMENTS = [
|
|
9414
|
+
"stream-url",
|
|
9415
|
+
"wss-stream",
|
|
9416
|
+
"webhook-url",
|
|
9417
|
+
"signed-webhook",
|
|
9418
|
+
"smoke-pass"
|
|
9419
|
+
];
|
|
9420
|
+
var hasFailingSmokeCheck = (smoke) => smoke?.checks.some((check) => check.status === "fail") ?? false;
|
|
9421
|
+
var evaluateVoiceTelephonyContract = (input) => {
|
|
9422
|
+
const requirements = input.options?.requirements ?? DEFAULT_REQUIREMENTS;
|
|
9423
|
+
const issues = [];
|
|
9424
|
+
const hasRequirement = (requirement) => requirements.includes(requirement);
|
|
9425
|
+
if (hasRequirement("stream-url") && !input.setup.urls.stream) {
|
|
9426
|
+
issues.push({
|
|
9427
|
+
message: "Missing media stream URL.",
|
|
9428
|
+
requirement: "stream-url",
|
|
9429
|
+
severity: "error"
|
|
9430
|
+
});
|
|
9431
|
+
}
|
|
9432
|
+
if (hasRequirement("wss-stream") && !input.setup.urls.stream.startsWith("wss://")) {
|
|
9433
|
+
issues.push({
|
|
9434
|
+
message: "Media stream URL must use wss://.",
|
|
9435
|
+
requirement: "wss-stream",
|
|
9436
|
+
severity: "error"
|
|
9437
|
+
});
|
|
9438
|
+
}
|
|
9439
|
+
if (hasRequirement("webhook-url") && !input.setup.urls.webhook) {
|
|
9440
|
+
issues.push({
|
|
9441
|
+
message: "Missing carrier webhook URL.",
|
|
9442
|
+
requirement: "webhook-url",
|
|
9443
|
+
severity: "error"
|
|
9444
|
+
});
|
|
9445
|
+
}
|
|
9446
|
+
if (hasRequirement("signed-webhook") && !input.setup.signing.configured) {
|
|
9447
|
+
issues.push({
|
|
9448
|
+
message: "Carrier webhook signature verification is not configured.",
|
|
9449
|
+
requirement: "signed-webhook",
|
|
9450
|
+
severity: "error"
|
|
9451
|
+
});
|
|
9452
|
+
}
|
|
9453
|
+
if (hasRequirement("smoke-pass")) {
|
|
9454
|
+
if (!input.smoke) {
|
|
9455
|
+
issues.push({
|
|
9456
|
+
message: "Missing telephony smoke test report.",
|
|
9457
|
+
requirement: "smoke-pass",
|
|
9458
|
+
severity: "error"
|
|
9459
|
+
});
|
|
9460
|
+
} else if (!input.smoke.pass || hasFailingSmokeCheck(input.smoke)) {
|
|
9461
|
+
issues.push({
|
|
9462
|
+
message: "Telephony smoke test did not pass.",
|
|
9463
|
+
requirement: "smoke-pass",
|
|
9464
|
+
severity: "error"
|
|
9465
|
+
});
|
|
9466
|
+
}
|
|
9467
|
+
}
|
|
9468
|
+
for (const warning of input.setup.warnings) {
|
|
9469
|
+
issues.push({
|
|
9470
|
+
message: warning,
|
|
9471
|
+
requirement: "stream-url",
|
|
9472
|
+
severity: "warning"
|
|
9473
|
+
});
|
|
9474
|
+
}
|
|
9475
|
+
for (const name of input.setup.missing) {
|
|
9476
|
+
issues.push({
|
|
9477
|
+
message: `${name} is missing.`,
|
|
9478
|
+
requirement: "webhook-url",
|
|
9479
|
+
severity: "error"
|
|
9480
|
+
});
|
|
9481
|
+
}
|
|
9482
|
+
return {
|
|
9483
|
+
issues,
|
|
9484
|
+
pass: issues.every((issue) => issue.severity !== "error"),
|
|
9485
|
+
provider: input.setup.provider,
|
|
9486
|
+
requirements,
|
|
9487
|
+
setup: input.setup,
|
|
9488
|
+
smoke: input.smoke
|
|
9489
|
+
};
|
|
9490
|
+
};
|
|
9491
|
+
|
|
9492
|
+
// src/telephony/matrix.ts
|
|
9493
|
+
var escapeHtml14 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
9494
|
+
var labelForProvider = (provider) => provider.split("-").map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`).join(" ");
|
|
9495
|
+
var resolveEntryStatus = (contract, setup, smoke) => {
|
|
9496
|
+
if (!contract.pass || !setup.ready || smoke?.pass === false) {
|
|
9497
|
+
return "fail";
|
|
9498
|
+
}
|
|
9499
|
+
if (contract.issues.some((issue) => issue.severity === "warning") || setup.warnings.length > 0 || smoke?.checks.some((check) => check.status === "warn")) {
|
|
9500
|
+
return "warn";
|
|
9501
|
+
}
|
|
9502
|
+
return "pass";
|
|
9503
|
+
};
|
|
9504
|
+
var createVoiceTelephonyCarrierMatrix = (options) => {
|
|
9505
|
+
const entries = options.providers.map((provider) => {
|
|
9506
|
+
const contract = provider.contract ?? evaluateVoiceTelephonyContract({
|
|
9507
|
+
options: options.contract,
|
|
9508
|
+
setup: provider.setup,
|
|
9509
|
+
smoke: provider.smoke
|
|
9510
|
+
});
|
|
9511
|
+
const failures = provider.smoke?.checks.filter((check) => check.status === "fail").length ?? 0;
|
|
9512
|
+
const warnings = contract.issues.filter((issue) => issue.severity === "warning").length + (provider.smoke?.checks.filter((check) => check.status === "warn").length ?? 0);
|
|
9513
|
+
const errors = contract.issues.filter((issue) => issue.severity === "error").length;
|
|
9514
|
+
const status = resolveEntryStatus(contract, provider.setup, provider.smoke);
|
|
9515
|
+
return {
|
|
9516
|
+
contract,
|
|
9517
|
+
issues: contract.issues,
|
|
9518
|
+
name: provider.name ?? labelForProvider(provider.setup.provider),
|
|
9519
|
+
provider: provider.setup.provider,
|
|
9520
|
+
ready: provider.setup.ready,
|
|
9521
|
+
setup: provider.setup,
|
|
9522
|
+
smoke: provider.smoke,
|
|
9523
|
+
status,
|
|
9524
|
+
summary: {
|
|
9525
|
+
errors,
|
|
9526
|
+
failures,
|
|
9527
|
+
missing: provider.setup.missing.length,
|
|
9528
|
+
warnings
|
|
9529
|
+
}
|
|
9530
|
+
};
|
|
9531
|
+
});
|
|
9532
|
+
const summary = {
|
|
9533
|
+
contractsPassing: entries.filter((entry) => entry.contract.pass).length,
|
|
9534
|
+
failing: entries.filter((entry) => entry.status === "fail").length,
|
|
9535
|
+
providers: entries.length,
|
|
9536
|
+
ready: entries.filter((entry) => entry.ready).length,
|
|
9537
|
+
smokePassing: entries.filter((entry) => entry.smoke?.pass).length,
|
|
9538
|
+
warnings: entries.reduce((total, entry) => total + entry.summary.warnings, 0)
|
|
9539
|
+
};
|
|
9540
|
+
return {
|
|
9541
|
+
entries,
|
|
9542
|
+
generatedAt: options.generatedAt ?? Date.now(),
|
|
9543
|
+
pass: entries.length > 0 && entries.every((entry) => entry.status !== "fail"),
|
|
9544
|
+
summary
|
|
9545
|
+
};
|
|
9546
|
+
};
|
|
9547
|
+
var badgeStyles = {
|
|
9548
|
+
fail: "background:#fee2e2;color:#991b1b;border-color:#fecaca;",
|
|
9549
|
+
pass: "background:#dcfce7;color:#166534;border-color:#bbf7d0;",
|
|
9550
|
+
warn: "background:#fef3c7;color:#92400e;border-color:#fde68a;"
|
|
9551
|
+
};
|
|
9552
|
+
var renderVoiceTelephonyCarrierMatrixHTML = (matrix, options = {}) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 1040px; margin: 40px auto; padding: 0 20px; color: #172033;">
|
|
9553
|
+
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Carrier matrix</p>
|
|
9554
|
+
<h1 style="font-size: 34px; margin: 0 0 8px;">${escapeHtml14(options.title ?? "AbsoluteJS Voice Carrier Matrix")}</h1>
|
|
9555
|
+
<p style="color:#52606d; margin: 0 0 24px;">${matrix.summary.ready}/${matrix.summary.providers} ready, ${matrix.summary.contractsPassing}/${matrix.summary.providers} contract passing, ${matrix.summary.smokePassing}/${matrix.summary.providers} smoke passing.</p>
|
|
9556
|
+
<section style="display:grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 16px;">
|
|
9557
|
+
${matrix.entries.map((entry) => `<article style="border:1px solid #d9e2ec; border-radius:18px; padding:18px; background:#fff; box-shadow:0 18px 48px rgba(15,23,42,.08);">
|
|
9558
|
+
<div style="display:flex; justify-content:space-between; gap:12px; align-items:center;">
|
|
9559
|
+
<h2 style="margin:0; font-size:20px;">${escapeHtml14(entry.name)}</h2>
|
|
9560
|
+
<span style="border:1px solid; border-radius:999px; padding:4px 10px; font-size:12px; font-weight:700; ${badgeStyles[entry.status]}">${escapeHtml14(entry.status.toUpperCase())}</span>
|
|
9561
|
+
</div>
|
|
9562
|
+
<dl style="display:grid; grid-template-columns: 1fr 1fr; gap:8px 12px; margin:16px 0;">
|
|
9563
|
+
<dt style="color:#64748b;">Setup</dt><dd style="margin:0; font-weight:700;">${entry.ready ? "Ready" : "Needs attention"}</dd>
|
|
9564
|
+
<dt style="color:#64748b;">Signing</dt><dd style="margin:0; font-weight:700;">${entry.setup.signing.configured ? entry.setup.signing.mode : "missing"}</dd>
|
|
9565
|
+
<dt style="color:#64748b;">Smoke</dt><dd style="margin:0; font-weight:700;">${entry.smoke ? entry.smoke.pass ? "Pass" : "Fail" : "Missing"}</dd>
|
|
9566
|
+
<dt style="color:#64748b;">Contract</dt><dd style="margin:0; font-weight:700;">${entry.contract.pass ? "Pass" : "Fail"}</dd>
|
|
9567
|
+
</dl>
|
|
9568
|
+
<p style="margin:0 0 8px; color:#475569;"><strong>Stream:</strong> <code>${escapeHtml14(entry.setup.urls.stream || "missing")}</code></p>
|
|
9569
|
+
<p style="margin:0 0 12px; color:#475569;"><strong>Webhook:</strong> <code>${escapeHtml14(entry.setup.urls.webhook || "missing")}</code></p>
|
|
9570
|
+
${entry.issues.length ? `<ul style="margin:12px 0 0; padding-left:18px;">${entry.issues.map((issue) => `<li>${escapeHtml14(issue.severity)}: ${escapeHtml14(issue.message)}</li>`).join("")}</ul>` : '<p style="margin:12px 0 0; color:#166534;">No contract issues.</p>'}
|
|
9571
|
+
</article>`).join("")}
|
|
9572
|
+
</section>
|
|
9573
|
+
</main>`;
|
|
9574
|
+
var createVoiceTelephonyCarrierMatrixRoutes = (options) => {
|
|
9575
|
+
const path = options.path ?? "/api/voice/telephony/carriers";
|
|
9576
|
+
return new Elysia12({
|
|
9577
|
+
name: options.name ?? "absolutejs-voice-telephony-carrier-matrix"
|
|
9578
|
+
}).get(path, async ({ query, request }) => {
|
|
9579
|
+
const providers = await options.load({ query, request });
|
|
9580
|
+
const matrix = createVoiceTelephonyCarrierMatrix({
|
|
9581
|
+
contract: options.contract,
|
|
9582
|
+
providers
|
|
9583
|
+
});
|
|
9584
|
+
if (query.format === "html") {
|
|
9585
|
+
return new Response(renderVoiceTelephonyCarrierMatrixHTML(matrix, {
|
|
9586
|
+
title: options.title
|
|
9587
|
+
}), {
|
|
9588
|
+
headers: {
|
|
9589
|
+
"content-type": "text/html; charset=utf-8"
|
|
9590
|
+
}
|
|
9591
|
+
});
|
|
9592
|
+
}
|
|
9593
|
+
return matrix;
|
|
9594
|
+
});
|
|
9595
|
+
};
|
|
9596
|
+
|
|
9597
|
+
// src/productionReadiness.ts
|
|
9598
|
+
var escapeHtml15 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
9599
|
+
var rollupStatus = (checks) => checks.some((check) => check.status === "fail") ? "fail" : checks.some((check) => check.status === "warn") ? "warn" : "pass";
|
|
9600
|
+
var carrierStatus = (matrix) => matrix.summary.failing > 0 ? "fail" : matrix.summary.warnings > 0 || matrix.summary.ready < matrix.summary.providers ? "warn" : "pass";
|
|
9601
|
+
var resolveCarriers = async (options, input) => {
|
|
9602
|
+
if (options.carriers === false || options.carriers === undefined) {
|
|
9603
|
+
return;
|
|
9604
|
+
}
|
|
9605
|
+
const providers = typeof options.carriers === "function" ? await options.carriers(input) : options.carriers;
|
|
9606
|
+
return createVoiceTelephonyCarrierMatrix({
|
|
9607
|
+
providers: [...providers]
|
|
9608
|
+
});
|
|
9609
|
+
};
|
|
9610
|
+
var buildVoiceProductionReadinessReport = async (options, input = {}) => {
|
|
9611
|
+
const request = input.request ?? new Request("http://localhost/");
|
|
9612
|
+
const query = input.query ?? {};
|
|
9613
|
+
const events = await options.store.list();
|
|
9614
|
+
const routingEvents = listVoiceRoutingEvents(events);
|
|
9615
|
+
const routingSessions = summarizeVoiceRoutingSessions(routingEvents);
|
|
9616
|
+
const [quality, providers, sessions, handoffs, carriers] = await Promise.all([
|
|
9617
|
+
evaluateVoiceQuality({ events }),
|
|
9618
|
+
Promise.all([
|
|
9619
|
+
summarizeVoiceProviderHealth({
|
|
9620
|
+
events,
|
|
9621
|
+
providers: options.llmProviders ?? []
|
|
9622
|
+
}),
|
|
9623
|
+
summarizeVoiceProviderHealth({
|
|
9624
|
+
events: events.filter((event) => event.payload.kind === "stt"),
|
|
9625
|
+
providers: options.sttProviders ?? []
|
|
9626
|
+
}),
|
|
9627
|
+
summarizeVoiceProviderHealth({
|
|
9628
|
+
events: events.filter((event) => event.payload.kind === "tts"),
|
|
9629
|
+
providers: options.ttsProviders ?? []
|
|
9630
|
+
})
|
|
9631
|
+
]).then((groups) => groups.flat()),
|
|
9632
|
+
summarizeVoiceSessions({ events, status: "all" }),
|
|
9633
|
+
summarizeVoiceHandoffHealth({ events }),
|
|
9634
|
+
resolveCarriers(options, { query, request })
|
|
9635
|
+
]);
|
|
9636
|
+
const degradedProviders = providers.filter((provider) => provider.status === "degraded" || provider.status === "rate-limited" || provider.status === "suppressed").length;
|
|
9637
|
+
const failedSessions = sessions.filter((session) => session.status === "failed").length;
|
|
9638
|
+
const checks = [
|
|
9639
|
+
{
|
|
9640
|
+
detail: quality.status === "pass" ? "Quality gates are passing." : "Quality gates need attention.",
|
|
9641
|
+
href: options.links?.quality ?? "/quality",
|
|
9642
|
+
label: "Quality gates",
|
|
9643
|
+
status: quality.status,
|
|
9644
|
+
value: quality.status
|
|
9645
|
+
},
|
|
9646
|
+
{
|
|
9647
|
+
detail: degradedProviders === 0 ? "No configured providers are currently degraded." : `${degradedProviders} provider(s) are degraded, suppressed, or rate-limited.`,
|
|
9648
|
+
href: options.links?.resilience ?? "/resilience",
|
|
9649
|
+
label: "Provider health",
|
|
9650
|
+
status: degradedProviders > 0 ? "fail" : "pass",
|
|
9651
|
+
value: degradedProviders
|
|
9652
|
+
},
|
|
9653
|
+
{
|
|
9654
|
+
detail: failedSessions === 0 ? sessions.length > 0 ? "Recent sessions have no recorded provider/session failures." : "No sessions have been recorded yet; run a smoke or live session for proof." : `${failedSessions} recent session(s) have failures.`,
|
|
9655
|
+
href: options.links?.sessions ?? "/sessions",
|
|
9656
|
+
label: "Session health",
|
|
9657
|
+
status: failedSessions > 0 ? "fail" : sessions.length === 0 ? "warn" : "pass",
|
|
9658
|
+
value: `${sessions.length - failedSessions}/${sessions.length}`
|
|
9659
|
+
},
|
|
9660
|
+
{
|
|
9661
|
+
detail: handoffs.failed === 0 ? "No failed handoff deliveries are recorded." : `${handoffs.failed} handoff delivery failure(s) are recorded.`,
|
|
9662
|
+
href: options.links?.handoffs ?? "/handoffs",
|
|
9663
|
+
label: "Handoff delivery",
|
|
9664
|
+
status: handoffs.failed > 0 ? "fail" : "pass",
|
|
9665
|
+
value: `${handoffs.total - handoffs.failed}/${handoffs.total}`
|
|
9666
|
+
},
|
|
9667
|
+
{
|
|
9668
|
+
detail: routingEvents.length > 0 ? `${routingSessions.length} session(s) have provider routing evidence.` : "No provider routing traces are recorded yet.",
|
|
9669
|
+
href: options.links?.resilience ?? "/resilience",
|
|
9670
|
+
label: "Routing evidence",
|
|
9671
|
+
status: routingEvents.length > 0 ? "pass" : "warn",
|
|
9672
|
+
value: routingEvents.length
|
|
9673
|
+
}
|
|
9674
|
+
];
|
|
9675
|
+
const carrierSummary = carriers ? {
|
|
9676
|
+
failing: carriers.summary.failing,
|
|
9677
|
+
providers: carriers.summary.providers,
|
|
9678
|
+
ready: carriers.summary.ready,
|
|
9679
|
+
status: carrierStatus(carriers),
|
|
9680
|
+
warnings: carriers.summary.warnings
|
|
9681
|
+
} : undefined;
|
|
9682
|
+
if (carriers && carrierSummary) {
|
|
9683
|
+
checks.push({
|
|
9684
|
+
detail: carrierSummary.status === "pass" ? "Configured carrier setup and contract checks are passing." : `${carrierSummary.failing} carrier(s) failing, ${carrierSummary.warnings} warning(s).`,
|
|
9685
|
+
href: options.links?.carriers ?? "/carriers",
|
|
9686
|
+
label: "Carrier readiness",
|
|
9687
|
+
status: carrierSummary.status,
|
|
9688
|
+
value: `${carrierSummary.ready}/${carrierSummary.providers}`
|
|
9689
|
+
});
|
|
9690
|
+
}
|
|
9691
|
+
return {
|
|
9692
|
+
checkedAt: Date.now(),
|
|
9693
|
+
checks,
|
|
9694
|
+
links: {
|
|
9695
|
+
carriers: "/carriers",
|
|
9696
|
+
handoffs: "/handoffs",
|
|
9697
|
+
quality: "/quality",
|
|
9698
|
+
resilience: "/resilience",
|
|
9699
|
+
sessions: "/sessions",
|
|
9700
|
+
...options.links
|
|
9701
|
+
},
|
|
9702
|
+
status: rollupStatus(checks),
|
|
9703
|
+
summary: {
|
|
9704
|
+
carriers: carrierSummary,
|
|
9705
|
+
handoffs: {
|
|
9706
|
+
failed: handoffs.failed,
|
|
9707
|
+
total: handoffs.total
|
|
9708
|
+
},
|
|
9709
|
+
providers: {
|
|
9710
|
+
degraded: degradedProviders,
|
|
9711
|
+
total: providers.length
|
|
9712
|
+
},
|
|
9713
|
+
quality: {
|
|
9714
|
+
status: quality.status
|
|
9715
|
+
},
|
|
9716
|
+
routing: {
|
|
9717
|
+
events: routingEvents.length,
|
|
9718
|
+
sessions: routingSessions.length
|
|
9719
|
+
},
|
|
9720
|
+
sessions: {
|
|
9721
|
+
failed: failedSessions,
|
|
9722
|
+
total: sessions.length
|
|
9723
|
+
}
|
|
9724
|
+
}
|
|
9725
|
+
};
|
|
9726
|
+
};
|
|
9727
|
+
var renderVoiceProductionReadinessHTML = (report, options = {}) => {
|
|
9728
|
+
const title = options.title ?? "AbsoluteJS Voice Production Readiness";
|
|
9729
|
+
const checks = report.checks.map((check) => `<article class="check ${escapeHtml15(check.status)}">
|
|
9730
|
+
<div>
|
|
9731
|
+
<span>${escapeHtml15(check.status.toUpperCase())}</span>
|
|
9732
|
+
<h2>${escapeHtml15(check.label)}</h2>
|
|
9733
|
+
${check.detail ? `<p>${escapeHtml15(check.detail)}</p>` : ""}
|
|
9734
|
+
</div>
|
|
9735
|
+
<strong>${escapeHtml15(String(check.value ?? check.status))}</strong>
|
|
9736
|
+
${check.href ? `<a href="${escapeHtml15(check.href)}">Open surface</a>` : ""}
|
|
9737
|
+
</article>`).join("");
|
|
9738
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml15(title)}</title><style>body{background:#0c0f14;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1060px;padding:32px}.hero{background:linear-gradient(135deg,rgba(20,184,166,.18),rgba(245,158,11,.12));border:1px solid #26313d;border-radius:28px;margin-bottom:18px;padding:28px}.eyebrow{color:#fbbf24;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,5rem);line-height:.9;margin:.2rem 0 1rem}.status{display:inline-flex;border:1px solid #3f3f46;border-radius:999px;padding:8px 12px}.status.pass,.check.pass{border-color:rgba(34,197,94,.55)}.status.warn,.check.warn{border-color:rgba(245,158,11,.65)}.status.fail,.check.fail{border-color:rgba(239,68,68,.75)}.checks{display:grid;gap:14px}.check{align-items:center;background:#141922;border:1px solid #26313d;border-radius:22px;display:grid;gap:16px;grid-template-columns:1fr auto auto;padding:18px}.check span{color:#a8b0b8;font-size:.78rem;font-weight:900;letter-spacing:.08em}.check h2{margin:.2rem 0}.check p{color:#b9c0c8;margin:.2rem 0 0}.check strong{font-size:1.5rem}.check a,a{color:#fbbf24}@media(max-width:760px){main{padding:20px}.check{grid-template-columns:1fr}}</style></head><body><main><section class="hero"><p class="eyebrow">Self-hosted readiness</p><h1>${escapeHtml15(title)}</h1><p>One deployable pass/fail report for quality gates, provider failover, session health, handoffs, routing evidence, and optional carrier readiness.</p><p class="status ${escapeHtml15(report.status)}">Overall: ${escapeHtml15(report.status.toUpperCase())}</p><p>Checked ${escapeHtml15(new Date(report.checkedAt).toLocaleString())}</p></section><section class="checks">${checks}</section></main></body></html>`;
|
|
9739
|
+
};
|
|
9740
|
+
var createVoiceProductionReadinessRoutes = (options) => {
|
|
9741
|
+
const path = options.path ?? "/api/production-readiness";
|
|
9742
|
+
const htmlPath = options.htmlPath ?? "/production-readiness";
|
|
9743
|
+
const routes = new Elysia13({
|
|
9744
|
+
name: options.name ?? "absolutejs-voice-production-readiness"
|
|
9745
|
+
});
|
|
9746
|
+
routes.get(path, async ({ query, request }) => buildVoiceProductionReadinessReport(options, { query, request }));
|
|
9747
|
+
if (htmlPath !== false) {
|
|
9748
|
+
routes.get(htmlPath, async ({ query, request }) => {
|
|
9749
|
+
const report = await buildVoiceProductionReadinessReport(options, {
|
|
9750
|
+
query,
|
|
9751
|
+
request
|
|
9752
|
+
});
|
|
9753
|
+
const body = await (options.render ?? renderVoiceProductionReadinessHTML)(report);
|
|
9754
|
+
return new Response(body, {
|
|
9755
|
+
headers: {
|
|
9756
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
9757
|
+
...options.headers
|
|
9758
|
+
}
|
|
9759
|
+
});
|
|
9760
|
+
});
|
|
9761
|
+
}
|
|
9762
|
+
return routes;
|
|
9763
|
+
};
|
|
9764
|
+
|
|
9300
9765
|
// src/appKit.ts
|
|
9301
9766
|
var DEFAULT_LINKS2 = [
|
|
9302
9767
|
{
|
|
@@ -9321,6 +9786,12 @@ var DEFAULT_LINKS2 = [
|
|
|
9321
9786
|
href: "/resilience",
|
|
9322
9787
|
label: "Resilience"
|
|
9323
9788
|
},
|
|
9789
|
+
{
|
|
9790
|
+
description: "One JSON/HTML production readiness rollup.",
|
|
9791
|
+
href: "/production-readiness",
|
|
9792
|
+
label: "Production Readiness",
|
|
9793
|
+
statusHref: "/api/production-readiness"
|
|
9794
|
+
},
|
|
9324
9795
|
{
|
|
9325
9796
|
description: "Recent sessions and replay links.",
|
|
9326
9797
|
href: "/sessions",
|
|
@@ -9453,7 +9924,7 @@ var summarizeVoiceAppKitStatus = async (options) => {
|
|
|
9453
9924
|
};
|
|
9454
9925
|
};
|
|
9455
9926
|
var createVoiceAppKitRoutes = (options) => {
|
|
9456
|
-
const routes = new
|
|
9927
|
+
const routes = new Elysia14({
|
|
9457
9928
|
name: options.name ?? "absolutejs-voice-app-kit"
|
|
9458
9929
|
});
|
|
9459
9930
|
const links = resolveLinks(options.links);
|
|
@@ -9560,6 +10031,23 @@ var createVoiceAppKitRoutes = (options) => {
|
|
|
9560
10031
|
...options.resilience
|
|
9561
10032
|
}));
|
|
9562
10033
|
}
|
|
10034
|
+
if (options.productionReadiness !== false) {
|
|
10035
|
+
surfaces.push("productionReadiness");
|
|
10036
|
+
routes.use(createVoiceProductionReadinessRoutes({
|
|
10037
|
+
...common,
|
|
10038
|
+
links: {
|
|
10039
|
+
handoffs: "/handoffs",
|
|
10040
|
+
quality: "/quality",
|
|
10041
|
+
resilience: "/resilience",
|
|
10042
|
+
sessions: "/sessions"
|
|
10043
|
+
},
|
|
10044
|
+
llmProviders: options.llmProviders,
|
|
10045
|
+
sttProviders: options.sttProviders,
|
|
10046
|
+
title: options.title ? `${options.title} Production Readiness` : undefined,
|
|
10047
|
+
ttsProviders: options.ttsProviders,
|
|
10048
|
+
...options.productionReadiness
|
|
10049
|
+
}));
|
|
10050
|
+
}
|
|
9563
10051
|
if (options.opsConsole !== false) {
|
|
9564
10052
|
surfaces.push("opsConsole");
|
|
9565
10053
|
routes.use(createVoiceOpsConsoleRoutes({
|
|
@@ -10071,7 +10559,7 @@ var createVoiceToolIdempotencyKey = (input) => {
|
|
|
10071
10559
|
].join(":");
|
|
10072
10560
|
};
|
|
10073
10561
|
// src/toolContract.ts
|
|
10074
|
-
import { Elysia as
|
|
10562
|
+
import { Elysia as Elysia15 } from "elysia";
|
|
10075
10563
|
var createDefaultSession = (contractId, caseId) => createVoiceSessionRecord(`tool-contract-${contractId}-${caseId}`);
|
|
10076
10564
|
var createDefaultTurn = (caseId) => ({
|
|
10077
10565
|
committedAt: Date.now(),
|
|
@@ -10081,7 +10569,7 @@ var createDefaultTurn = (caseId) => ({
|
|
|
10081
10569
|
});
|
|
10082
10570
|
var defaultApi = {};
|
|
10083
10571
|
var sameJSON = (left, right) => JSON.stringify(left) === JSON.stringify(right);
|
|
10084
|
-
var
|
|
10572
|
+
var escapeHtml16 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
10085
10573
|
var evaluateExpectation = (input) => {
|
|
10086
10574
|
const issues = [];
|
|
10087
10575
|
const expect = input.expect;
|
|
@@ -10247,19 +10735,19 @@ var renderVoiceToolContractHTML = (report, options = {}) => {
|
|
|
10247
10735
|
const title = options.title ?? "Voice Tool Contracts";
|
|
10248
10736
|
const contracts = report.contracts.map((contract) => {
|
|
10249
10737
|
const cases = contract.cases.map((testCase) => `<tr>
|
|
10250
|
-
<td>${
|
|
10738
|
+
<td>${escapeHtml16(testCase.label ?? testCase.caseId)}</td>
|
|
10251
10739
|
<td class="${testCase.pass ? "pass" : "fail"}">${testCase.pass ? "pass" : "fail"}</td>
|
|
10252
|
-
<td>${
|
|
10740
|
+
<td>${escapeHtml16(testCase.status)}</td>
|
|
10253
10741
|
<td>${String(testCase.attempts)}</td>
|
|
10254
10742
|
<td>${String(testCase.elapsedMs)}ms</td>
|
|
10255
10743
|
<td>${testCase.timedOut ? "yes" : "no"}</td>
|
|
10256
|
-
<td>${
|
|
10744
|
+
<td>${escapeHtml16(testCase.issues.map((issue) => issue.message).join(" ") || testCase.error || "")}</td>
|
|
10257
10745
|
</tr>`).join("");
|
|
10258
10746
|
return `<section class="contract ${contract.pass ? "pass" : "fail"}">
|
|
10259
10747
|
<div class="contract-header">
|
|
10260
10748
|
<div>
|
|
10261
|
-
<p class="eyebrow">${
|
|
10262
|
-
<h2>${
|
|
10749
|
+
<p class="eyebrow">${escapeHtml16(contract.toolName)}</p>
|
|
10750
|
+
<h2>${escapeHtml16(contract.label ?? contract.contractId)}</h2>
|
|
10263
10751
|
</div>
|
|
10264
10752
|
<strong class="${contract.pass ? "pass" : "fail"}">${contract.pass ? "Passing" : "Failing"}</strong>
|
|
10265
10753
|
</div>
|
|
@@ -10269,7 +10757,7 @@ var renderVoiceToolContractHTML = (report, options = {}) => {
|
|
|
10269
10757
|
</table>
|
|
10270
10758
|
</section>`;
|
|
10271
10759
|
}).join("");
|
|
10272
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
10760
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml16(title)}</title><style>body{background:#101316;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}.hero,.contract{background:#181d22;border:1px solid #2a323a;border-radius:20px;margin-bottom:16px;padding:20px}.hero{background:linear-gradient(135deg,rgba(34,197,94,.14),rgba(245,158,11,.12))}.eyebrow{color:#fbbf24;font-size:.78rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}h1{font-size:clamp(2.3rem,6vw,5rem);letter-spacing:-.06em;line-height:.9;margin:.2rem 0 1rem}.summary{display:flex;flex-wrap:wrap;gap:10px}.pill{background:#0f1217;border:1px solid #3f3f46;border-radius:999px;padding:7px 10px}.contract-header{align-items:flex-start;display:flex;gap:16px;justify-content:space-between}h2{margin:.2rem 0 1rem}.pass{color:#86efac}.fail{color:#fca5a5}.contract.fail{border-color:rgba(248,113,113,.45)}table{border-collapse:collapse;width:100%}td,th{border-bottom:1px solid #2a323a;padding:12px;text-align:left;vertical-align:top}th{color:#a8b0b8;font-size:.82rem}@media(max-width:800px){main{padding:18px}table{display:block;overflow:auto}.contract-header{display:block}}</style></head><body><main><section class="hero"><p class="eyebrow">Tool Reliability</p><h1>${escapeHtml16(title)}</h1><div class="summary"><span class="pill ${report.status === "pass" ? "pass" : "fail"}">${escapeHtml16(report.status)}</span><span class="pill">${String(report.passed)} passing</span><span class="pill">${String(report.failed)} failing</span><span class="pill">${String(report.total)} contracts</span></div></section>${contracts || '<section class="contract"><p>No tool contracts configured.</p></section>'}</main></body></html>`;
|
|
10273
10761
|
};
|
|
10274
10762
|
var createVoiceToolContractJSONHandler = (options) => () => runVoiceToolContractSuite(options);
|
|
10275
10763
|
var createVoiceToolContractHTMLHandler = (options) => async () => {
|
|
@@ -10286,7 +10774,7 @@ var createVoiceToolContractHTMLHandler = (options) => async () => {
|
|
|
10286
10774
|
var createVoiceToolContractRoutes = (options) => {
|
|
10287
10775
|
const path = options.path ?? "/api/tool-contracts";
|
|
10288
10776
|
const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
|
|
10289
|
-
const routes = new
|
|
10777
|
+
const routes = new Elysia15({
|
|
10290
10778
|
name: options.name ?? "absolutejs-voice-tool-contracts"
|
|
10291
10779
|
}).get(path, createVoiceToolContractJSONHandler(options));
|
|
10292
10780
|
if (htmlPath) {
|
|
@@ -10295,9 +10783,9 @@ var createVoiceToolContractRoutes = (options) => {
|
|
|
10295
10783
|
return routes;
|
|
10296
10784
|
};
|
|
10297
10785
|
// src/turnQuality.ts
|
|
10298
|
-
import { Elysia as
|
|
10786
|
+
import { Elysia as Elysia16 } from "elysia";
|
|
10299
10787
|
var DEFAULT_CONFIDENCE_WARN_THRESHOLD = 0.72;
|
|
10300
|
-
var
|
|
10788
|
+
var escapeHtml17 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
10301
10789
|
var getTurnLatencyMs = (turn) => {
|
|
10302
10790
|
const firstTranscriptAt = turn.transcripts.map((transcript) => transcript.endedAtMs ?? transcript.startedAtMs).filter((value) => typeof value === "number").sort((left, right) => left - right)[0];
|
|
10303
10791
|
if (firstTranscriptAt === undefined) {
|
|
@@ -10368,24 +10856,24 @@ var summarizeVoiceTurnQuality = async (options) => {
|
|
|
10368
10856
|
};
|
|
10369
10857
|
var renderVoiceTurnQualityHTML = (report, options = {}) => {
|
|
10370
10858
|
const title = options.title ?? "Voice Turn Quality";
|
|
10371
|
-
const turns = report.turns.map((turn) => `<article class="turn ${
|
|
10859
|
+
const turns = report.turns.map((turn) => `<article class="turn ${escapeHtml17(turn.status)}">
|
|
10372
10860
|
<div class="turn-header">
|
|
10373
10861
|
<div>
|
|
10374
|
-
<p class="eyebrow">${
|
|
10375
|
-
<h2>${
|
|
10862
|
+
<p class="eyebrow">${escapeHtml17(turn.sessionId)} \xB7 ${escapeHtml17(turn.turnId)}</p>
|
|
10863
|
+
<h2>${escapeHtml17(turn.text || "Empty turn")}</h2>
|
|
10376
10864
|
</div>
|
|
10377
|
-
<strong>${
|
|
10865
|
+
<strong>${escapeHtml17(turn.status)}</strong>
|
|
10378
10866
|
</div>
|
|
10379
10867
|
<dl>
|
|
10380
|
-
<div><dt>Source</dt><dd>${
|
|
10868
|
+
<div><dt>Source</dt><dd>${escapeHtml17(turn.source ?? "unknown")}</dd></div>
|
|
10381
10869
|
<div><dt>Confidence</dt><dd>${turn.averageConfidence === undefined ? "n/a" : `${Math.round(turn.averageConfidence * 100)}%`}</dd></div>
|
|
10382
|
-
<div><dt>Fallback</dt><dd>${turn.fallbackUsed ? `yes (${
|
|
10383
|
-
<div><dt>Correction</dt><dd>${turn.correctionChanged ? `changed${turn.correctionProvider ? ` by ${
|
|
10870
|
+
<div><dt>Fallback</dt><dd>${turn.fallbackUsed ? `yes (${escapeHtml17(turn.fallbackSelectionReason ?? "selected")})` : "no"}</dd></div>
|
|
10871
|
+
<div><dt>Correction</dt><dd>${turn.correctionChanged ? `changed${turn.correctionProvider ? ` by ${escapeHtml17(turn.correctionProvider)}` : ""}` : "none"}</dd></div>
|
|
10384
10872
|
<div><dt>Transcripts</dt><dd>${String(turn.selectedTranscriptCount)} selected \xB7 ${String(turn.finalTranscriptCount)} final \xB7 ${String(turn.partialTranscriptCount)} partial</dd></div>
|
|
10385
10873
|
<div><dt>Cost</dt><dd>${turn.costUnits === undefined ? "n/a" : String(turn.costUnits)}</dd></div>
|
|
10386
10874
|
</dl>
|
|
10387
10875
|
</article>`).join("");
|
|
10388
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
10876
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml17(title)}</title><style>body{background:#101316;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}.hero,.turn{background:#181d22;border:1px solid #2a323a;border-radius:20px;margin-bottom:16px;padding:20px}.hero{background:linear-gradient(135deg,rgba(251,191,36,.16),rgba(34,197,94,.1))}.eyebrow{color:#fbbf24;font-size:.78rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}h1{font-size:clamp(2.3rem,6vw,5rem);letter-spacing:-.06em;line-height:.9;margin:.2rem 0 1rem}h2{margin:.2rem 0 1rem}.summary{display:flex;flex-wrap:wrap;gap:10px}.pill{background:#0f1217;border:1px solid #3f3f46;border-radius:999px;padding:7px 10px}.turn-header{align-items:flex-start;display:flex;gap:16px;justify-content:space-between}.pass{color:#86efac}.warn,.unknown{color:#fde68a}.fail{color:#fca5a5}.turn.fail{border-color:rgba(248,113,113,.45)}dl{display:grid;gap:8px;grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}dt{color:#a8b0b8;font-size:.8rem}dd{margin:0}@media(max-width:800px){main{padding:18px}.turn-header{display:block}}</style></head><body><main><section class="hero"><p class="eyebrow">Realtime STT Debugging</p><h1>${escapeHtml17(title)}</h1><div class="summary"><span class="pill ${escapeHtml17(report.status)}">${escapeHtml17(report.status)}</span><span class="pill">${String(report.total)} turns</span><span class="pill">${String(report.warnings)} warnings</span><span class="pill">${String(report.failed)} failed</span><span class="pill">${String(report.sessions)} sessions</span></div></section>${turns || '<section class="turn"><p>No committed turns found.</p></section>'}</main></body></html>`;
|
|
10389
10877
|
};
|
|
10390
10878
|
var createVoiceTurnQualityJSONHandler = (options) => async () => summarizeVoiceTurnQuality(options);
|
|
10391
10879
|
var createVoiceTurnQualityHTMLHandler = (options) => async () => {
|
|
@@ -10402,7 +10890,7 @@ var createVoiceTurnQualityHTMLHandler = (options) => async () => {
|
|
|
10402
10890
|
var createVoiceTurnQualityRoutes = (options) => {
|
|
10403
10891
|
const path = options.path ?? "/api/turn-quality";
|
|
10404
10892
|
const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
|
|
10405
|
-
const routes = new
|
|
10893
|
+
const routes = new Elysia16({
|
|
10406
10894
|
name: options.name ?? "absolutejs-voice-turn-quality"
|
|
10407
10895
|
}).get(path, createVoiceTurnQualityJSONHandler(options));
|
|
10408
10896
|
if (htmlPath) {
|
|
@@ -10411,8 +10899,8 @@ var createVoiceTurnQualityRoutes = (options) => {
|
|
|
10411
10899
|
return routes;
|
|
10412
10900
|
};
|
|
10413
10901
|
// src/outcomeContract.ts
|
|
10414
|
-
import { Elysia as
|
|
10415
|
-
var
|
|
10902
|
+
import { Elysia as Elysia17 } from "elysia";
|
|
10903
|
+
var escapeHtml18 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
10416
10904
|
var getPayloadString = (event, key) => typeof event.payload[key] === "string" ? event.payload[key] : undefined;
|
|
10417
10905
|
var toList = async (input) => Array.isArray(input) ? input : await input?.list() ?? [];
|
|
10418
10906
|
var hydrateSessions = async (input) => {
|
|
@@ -10520,9 +11008,9 @@ var renderVoiceOutcomeContractHTML = (report, options = {}) => {
|
|
|
10520
11008
|
const contracts = report.contracts.map((contract) => `<section class="contract ${contract.pass ? "pass" : "fail"}">
|
|
10521
11009
|
<div class="contract-header">
|
|
10522
11010
|
<div>
|
|
10523
|
-
<p class="eyebrow">${
|
|
10524
|
-
<h2>${
|
|
10525
|
-
${contract.description ? `<p>${
|
|
11011
|
+
<p class="eyebrow">${escapeHtml18(contract.contractId)}</p>
|
|
11012
|
+
<h2>${escapeHtml18(contract.label ?? contract.contractId)}</h2>
|
|
11013
|
+
${contract.description ? `<p>${escapeHtml18(contract.description)}</p>` : ""}
|
|
10526
11014
|
</div>
|
|
10527
11015
|
<strong>${contract.pass ? "pass" : "fail"}</strong>
|
|
10528
11016
|
</div>
|
|
@@ -10533,9 +11021,9 @@ var renderVoiceOutcomeContractHTML = (report, options = {}) => {
|
|
|
10533
11021
|
<span>handoffs ${String(contract.matched.handoffs)}</span>
|
|
10534
11022
|
<span>events ${String(contract.matched.integrationEvents)}</span>
|
|
10535
11023
|
</div>
|
|
10536
|
-
${contract.issues.length ? `<ul>${contract.issues.map((issue) => `<li>${
|
|
11024
|
+
${contract.issues.length ? `<ul>${contract.issues.map((issue) => `<li>${escapeHtml18(issue.message)}</li>`).join("")}</ul>` : ""}
|
|
10537
11025
|
</section>`).join("");
|
|
10538
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
11026
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml18(title)}</title><style>body{background:#101316;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}.hero,.contract{background:#181d22;border:1px solid #2a323a;border-radius:20px;margin-bottom:16px;padding:20px}.hero{background:linear-gradient(135deg,rgba(34,197,94,.14),rgba(14,165,233,.12))}.eyebrow{color:#7dd3fc;font-size:.78rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}h1{font-size:clamp(2.3rem,6vw,5rem);letter-spacing:-.06em;line-height:.9;margin:.2rem 0 1rem}h2{margin:.2rem 0}.summary,.grid{display:flex;flex-wrap:wrap;gap:10px}.pill,.grid span{background:#0f1217;border:1px solid #3f3f46;border-radius:999px;padding:7px 10px}.contract-header{display:flex;gap:16px;justify-content:space-between}.pass{color:#86efac}.fail{color:#fca5a5}.contract.fail{border-color:rgba(248,113,113,.45)}li{margin:8px 0}@media(max-width:800px){main{padding:18px}.contract-header{display:block}}</style></head><body><main><section class="hero"><p class="eyebrow">Business Outcome Verification</p><h1>${escapeHtml18(title)}</h1><div class="summary"><span class="pill ${report.status}">${report.status}</span><span class="pill">${String(report.passed)} passing</span><span class="pill">${String(report.failed)} failing</span><span class="pill">${String(report.total)} contracts</span></div></section>${contracts || '<section class="contract"><p>No outcome contracts configured.</p></section>'}</main></body></html>`;
|
|
10539
11027
|
};
|
|
10540
11028
|
var createVoiceOutcomeContractJSONHandler = (options) => async () => runVoiceOutcomeContractSuite(options);
|
|
10541
11029
|
var createVoiceOutcomeContractHTMLHandler = (options) => async () => {
|
|
@@ -10551,7 +11039,7 @@ var createVoiceOutcomeContractHTMLHandler = (options) => async () => {
|
|
|
10551
11039
|
var createVoiceOutcomeContractRoutes = (options) => {
|
|
10552
11040
|
const path = options.path ?? "/api/outcome-contracts";
|
|
10553
11041
|
const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
|
|
10554
|
-
const routes = new
|
|
11042
|
+
const routes = new Elysia17({
|
|
10555
11043
|
name: options.name ?? "absolutejs-voice-outcome-contracts"
|
|
10556
11044
|
}).get(path, createVoiceOutcomeContractJSONHandler(options));
|
|
10557
11045
|
if (htmlPath) {
|
|
@@ -10560,7 +11048,7 @@ var createVoiceOutcomeContractRoutes = (options) => {
|
|
|
10560
11048
|
return routes;
|
|
10561
11049
|
};
|
|
10562
11050
|
// src/telephonyOutcome.ts
|
|
10563
|
-
import { Elysia as
|
|
11051
|
+
import { Elysia as Elysia18 } from "elysia";
|
|
10564
11052
|
var DEFAULT_COMPLETED_STATUSES = [
|
|
10565
11053
|
"answered",
|
|
10566
11054
|
"completed",
|
|
@@ -11207,7 +11695,7 @@ var createVoiceTelephonyWebhookHandler = (options = {}) => async (input) => {
|
|
|
11207
11695
|
var createVoiceTelephonyWebhookRoutes = (options = {}) => {
|
|
11208
11696
|
const path = options.path ?? "/api/voice/telephony/webhook";
|
|
11209
11697
|
const handler = createVoiceTelephonyWebhookHandler(options);
|
|
11210
|
-
return new
|
|
11698
|
+
return new Elysia18({
|
|
11211
11699
|
name: options.name ?? "absolutejs-voice-telephony-webhooks"
|
|
11212
11700
|
}).post(path, async ({ query, request }) => {
|
|
11213
11701
|
try {
|
|
@@ -13475,7 +13963,7 @@ var createVoiceMemoryStore = () => {
|
|
|
13475
13963
|
return { get, getOrCreate, list, remove, set };
|
|
13476
13964
|
};
|
|
13477
13965
|
// src/opsWebhook.ts
|
|
13478
|
-
import { Elysia as
|
|
13966
|
+
import { Elysia as Elysia19 } from "elysia";
|
|
13479
13967
|
var toHex5 = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
13480
13968
|
var signVoiceOpsWebhookBody = async (input) => {
|
|
13481
13969
|
const encoder = new TextEncoder;
|
|
@@ -13605,7 +14093,7 @@ var verifyVoiceOpsWebhookSignature = async (input) => {
|
|
|
13605
14093
|
};
|
|
13606
14094
|
var createVoiceOpsWebhookReceiverRoutes = (options = {}) => {
|
|
13607
14095
|
const path = options.path ?? "/api/voice-ops/webhook";
|
|
13608
|
-
return new
|
|
14096
|
+
return new Elysia19().post(path, async ({ body, request, set }) => {
|
|
13609
14097
|
const bodyText = typeof body === "string" ? body : JSON.stringify(body);
|
|
13610
14098
|
if (options.signingSecret) {
|
|
13611
14099
|
const verification = await verifyVoiceOpsWebhookSignature({
|
|
@@ -15334,7 +15822,7 @@ var createVoiceSTTRoutingCorrectionHandler = (mode = "generic") => {
|
|
|
15334
15822
|
};
|
|
15335
15823
|
// src/telephony/twilio.ts
|
|
15336
15824
|
import { Buffer as Buffer3 } from "buffer";
|
|
15337
|
-
import { Elysia as
|
|
15825
|
+
import { Elysia as Elysia20 } from "elysia";
|
|
15338
15826
|
var TWILIO_MULAW_SAMPLE_RATE = 8000;
|
|
15339
15827
|
var VOICE_PCM_SAMPLE_RATE = 16000;
|
|
15340
15828
|
var escapeXml2 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
@@ -15364,7 +15852,7 @@ var resolveTwilioStreamParameters = async (parameters, input) => {
|
|
|
15364
15852
|
return parameters;
|
|
15365
15853
|
};
|
|
15366
15854
|
var joinUrlPath = (origin, path) => `${origin.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
|
|
15367
|
-
var
|
|
15855
|
+
var escapeHtml19 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
15368
15856
|
var getWebhookVerificationUrl = (webhook, input) => {
|
|
15369
15857
|
if (!webhook?.verificationUrl) {
|
|
15370
15858
|
return;
|
|
@@ -15407,23 +15895,23 @@ var buildTwilioVoiceSetupStatus = async (options, input) => {
|
|
|
15407
15895
|
};
|
|
15408
15896
|
var renderTwilioVoiceSetupHTML = (status, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
|
|
15409
15897
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Twilio setup</p>
|
|
15410
|
-
<h1>${
|
|
15898
|
+
<h1>${escapeHtml19(title)}</h1>
|
|
15411
15899
|
<p><strong>Status:</strong> ${status.ready ? "Ready" : "Needs attention"}</p>
|
|
15412
15900
|
<section>
|
|
15413
15901
|
<h2>URLs</h2>
|
|
15414
15902
|
<ul>
|
|
15415
|
-
<li><strong>TwiML:</strong> <code>${
|
|
15416
|
-
<li><strong>Media stream:</strong> <code>${
|
|
15417
|
-
<li><strong>Status webhook:</strong> <code>${
|
|
15903
|
+
<li><strong>TwiML:</strong> <code>${escapeHtml19(status.urls.twiml)}</code></li>
|
|
15904
|
+
<li><strong>Media stream:</strong> <code>${escapeHtml19(status.urls.stream)}</code></li>
|
|
15905
|
+
<li><strong>Status webhook:</strong> <code>${escapeHtml19(status.urls.webhook)}</code></li>
|
|
15418
15906
|
</ul>
|
|
15419
15907
|
</section>
|
|
15420
15908
|
<section>
|
|
15421
15909
|
<h2>Signing</h2>
|
|
15422
15910
|
<p>Mode: <code>${status.signing.mode}</code></p>
|
|
15423
|
-
${status.signing.verificationUrl ? `<p>Verification URL: <code>${
|
|
15911
|
+
${status.signing.verificationUrl ? `<p>Verification URL: <code>${escapeHtml19(status.signing.verificationUrl)}</code></p>` : ""}
|
|
15424
15912
|
</section>
|
|
15425
|
-
${status.missing.length ? `<section><h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${
|
|
15426
|
-
${status.warnings.length ? `<section><h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${
|
|
15913
|
+
${status.missing.length ? `<section><h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml19(name)}</code></li>`).join("")}</ul></section>` : ""}
|
|
15914
|
+
${status.warnings.length ? `<section><h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml19(warning)}</li>`).join("")}</ul></section>` : ""}
|
|
15427
15915
|
</main>`;
|
|
15428
15916
|
var extractTwilioStreamUrl = (twiml) => twiml.match(/<Stream\b[^>]*\surl="([^"]+)"/i)?.[1]?.replaceAll("&", "&");
|
|
15429
15917
|
var createSmokeCheck = (name, status, message, details) => ({
|
|
@@ -15434,20 +15922,20 @@ var createSmokeCheck = (name, status, message, details) => ({
|
|
|
15434
15922
|
});
|
|
15435
15923
|
var renderTwilioVoiceSmokeHTML = (report, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
|
|
15436
15924
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Twilio smoke test</p>
|
|
15437
|
-
<h1>${
|
|
15925
|
+
<h1>${escapeHtml19(title)}</h1>
|
|
15438
15926
|
<p><strong>Status:</strong> ${report.pass ? "Pass" : "Fail"}</p>
|
|
15439
15927
|
<section>
|
|
15440
15928
|
<h2>Checks</h2>
|
|
15441
15929
|
<ul>
|
|
15442
|
-
${report.checks.map((check) => `<li><strong>${
|
|
15930
|
+
${report.checks.map((check) => `<li><strong>${escapeHtml19(check.name)}</strong>: ${escapeHtml19(check.status)}${check.message ? ` - ${escapeHtml19(check.message)}` : ""}</li>`).join("")}
|
|
15443
15931
|
</ul>
|
|
15444
15932
|
</section>
|
|
15445
15933
|
<section>
|
|
15446
15934
|
<h2>Observed URLs</h2>
|
|
15447
15935
|
<ul>
|
|
15448
|
-
<li><strong>TwiML:</strong> <code>${
|
|
15449
|
-
<li><strong>Stream:</strong> <code>${
|
|
15450
|
-
<li><strong>Webhook:</strong> <code>${
|
|
15936
|
+
<li><strong>TwiML:</strong> <code>${escapeHtml19(report.setup.urls.twiml)}</code></li>
|
|
15937
|
+
<li><strong>Stream:</strong> <code>${escapeHtml19(report.twiml?.streamUrl ?? report.setup.urls.stream)}</code></li>
|
|
15938
|
+
<li><strong>Webhook:</strong> <code>${escapeHtml19(report.setup.urls.webhook)}</code></li>
|
|
15451
15939
|
</ul>
|
|
15452
15940
|
</section>
|
|
15453
15941
|
</main>`;
|
|
@@ -15907,7 +16395,7 @@ var createTwilioVoiceRoutes = (options) => {
|
|
|
15907
16395
|
const smokePath = options.smoke?.path === false ? false : options.smoke?.path ?? "/api/voice/twilio/smoke";
|
|
15908
16396
|
const bridges = new WeakMap;
|
|
15909
16397
|
const webhookPolicy = options.webhook?.policy ?? options.outcomePolicy ?? createVoiceTelephonyOutcomePolicy();
|
|
15910
|
-
const app = new
|
|
16398
|
+
const app = new Elysia20({
|
|
15911
16399
|
name: options.name ?? "absolutejs-voice-twilio"
|
|
15912
16400
|
}).get(twimlPath, async ({ query, request }) => {
|
|
15913
16401
|
const streamUrl = await resolveTwilioStreamUrl(options, {
|
|
@@ -16041,90 +16529,11 @@ var createTwilioVoiceRoutes = (options) => {
|
|
|
16041
16529
|
return report;
|
|
16042
16530
|
});
|
|
16043
16531
|
};
|
|
16044
|
-
// src/telephony/contract.ts
|
|
16045
|
-
var DEFAULT_REQUIREMENTS = [
|
|
16046
|
-
"stream-url",
|
|
16047
|
-
"wss-stream",
|
|
16048
|
-
"webhook-url",
|
|
16049
|
-
"signed-webhook",
|
|
16050
|
-
"smoke-pass"
|
|
16051
|
-
];
|
|
16052
|
-
var hasFailingSmokeCheck = (smoke) => smoke?.checks.some((check) => check.status === "fail") ?? false;
|
|
16053
|
-
var evaluateVoiceTelephonyContract = (input) => {
|
|
16054
|
-
const requirements = input.options?.requirements ?? DEFAULT_REQUIREMENTS;
|
|
16055
|
-
const issues = [];
|
|
16056
|
-
const hasRequirement = (requirement) => requirements.includes(requirement);
|
|
16057
|
-
if (hasRequirement("stream-url") && !input.setup.urls.stream) {
|
|
16058
|
-
issues.push({
|
|
16059
|
-
message: "Missing media stream URL.",
|
|
16060
|
-
requirement: "stream-url",
|
|
16061
|
-
severity: "error"
|
|
16062
|
-
});
|
|
16063
|
-
}
|
|
16064
|
-
if (hasRequirement("wss-stream") && !input.setup.urls.stream.startsWith("wss://")) {
|
|
16065
|
-
issues.push({
|
|
16066
|
-
message: "Media stream URL must use wss://.",
|
|
16067
|
-
requirement: "wss-stream",
|
|
16068
|
-
severity: "error"
|
|
16069
|
-
});
|
|
16070
|
-
}
|
|
16071
|
-
if (hasRequirement("webhook-url") && !input.setup.urls.webhook) {
|
|
16072
|
-
issues.push({
|
|
16073
|
-
message: "Missing carrier webhook URL.",
|
|
16074
|
-
requirement: "webhook-url",
|
|
16075
|
-
severity: "error"
|
|
16076
|
-
});
|
|
16077
|
-
}
|
|
16078
|
-
if (hasRequirement("signed-webhook") && !input.setup.signing.configured) {
|
|
16079
|
-
issues.push({
|
|
16080
|
-
message: "Carrier webhook signature verification is not configured.",
|
|
16081
|
-
requirement: "signed-webhook",
|
|
16082
|
-
severity: "error"
|
|
16083
|
-
});
|
|
16084
|
-
}
|
|
16085
|
-
if (hasRequirement("smoke-pass")) {
|
|
16086
|
-
if (!input.smoke) {
|
|
16087
|
-
issues.push({
|
|
16088
|
-
message: "Missing telephony smoke test report.",
|
|
16089
|
-
requirement: "smoke-pass",
|
|
16090
|
-
severity: "error"
|
|
16091
|
-
});
|
|
16092
|
-
} else if (!input.smoke.pass || hasFailingSmokeCheck(input.smoke)) {
|
|
16093
|
-
issues.push({
|
|
16094
|
-
message: "Telephony smoke test did not pass.",
|
|
16095
|
-
requirement: "smoke-pass",
|
|
16096
|
-
severity: "error"
|
|
16097
|
-
});
|
|
16098
|
-
}
|
|
16099
|
-
}
|
|
16100
|
-
for (const warning of input.setup.warnings) {
|
|
16101
|
-
issues.push({
|
|
16102
|
-
message: warning,
|
|
16103
|
-
requirement: "stream-url",
|
|
16104
|
-
severity: "warning"
|
|
16105
|
-
});
|
|
16106
|
-
}
|
|
16107
|
-
for (const name of input.setup.missing) {
|
|
16108
|
-
issues.push({
|
|
16109
|
-
message: `${name} is missing.`,
|
|
16110
|
-
requirement: "webhook-url",
|
|
16111
|
-
severity: "error"
|
|
16112
|
-
});
|
|
16113
|
-
}
|
|
16114
|
-
return {
|
|
16115
|
-
issues,
|
|
16116
|
-
pass: issues.every((issue) => issue.severity !== "error"),
|
|
16117
|
-
provider: input.setup.provider,
|
|
16118
|
-
requirements,
|
|
16119
|
-
setup: input.setup,
|
|
16120
|
-
smoke: input.smoke
|
|
16121
|
-
};
|
|
16122
|
-
};
|
|
16123
16532
|
// src/telephony/telnyx.ts
|
|
16124
16533
|
import { Buffer as Buffer4 } from "buffer";
|
|
16125
|
-
import { Elysia as
|
|
16534
|
+
import { Elysia as Elysia21 } from "elysia";
|
|
16126
16535
|
var escapeXml3 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
16127
|
-
var
|
|
16536
|
+
var escapeHtml20 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
16128
16537
|
var joinUrlPath2 = (origin, path) => `${origin.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
|
|
16129
16538
|
var resolveRequestOrigin2 = (request) => {
|
|
16130
16539
|
const url = new URL(request.url);
|
|
@@ -16325,21 +16734,21 @@ var buildTelnyxVoiceSetupStatus = async (options, input) => {
|
|
|
16325
16734
|
};
|
|
16326
16735
|
var renderTelnyxSetupHTML = (status, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
|
|
16327
16736
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Telnyx setup</p>
|
|
16328
|
-
<h1>${
|
|
16737
|
+
<h1>${escapeHtml20(title)}</h1>
|
|
16329
16738
|
<p><strong>Status:</strong> ${status.ready ? "Ready" : "Needs attention"}</p>
|
|
16330
16739
|
<ul>
|
|
16331
|
-
<li><strong>TeXML:</strong> <code>${
|
|
16332
|
-
<li><strong>Media stream:</strong> <code>${
|
|
16333
|
-
<li><strong>Status webhook:</strong> <code>${
|
|
16740
|
+
<li><strong>TeXML:</strong> <code>${escapeHtml20(status.urls.texml)}</code></li>
|
|
16741
|
+
<li><strong>Media stream:</strong> <code>${escapeHtml20(status.urls.stream)}</code></li>
|
|
16742
|
+
<li><strong>Status webhook:</strong> <code>${escapeHtml20(status.urls.webhook)}</code></li>
|
|
16334
16743
|
</ul>
|
|
16335
|
-
${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${
|
|
16336
|
-
${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${
|
|
16744
|
+
${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml20(name)}</code></li>`).join("")}</ul>` : ""}
|
|
16745
|
+
${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml20(warning)}</li>`).join("")}</ul>` : ""}
|
|
16337
16746
|
</main>`;
|
|
16338
16747
|
var renderTelnyxSmokeHTML = (report, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
|
|
16339
16748
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Telnyx smoke test</p>
|
|
16340
|
-
<h1>${
|
|
16749
|
+
<h1>${escapeHtml20(title)}</h1>
|
|
16341
16750
|
<p><strong>Status:</strong> ${report.pass ? "Pass" : "Fail"}</p>
|
|
16342
|
-
<ul>${report.checks.map((check) => `<li><strong>${
|
|
16751
|
+
<ul>${report.checks.map((check) => `<li><strong>${escapeHtml20(check.name)}</strong>: ${escapeHtml20(check.status)}${check.message ? ` - ${escapeHtml20(check.message)}` : ""}</li>`).join("")}</ul>
|
|
16343
16752
|
</main>`;
|
|
16344
16753
|
var runTelnyxSmokeTest = async (input) => {
|
|
16345
16754
|
const setup = await buildTelnyxVoiceSetupStatus(input.options, input);
|
|
@@ -16433,7 +16842,7 @@ var createTelnyxVoiceRoutes = (options = {}) => {
|
|
|
16433
16842
|
publicKey: options.webhook?.publicKey,
|
|
16434
16843
|
toleranceSeconds: options.webhook?.toleranceSeconds
|
|
16435
16844
|
}) : undefined);
|
|
16436
|
-
const app = new
|
|
16845
|
+
const app = new Elysia21({
|
|
16437
16846
|
name: options.name ?? "absolutejs-voice-telnyx"
|
|
16438
16847
|
}).get(texmlPath, async ({ query, request }) => {
|
|
16439
16848
|
const streamUrl = await resolveTelnyxStreamUrl(options, {
|
|
@@ -16543,9 +16952,9 @@ var createTelnyxVoiceRoutes = (options = {}) => {
|
|
|
16543
16952
|
};
|
|
16544
16953
|
// src/telephony/plivo.ts
|
|
16545
16954
|
import { Buffer as Buffer5 } from "buffer";
|
|
16546
|
-
import { Elysia as
|
|
16955
|
+
import { Elysia as Elysia22 } from "elysia";
|
|
16547
16956
|
var escapeXml4 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
16548
|
-
var
|
|
16957
|
+
var escapeHtml21 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
16549
16958
|
var joinUrlPath3 = (origin, path) => `${origin.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
|
|
16550
16959
|
var resolveRequestOrigin3 = (request) => {
|
|
16551
16960
|
const url = new URL(request.url);
|
|
@@ -16796,21 +17205,21 @@ var buildPlivoVoiceSetupStatus = async (options, input) => {
|
|
|
16796
17205
|
};
|
|
16797
17206
|
var renderPlivoSetupHTML = (status, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
|
|
16798
17207
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Plivo setup</p>
|
|
16799
|
-
<h1>${
|
|
17208
|
+
<h1>${escapeHtml21(title)}</h1>
|
|
16800
17209
|
<p><strong>Status:</strong> ${status.ready ? "Ready" : "Needs attention"}</p>
|
|
16801
17210
|
<ul>
|
|
16802
|
-
<li><strong>Answer XML:</strong> <code>${
|
|
16803
|
-
<li><strong>Audio stream:</strong> <code>${
|
|
16804
|
-
<li><strong>Status webhook:</strong> <code>${
|
|
17211
|
+
<li><strong>Answer XML:</strong> <code>${escapeHtml21(status.urls.answer)}</code></li>
|
|
17212
|
+
<li><strong>Audio stream:</strong> <code>${escapeHtml21(status.urls.stream)}</code></li>
|
|
17213
|
+
<li><strong>Status webhook:</strong> <code>${escapeHtml21(status.urls.webhook)}</code></li>
|
|
16805
17214
|
</ul>
|
|
16806
|
-
${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${
|
|
16807
|
-
${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${
|
|
17215
|
+
${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml21(name)}</code></li>`).join("")}</ul>` : ""}
|
|
17216
|
+
${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml21(warning)}</li>`).join("")}</ul>` : ""}
|
|
16808
17217
|
</main>`;
|
|
16809
17218
|
var renderPlivoSmokeHTML = (report, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
|
|
16810
17219
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Plivo smoke test</p>
|
|
16811
|
-
<h1>${
|
|
17220
|
+
<h1>${escapeHtml21(title)}</h1>
|
|
16812
17221
|
<p><strong>Status:</strong> ${report.pass ? "Pass" : "Fail"}</p>
|
|
16813
|
-
<ul>${report.checks.map((check) => `<li><strong>${
|
|
17222
|
+
<ul>${report.checks.map((check) => `<li><strong>${escapeHtml21(check.name)}</strong>: ${escapeHtml21(check.status)}${check.message ? ` - ${escapeHtml21(check.message)}` : ""}</li>`).join("")}</ul>
|
|
16814
17223
|
</main>`;
|
|
16815
17224
|
var runPlivoSmokeTest = async (input) => {
|
|
16816
17225
|
const setup = await buildPlivoVoiceSetupStatus(input.options, input);
|
|
@@ -16905,7 +17314,7 @@ var createPlivoVoiceRoutes = (options = {}) => {
|
|
|
16905
17314
|
request: input.request
|
|
16906
17315
|
}) : verificationUrl ?? input.request.url
|
|
16907
17316
|
}) : undefined);
|
|
16908
|
-
const app = new
|
|
17317
|
+
const app = new Elysia22({
|
|
16909
17318
|
name: options.name ?? "absolutejs-voice-plivo"
|
|
16910
17319
|
}).get(answerPath, async ({ query, request }) => {
|
|
16911
17320
|
const streamUrl = await resolvePlivoStreamUrl(options, {
|
|
@@ -17013,111 +17422,6 @@ var createPlivoVoiceRoutes = (options = {}) => {
|
|
|
17013
17422
|
return report;
|
|
17014
17423
|
});
|
|
17015
17424
|
};
|
|
17016
|
-
// src/telephony/matrix.ts
|
|
17017
|
-
import { Elysia as Elysia21 } from "elysia";
|
|
17018
|
-
var escapeHtml20 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
17019
|
-
var labelForProvider = (provider) => provider.split("-").map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`).join(" ");
|
|
17020
|
-
var resolveEntryStatus = (contract, setup, smoke) => {
|
|
17021
|
-
if (!contract.pass || !setup.ready || smoke?.pass === false) {
|
|
17022
|
-
return "fail";
|
|
17023
|
-
}
|
|
17024
|
-
if (contract.issues.some((issue) => issue.severity === "warning") || setup.warnings.length > 0 || smoke?.checks.some((check) => check.status === "warn")) {
|
|
17025
|
-
return "warn";
|
|
17026
|
-
}
|
|
17027
|
-
return "pass";
|
|
17028
|
-
};
|
|
17029
|
-
var createVoiceTelephonyCarrierMatrix = (options) => {
|
|
17030
|
-
const entries = options.providers.map((provider) => {
|
|
17031
|
-
const contract = provider.contract ?? evaluateVoiceTelephonyContract({
|
|
17032
|
-
options: options.contract,
|
|
17033
|
-
setup: provider.setup,
|
|
17034
|
-
smoke: provider.smoke
|
|
17035
|
-
});
|
|
17036
|
-
const failures = provider.smoke?.checks.filter((check) => check.status === "fail").length ?? 0;
|
|
17037
|
-
const warnings = contract.issues.filter((issue) => issue.severity === "warning").length + (provider.smoke?.checks.filter((check) => check.status === "warn").length ?? 0);
|
|
17038
|
-
const errors = contract.issues.filter((issue) => issue.severity === "error").length;
|
|
17039
|
-
const status = resolveEntryStatus(contract, provider.setup, provider.smoke);
|
|
17040
|
-
return {
|
|
17041
|
-
contract,
|
|
17042
|
-
issues: contract.issues,
|
|
17043
|
-
name: provider.name ?? labelForProvider(provider.setup.provider),
|
|
17044
|
-
provider: provider.setup.provider,
|
|
17045
|
-
ready: provider.setup.ready,
|
|
17046
|
-
setup: provider.setup,
|
|
17047
|
-
smoke: provider.smoke,
|
|
17048
|
-
status,
|
|
17049
|
-
summary: {
|
|
17050
|
-
errors,
|
|
17051
|
-
failures,
|
|
17052
|
-
missing: provider.setup.missing.length,
|
|
17053
|
-
warnings
|
|
17054
|
-
}
|
|
17055
|
-
};
|
|
17056
|
-
});
|
|
17057
|
-
const summary = {
|
|
17058
|
-
contractsPassing: entries.filter((entry) => entry.contract.pass).length,
|
|
17059
|
-
failing: entries.filter((entry) => entry.status === "fail").length,
|
|
17060
|
-
providers: entries.length,
|
|
17061
|
-
ready: entries.filter((entry) => entry.ready).length,
|
|
17062
|
-
smokePassing: entries.filter((entry) => entry.smoke?.pass).length,
|
|
17063
|
-
warnings: entries.reduce((total, entry) => total + entry.summary.warnings, 0)
|
|
17064
|
-
};
|
|
17065
|
-
return {
|
|
17066
|
-
entries,
|
|
17067
|
-
generatedAt: options.generatedAt ?? Date.now(),
|
|
17068
|
-
pass: entries.length > 0 && entries.every((entry) => entry.status !== "fail"),
|
|
17069
|
-
summary
|
|
17070
|
-
};
|
|
17071
|
-
};
|
|
17072
|
-
var badgeStyles = {
|
|
17073
|
-
fail: "background:#fee2e2;color:#991b1b;border-color:#fecaca;",
|
|
17074
|
-
pass: "background:#dcfce7;color:#166534;border-color:#bbf7d0;",
|
|
17075
|
-
warn: "background:#fef3c7;color:#92400e;border-color:#fde68a;"
|
|
17076
|
-
};
|
|
17077
|
-
var renderVoiceTelephonyCarrierMatrixHTML = (matrix, options = {}) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 1040px; margin: 40px auto; padding: 0 20px; color: #172033;">
|
|
17078
|
-
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Carrier matrix</p>
|
|
17079
|
-
<h1 style="font-size: 34px; margin: 0 0 8px;">${escapeHtml20(options.title ?? "AbsoluteJS Voice Carrier Matrix")}</h1>
|
|
17080
|
-
<p style="color:#52606d; margin: 0 0 24px;">${matrix.summary.ready}/${matrix.summary.providers} ready, ${matrix.summary.contractsPassing}/${matrix.summary.providers} contract passing, ${matrix.summary.smokePassing}/${matrix.summary.providers} smoke passing.</p>
|
|
17081
|
-
<section style="display:grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 16px;">
|
|
17082
|
-
${matrix.entries.map((entry) => `<article style="border:1px solid #d9e2ec; border-radius:18px; padding:18px; background:#fff; box-shadow:0 18px 48px rgba(15,23,42,.08);">
|
|
17083
|
-
<div style="display:flex; justify-content:space-between; gap:12px; align-items:center;">
|
|
17084
|
-
<h2 style="margin:0; font-size:20px;">${escapeHtml20(entry.name)}</h2>
|
|
17085
|
-
<span style="border:1px solid; border-radius:999px; padding:4px 10px; font-size:12px; font-weight:700; ${badgeStyles[entry.status]}">${escapeHtml20(entry.status.toUpperCase())}</span>
|
|
17086
|
-
</div>
|
|
17087
|
-
<dl style="display:grid; grid-template-columns: 1fr 1fr; gap:8px 12px; margin:16px 0;">
|
|
17088
|
-
<dt style="color:#64748b;">Setup</dt><dd style="margin:0; font-weight:700;">${entry.ready ? "Ready" : "Needs attention"}</dd>
|
|
17089
|
-
<dt style="color:#64748b;">Signing</dt><dd style="margin:0; font-weight:700;">${entry.setup.signing.configured ? entry.setup.signing.mode : "missing"}</dd>
|
|
17090
|
-
<dt style="color:#64748b;">Smoke</dt><dd style="margin:0; font-weight:700;">${entry.smoke ? entry.smoke.pass ? "Pass" : "Fail" : "Missing"}</dd>
|
|
17091
|
-
<dt style="color:#64748b;">Contract</dt><dd style="margin:0; font-weight:700;">${entry.contract.pass ? "Pass" : "Fail"}</dd>
|
|
17092
|
-
</dl>
|
|
17093
|
-
<p style="margin:0 0 8px; color:#475569;"><strong>Stream:</strong> <code>${escapeHtml20(entry.setup.urls.stream || "missing")}</code></p>
|
|
17094
|
-
<p style="margin:0 0 12px; color:#475569;"><strong>Webhook:</strong> <code>${escapeHtml20(entry.setup.urls.webhook || "missing")}</code></p>
|
|
17095
|
-
${entry.issues.length ? `<ul style="margin:12px 0 0; padding-left:18px;">${entry.issues.map((issue) => `<li>${escapeHtml20(issue.severity)}: ${escapeHtml20(issue.message)}</li>`).join("")}</ul>` : '<p style="margin:12px 0 0; color:#166534;">No contract issues.</p>'}
|
|
17096
|
-
</article>`).join("")}
|
|
17097
|
-
</section>
|
|
17098
|
-
</main>`;
|
|
17099
|
-
var createVoiceTelephonyCarrierMatrixRoutes = (options) => {
|
|
17100
|
-
const path = options.path ?? "/api/voice/telephony/carriers";
|
|
17101
|
-
return new Elysia21({
|
|
17102
|
-
name: options.name ?? "absolutejs-voice-telephony-carrier-matrix"
|
|
17103
|
-
}).get(path, async ({ query, request }) => {
|
|
17104
|
-
const providers = await options.load({ query, request });
|
|
17105
|
-
const matrix = createVoiceTelephonyCarrierMatrix({
|
|
17106
|
-
contract: options.contract,
|
|
17107
|
-
providers
|
|
17108
|
-
});
|
|
17109
|
-
if (query.format === "html") {
|
|
17110
|
-
return new Response(renderVoiceTelephonyCarrierMatrixHTML(matrix, {
|
|
17111
|
-
title: options.title
|
|
17112
|
-
}), {
|
|
17113
|
-
headers: {
|
|
17114
|
-
"content-type": "text/html; charset=utf-8"
|
|
17115
|
-
}
|
|
17116
|
-
});
|
|
17117
|
-
}
|
|
17118
|
-
return matrix;
|
|
17119
|
-
});
|
|
17120
|
-
};
|
|
17121
17425
|
// src/telephony/response.ts
|
|
17122
17426
|
var normalizeWhitespace = (value) => value.replace(/\s+/g, " ").trim();
|
|
17123
17427
|
var DEFAULT_MAX_WORDS = 12;
|
|
@@ -17187,6 +17491,7 @@ export {
|
|
|
17187
17491
|
summarizeVoiceTrace,
|
|
17188
17492
|
summarizeVoiceSessions,
|
|
17189
17493
|
summarizeVoiceSessionReplay,
|
|
17494
|
+
summarizeVoiceRoutingSessions,
|
|
17190
17495
|
summarizeVoiceRoutingDecision,
|
|
17191
17496
|
summarizeVoiceProviderHealth,
|
|
17192
17497
|
summarizeVoiceProviderCapabilities,
|
|
@@ -17238,6 +17543,7 @@ export {
|
|
|
17238
17543
|
renderVoiceQualityHTML,
|
|
17239
17544
|
renderVoiceProviderHealthHTML,
|
|
17240
17545
|
renderVoiceProviderCapabilityHTML,
|
|
17546
|
+
renderVoiceProductionReadinessHTML,
|
|
17241
17547
|
renderVoiceOutcomeContractHTML,
|
|
17242
17548
|
renderVoiceOpsConsoleHTML,
|
|
17243
17549
|
renderVoiceHandoffHealthHTML,
|
|
@@ -17346,6 +17652,7 @@ export {
|
|
|
17346
17652
|
createVoiceProviderCapabilityRoutes,
|
|
17347
17653
|
createVoiceProviderCapabilityJSONHandler,
|
|
17348
17654
|
createVoiceProviderCapabilityHTMLHandler,
|
|
17655
|
+
createVoiceProductionReadinessRoutes,
|
|
17349
17656
|
createVoicePostgresTraceSinkDeliveryStore,
|
|
17350
17657
|
createVoicePostgresTraceEventStore,
|
|
17351
17658
|
createVoicePostgresTelephonyWebhookIdempotencyStore,
|
|
@@ -17449,6 +17756,7 @@ export {
|
|
|
17449
17756
|
compareVoiceEvalBaseline,
|
|
17450
17757
|
claimVoiceOpsTask,
|
|
17451
17758
|
buildVoiceTraceReplay,
|
|
17759
|
+
buildVoiceProductionReadinessReport,
|
|
17452
17760
|
buildVoiceOpsTaskFromSLABreach,
|
|
17453
17761
|
buildVoiceOpsTaskFromReview,
|
|
17454
17762
|
buildVoiceOpsConsoleReport,
|