@absolutejs/voice 0.0.22-beta.90 → 0.0.22-beta.92
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/angular/index.d.ts +1 -0
- package/dist/angular/index.js +144 -20
- package/dist/angular/voice-trace-timeline.service.d.ts +12 -0
- package/dist/appKit.d.ts +3 -1
- package/dist/bargeInRoutes.d.ts +56 -0
- package/dist/client/bargeInMonitor.d.ts +7 -0
- package/dist/client/duplex.d.ts +1 -1
- package/dist/client/index.d.ts +6 -0
- package/dist/client/index.js +274 -5
- package/dist/client/traceTimeline.d.ts +19 -0
- package/dist/client/traceTimelineWidget.d.ts +32 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +307 -213
- package/dist/react/VoiceTraceTimeline.d.ts +6 -0
- package/dist/react/index.d.ts +2 -0
- package/dist/react/index.js +316 -50
- package/dist/react/useVoiceTraceTimeline.d.ts +8 -0
- package/dist/svelte/createVoiceTraceTimeline.d.ts +10 -0
- package/dist/svelte/index.d.ts +1 -0
- package/dist/svelte/index.js +194 -16
- package/dist/testing/index.js +30 -5
- package/dist/trace.d.ts +1 -1
- package/dist/types.d.ts +40 -0
- package/dist/vue/index.d.ts +1 -0
- package/dist/vue/index.js +119 -6
- package/dist/vue/useVoiceTraceTimeline.d.ts +9 -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 Elysia16 } from "elysia";
|
|
5478
5478
|
|
|
5479
5479
|
// src/assistantHealth.ts
|
|
5480
5480
|
import { Elysia as Elysia3 } from "elysia";
|
|
@@ -6849,8 +6849,89 @@ var createVoiceAssistantHealthRoutes = (options) => {
|
|
|
6849
6849
|
return routes;
|
|
6850
6850
|
};
|
|
6851
6851
|
|
|
6852
|
-
// src/
|
|
6852
|
+
// src/bargeInRoutes.ts
|
|
6853
6853
|
import { Elysia as Elysia4 } from "elysia";
|
|
6854
|
+
var escapeHtml5 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
6855
|
+
var isBargeInPayload = (value) => !!value && typeof value === "object" && typeof value.at === "number" && typeof value.id === "string" && typeof value.reason === "string" && typeof value.status === "string";
|
|
6856
|
+
var toBargeInEvent = (event) => event.type === "client.barge_in" && isBargeInPayload(event.payload) ? event.payload : undefined;
|
|
6857
|
+
var summarizeVoiceBargeIn = (events, options = {}) => {
|
|
6858
|
+
const thresholdMs = options.thresholdMs ?? 250;
|
|
6859
|
+
const bargeInEvents = events.map(toBargeInEvent).filter((event) => !!event).sort((left, right) => left.at - right.at);
|
|
6860
|
+
const stopped = bargeInEvents.filter((event) => event.status === "stopped");
|
|
6861
|
+
const latencies = stopped.map((event) => event.latencyMs).filter((value) => typeof value === "number");
|
|
6862
|
+
const failed = stopped.filter((event) => typeof event.latencyMs === "number" && event.latencyMs > thresholdMs).length;
|
|
6863
|
+
const passed = stopped.length - failed;
|
|
6864
|
+
const grouped = new Map;
|
|
6865
|
+
for (const event of stopped) {
|
|
6866
|
+
const sessionId = event.sessionId ?? "unknown";
|
|
6867
|
+
grouped.set(sessionId, [...grouped.get(sessionId) ?? [], event]);
|
|
6868
|
+
}
|
|
6869
|
+
return {
|
|
6870
|
+
averageLatencyMs: latencies.length > 0 ? Math.round(latencies.reduce((total, value) => total + value, 0) / latencies.length) : undefined,
|
|
6871
|
+
checkedAt: Date.now(),
|
|
6872
|
+
events: bargeInEvents,
|
|
6873
|
+
failed,
|
|
6874
|
+
lastEvent: bargeInEvents.at(-1),
|
|
6875
|
+
passed,
|
|
6876
|
+
sessions: [...grouped.entries()].map(([sessionId, sessionEvents]) => {
|
|
6877
|
+
const sessionLatencies = sessionEvents.map((event) => event.latencyMs).filter((value) => typeof value === "number");
|
|
6878
|
+
const sessionFailed = sessionEvents.filter((event) => typeof event.latencyMs === "number" && event.latencyMs > thresholdMs).length;
|
|
6879
|
+
return {
|
|
6880
|
+
averageLatencyMs: sessionLatencies.length > 0 ? Math.round(sessionLatencies.reduce((total, value) => total + value, 0) / sessionLatencies.length) : undefined,
|
|
6881
|
+
failed: sessionFailed,
|
|
6882
|
+
passed: sessionEvents.length - sessionFailed,
|
|
6883
|
+
sessionId,
|
|
6884
|
+
total: sessionEvents.length
|
|
6885
|
+
};
|
|
6886
|
+
}).sort((left, right) => right.total - left.total),
|
|
6887
|
+
status: bargeInEvents.length === 0 ? "empty" : failed > 0 ? "fail" : stopped.length === 0 ? "warn" : "pass",
|
|
6888
|
+
thresholdMs,
|
|
6889
|
+
total: stopped.length
|
|
6890
|
+
};
|
|
6891
|
+
};
|
|
6892
|
+
var renderVoiceBargeInHTML = (report, options = {}) => {
|
|
6893
|
+
const title = options.title ?? "Voice Barge-In";
|
|
6894
|
+
const sessions = report.sessions.length ? report.sessions.map((session) => `<tr><td>${escapeHtml5(session.sessionId)}</td><td>${String(session.total)}</td><td>${String(session.passed)}</td><td>${String(session.failed)}</td><td>${String(session.averageLatencyMs ?? 0)}ms</td></tr>`).join("") : '<tr><td colspan="5">No barge-in events yet.</td></tr>';
|
|
6895
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml5(title)}</title><style>body{background:#101316;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1100px;padding:32px}.eyebrow{color:#5eead4;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,4.5rem);line-height:.92;margin:.2rem 0 1rem}.status{border:1px solid #475569;border-radius:999px;display:inline-flex;padding:8px 12px}.pass{color:#86efac}.warn{color:#fbbf24}.fail{color:#fca5a5}.empty{color:#cbd5e1}.metrics{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(170px,1fr));margin:20px 0}.metrics article{background:#181f27;border:1px solid #2b3642;border-radius:20px;padding:16px}.metrics span{color:#a8b0b8}.metrics strong{display:block;font-size:2rem}table{background:#181f27;border-collapse:collapse;border-radius:18px;overflow:hidden;width:100%}td,th{border-bottom:1px solid #2b3642;padding:12px;text-align:left}</style></head><body><main><p class="eyebrow">Interruption quality</p><h1>${escapeHtml5(title)}</h1><p class="status ${escapeHtml5(report.status)}">Status: ${escapeHtml5(report.status)}</p><section class="metrics"><article><span>Interruptions</span><strong>${String(report.total)}</strong></article><article><span>Avg latency</span><strong>${String(report.averageLatencyMs ?? 0)}ms</strong></article><article><span>Passed</span><strong>${String(report.passed)}</strong></article><article><span>Failed</span><strong>${String(report.failed)}</strong></article></section><table><thead><tr><th>Session</th><th>Total</th><th>Passed</th><th>Failed</th><th>Avg latency</th></tr></thead><tbody>${sessions}</tbody></table></main></body></html>`;
|
|
6896
|
+
};
|
|
6897
|
+
var createVoiceBargeInRoutes = (options) => {
|
|
6898
|
+
const path = options.path ?? "/api/voice-barge-in";
|
|
6899
|
+
const htmlPath = options.htmlPath ?? "/barge-in";
|
|
6900
|
+
const title = options.title ?? "AbsoluteJS Voice Barge-In";
|
|
6901
|
+
const routes = new Elysia4({
|
|
6902
|
+
name: options.name ?? "absolutejs-voice-barge-in"
|
|
6903
|
+
});
|
|
6904
|
+
routes.get(path, async () => summarizeVoiceBargeIn(await options.store.list(), {
|
|
6905
|
+
thresholdMs: options.thresholdMs
|
|
6906
|
+
}));
|
|
6907
|
+
routes.post(path, async ({ body }) => {
|
|
6908
|
+
if (!isBargeInPayload(body)) {
|
|
6909
|
+
return Response.json({ error: "Invalid barge-in event." }, { status: 400 });
|
|
6910
|
+
}
|
|
6911
|
+
await options.store.append({
|
|
6912
|
+
at: body.at,
|
|
6913
|
+
payload: body,
|
|
6914
|
+
sessionId: body.sessionId ?? "unknown",
|
|
6915
|
+
type: "client.barge_in"
|
|
6916
|
+
});
|
|
6917
|
+
return Response.json({ ok: true });
|
|
6918
|
+
});
|
|
6919
|
+
routes.get(htmlPath, async () => {
|
|
6920
|
+
const report = summarizeVoiceBargeIn(await options.store.list(), {
|
|
6921
|
+
thresholdMs: options.thresholdMs
|
|
6922
|
+
});
|
|
6923
|
+
return new Response(renderVoiceBargeInHTML(report, { title }), {
|
|
6924
|
+
headers: {
|
|
6925
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
6926
|
+
...options.headers
|
|
6927
|
+
}
|
|
6928
|
+
});
|
|
6929
|
+
});
|
|
6930
|
+
return routes;
|
|
6931
|
+
};
|
|
6932
|
+
|
|
6933
|
+
// src/diagnosticsRoutes.ts
|
|
6934
|
+
import { Elysia as Elysia5 } from "elysia";
|
|
6854
6935
|
|
|
6855
6936
|
// src/trace.ts
|
|
6856
6937
|
var createVoiceTraceEventId = (event) => [
|
|
@@ -7184,7 +7265,7 @@ var exportVoiceTrace = async (input) => {
|
|
|
7184
7265
|
};
|
|
7185
7266
|
};
|
|
7186
7267
|
var toNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
7187
|
-
var
|
|
7268
|
+
var escapeHtml6 = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
7188
7269
|
var formatTraceValue = (value) => {
|
|
7189
7270
|
if (value === undefined || value === null) {
|
|
7190
7271
|
return "";
|
|
@@ -7462,10 +7543,10 @@ var renderVoiceTraceHTML = (events, options = {}) => {
|
|
|
7462
7543
|
const offset = summary.startedAt === undefined ? event.at : Math.max(0, event.at - summary.startedAt);
|
|
7463
7544
|
return [
|
|
7464
7545
|
"<tr>",
|
|
7465
|
-
`<td>${
|
|
7466
|
-
`<td>${
|
|
7467
|
-
`<td>${
|
|
7468
|
-
`<td><code>${
|
|
7546
|
+
`<td>${escapeHtml6(String(offset))}</td>`,
|
|
7547
|
+
`<td>${escapeHtml6(event.type)}</td>`,
|
|
7548
|
+
`<td>${escapeHtml6(event.turnId ?? "")}</td>`,
|
|
7549
|
+
`<td><code>${escapeHtml6(JSON.stringify(event.payload))}</code></td>`,
|
|
7469
7550
|
"</tr>"
|
|
7470
7551
|
].join("");
|
|
7471
7552
|
}).join(`
|
|
@@ -7476,7 +7557,7 @@ var renderVoiceTraceHTML = (events, options = {}) => {
|
|
|
7476
7557
|
"<head>",
|
|
7477
7558
|
'<meta charset="utf-8" />',
|
|
7478
7559
|
'<meta name="viewport" content="width=device-width, initial-scale=1" />',
|
|
7479
|
-
`<title>${
|
|
7560
|
+
`<title>${escapeHtml6(options.title ?? "Voice Trace")}</title>`,
|
|
7480
7561
|
"<style>",
|
|
7481
7562
|
"body{font-family:ui-sans-serif,system-ui,sans-serif;margin:2rem;line-height:1.45;background:#f8f7f2;color:#181713}",
|
|
7482
7563
|
"main{max-width:1100px;margin:auto}",
|
|
@@ -7490,7 +7571,7 @@ var renderVoiceTraceHTML = (events, options = {}) => {
|
|
|
7490
7571
|
"</style>",
|
|
7491
7572
|
"</head>",
|
|
7492
7573
|
"<body><main>",
|
|
7493
|
-
`<h1>${
|
|
7574
|
+
`<h1>${escapeHtml6(options.title ?? `Voice Trace ${summary.sessionId ?? ""}`.trim())}</h1>`,
|
|
7494
7575
|
`<p class="${evaluation.pass ? "pass" : "fail"}">QA: ${evaluation.pass ? "pass" : "fail"}</p>`,
|
|
7495
7576
|
'<section class="summary">',
|
|
7496
7577
|
`<div class="card"><strong>Events</strong><br>${summary.eventCount}</div>`,
|
|
@@ -7504,7 +7585,7 @@ var renderVoiceTraceHTML = (events, options = {}) => {
|
|
|
7504
7585
|
eventRows,
|
|
7505
7586
|
"</tbody></table>",
|
|
7506
7587
|
"<h2>Markdown Export</h2>",
|
|
7507
|
-
`<pre>${
|
|
7588
|
+
`<pre>${escapeHtml6(markdown)}</pre>`,
|
|
7508
7589
|
"</main></body></html>"
|
|
7509
7590
|
].join(`
|
|
7510
7591
|
`);
|
|
@@ -7517,7 +7598,7 @@ var buildVoiceTraceReplay = (events, options = {}) => ({
|
|
|
7517
7598
|
});
|
|
7518
7599
|
|
|
7519
7600
|
// src/diagnosticsRoutes.ts
|
|
7520
|
-
var
|
|
7601
|
+
var escapeHtml7 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
7521
7602
|
var getString3 = (value) => typeof value === "string" && value.trim() ? value : undefined;
|
|
7522
7603
|
var getNumber2 = (value) => {
|
|
7523
7604
|
const parsed = typeof value === "number" ? value : typeof value === "string" ? Number(value) : undefined;
|
|
@@ -7583,9 +7664,9 @@ var renderDiagnosticsIndex = (input) => {
|
|
|
7583
7664
|
const rows = [...sessions.entries()].sort(([, left], [, right]) => (right.at(-1)?.at ?? 0) - (left.at(-1)?.at ?? 0)).slice(0, 50).map(([sessionId, events]) => {
|
|
7584
7665
|
const summary = summarizeVoiceTrace(events);
|
|
7585
7666
|
const encoded = encodeURIComponent(sessionId);
|
|
7586
|
-
return `<tr><td>${
|
|
7667
|
+
return `<tr><td>${escapeHtml7(sessionId)}</td><td>${summary.eventCount}</td><td>${summary.turnCount}</td><td>${summary.errorCount}</td><td><a href="${input.basePath}/html?sessionId=${encoded}&redact=true">HTML</a> \xB7 <a href="${input.basePath}/markdown?sessionId=${encoded}&redact=true">Markdown</a> \xB7 <a href="${input.basePath}/json?sessionId=${encoded}&redact=true">JSON</a></td></tr>`;
|
|
7587
7668
|
}).join("");
|
|
7588
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
7669
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml7(input.title)}</title><style>body{font-family:ui-sans-serif,system-ui,sans-serif;margin:2rem;background:#f8f7f2;color:#181713}main{max-width:1100px;margin:auto}table{width:100%;border-collapse:collapse;background:white}td,th{border-bottom:1px solid #eee;padding:.7rem;text-align:left}a{color:#9a3412}</style></head><body><main><h1>${escapeHtml7(input.title)}</h1><p>Recent voice trace diagnostics. Exports support filters: sessionId, traceId, turnId, scenarioId, type, provider, status, since, until, limit, redact.</p><table><thead><tr><th>Session</th><th>Events</th><th>Turns</th><th>Errors</th><th>Exports</th></tr></thead><tbody>${rows}</tbody></table></main></body></html>`;
|
|
7589
7670
|
};
|
|
7590
7671
|
var withRedaction = (events, query, defaultRedact) => {
|
|
7591
7672
|
const shouldRedact = query.redact === undefined ? defaultRedact : getBoolean(query.redact);
|
|
@@ -7594,7 +7675,7 @@ var withRedaction = (events, query, defaultRedact) => {
|
|
|
7594
7675
|
var createVoiceDiagnosticsRoutes = (options) => {
|
|
7595
7676
|
const path = options.path ?? "/diagnostics";
|
|
7596
7677
|
const title = options.title ?? "AbsoluteJS Voice Diagnostics";
|
|
7597
|
-
const routes = new
|
|
7678
|
+
const routes = new Elysia5({
|
|
7598
7679
|
name: options.name ?? "absolutejs-voice-diagnostics"
|
|
7599
7680
|
});
|
|
7600
7681
|
routes.get(path, async () => {
|
|
@@ -7652,16 +7733,16 @@ var createVoiceDiagnosticsRoutes = (options) => {
|
|
|
7652
7733
|
};
|
|
7653
7734
|
|
|
7654
7735
|
// src/evalRoutes.ts
|
|
7655
|
-
import { Elysia as
|
|
7736
|
+
import { Elysia as Elysia8 } from "elysia";
|
|
7656
7737
|
import { mkdir } from "fs/promises";
|
|
7657
7738
|
import { dirname } from "path";
|
|
7658
7739
|
|
|
7659
7740
|
// src/qualityRoutes.ts
|
|
7660
|
-
import { Elysia as
|
|
7741
|
+
import { Elysia as Elysia7 } from "elysia";
|
|
7661
7742
|
|
|
7662
7743
|
// src/handoffHealth.ts
|
|
7663
|
-
import { Elysia as
|
|
7664
|
-
var
|
|
7744
|
+
import { Elysia as Elysia6 } from "elysia";
|
|
7745
|
+
var escapeHtml8 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
7665
7746
|
var getString4 = (value) => typeof value === "string" && value.length > 0 ? value : undefined;
|
|
7666
7747
|
var isStatus = (value) => value === "delivered" || value === "failed" || value === "skipped";
|
|
7667
7748
|
var increment2 = (record, key) => {
|
|
@@ -7779,10 +7860,10 @@ var renderActionSummary = (summary) => {
|
|
|
7779
7860
|
return [
|
|
7780
7861
|
'<section class="voice-handoff-health-columns">',
|
|
7781
7862
|
"<article><h3>Actions</h3>",
|
|
7782
|
-
actions.length === 0 ? "<p>No handoff actions yet.</p>" : `<ul>${actions.map(([action, count]) => `<li>${
|
|
7863
|
+
actions.length === 0 ? "<p>No handoff actions yet.</p>" : `<ul>${actions.map(([action, count]) => `<li>${escapeHtml8(action)}: ${String(count)}</li>`).join("")}</ul>`,
|
|
7783
7864
|
"</article>",
|
|
7784
7865
|
"<article><h3>Adapters</h3>",
|
|
7785
|
-
adapters.length === 0 ? "<p>No adapter deliveries yet.</p>" : `<ul>${adapters.map(([adapterId, counts]) => `<li>${
|
|
7866
|
+
adapters.length === 0 ? "<p>No adapter deliveries yet.</p>" : `<ul>${adapters.map(([adapterId, counts]) => `<li>${escapeHtml8(adapterId)}: ${String(counts.delivered)} delivered / ${String(counts.failed)} failed / ${String(counts.skipped)} skipped</li>`).join("")}</ul>`,
|
|
7786
7867
|
"</article>",
|
|
7787
7868
|
"</section>"
|
|
7788
7869
|
].join("");
|
|
@@ -7796,22 +7877,22 @@ var renderVoiceHandoffHealthHTML = (summary) => [
|
|
|
7796
7877
|
summary.events.length === 0 ? '<p class="voice-handoff-health-empty">No handoffs found.</p>' : [
|
|
7797
7878
|
'<div class="voice-handoff-health-events">',
|
|
7798
7879
|
...summary.events.map((event) => [
|
|
7799
|
-
`<article class="${
|
|
7880
|
+
`<article class="${escapeHtml8(event.status)}">`,
|
|
7800
7881
|
'<div class="voice-handoff-health-event-header">',
|
|
7801
|
-
`<strong>${
|
|
7802
|
-
`<span>${
|
|
7882
|
+
`<strong>${escapeHtml8(event.action ?? "handoff")}</strong>`,
|
|
7883
|
+
`<span>${escapeHtml8(event.status)}</span>`,
|
|
7803
7884
|
"</div>",
|
|
7804
|
-
`<p><small>${
|
|
7805
|
-
event.target ? `<p>Target: ${
|
|
7806
|
-
event.reason ? `<p>Reason: ${
|
|
7885
|
+
`<p><small>${escapeHtml8(event.sessionId)}</small></p>`,
|
|
7886
|
+
event.target ? `<p>Target: ${escapeHtml8(event.target)}</p>` : "",
|
|
7887
|
+
event.reason ? `<p>Reason: ${escapeHtml8(event.reason)}</p>` : "",
|
|
7807
7888
|
event.deliveries.length ? `<ul>${event.deliveries.map((delivery) => [
|
|
7808
7889
|
"<li>",
|
|
7809
|
-
`${
|
|
7810
|
-
delivery.deliveredTo ? ` to ${
|
|
7811
|
-
delivery.error ? ` (${
|
|
7890
|
+
`${escapeHtml8(delivery.adapterId)}: ${escapeHtml8(delivery.status)}`,
|
|
7891
|
+
delivery.deliveredTo ? ` to ${escapeHtml8(delivery.deliveredTo)}` : "",
|
|
7892
|
+
delivery.error ? ` (${escapeHtml8(delivery.error)})` : "",
|
|
7812
7893
|
"</li>"
|
|
7813
7894
|
].join("")).join("")}</ul>` : "",
|
|
7814
|
-
event.replayHref ? `<p><a href="${
|
|
7895
|
+
event.replayHref ? `<p><a href="${escapeHtml8(event.replayHref)}">Open replay</a></p>` : "",
|
|
7815
7896
|
"</article>"
|
|
7816
7897
|
].join("")),
|
|
7817
7898
|
"</div>"
|
|
@@ -7843,7 +7924,7 @@ var createVoiceHandoffHealthHTMLHandler = (options = {}) => async ({ query }) =>
|
|
|
7843
7924
|
var createVoiceHandoffHealthRoutes = (options = {}) => {
|
|
7844
7925
|
const path = options.path ?? "/api/voice-handoffs";
|
|
7845
7926
|
const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
|
|
7846
|
-
const routes = new
|
|
7927
|
+
const routes = new Elysia6({
|
|
7847
7928
|
name: options.name ?? "absolutejs-voice-handoff-health"
|
|
7848
7929
|
}).get(path, createVoiceHandoffHealthJSONHandler(options));
|
|
7849
7930
|
if (htmlPath) {
|
|
@@ -7964,17 +8045,17 @@ var evaluateVoiceQuality = async (input) => {
|
|
|
7964
8045
|
thresholds
|
|
7965
8046
|
};
|
|
7966
8047
|
};
|
|
7967
|
-
var
|
|
8048
|
+
var escapeHtml9 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
7968
8049
|
var formatMetricValue = (metric) => metric.unit === "rate" ? `${(metric.actual * 100).toFixed(2)}%` : metric.unit === "ms" ? `${Math.round(metric.actual)}ms` : String(metric.actual);
|
|
7969
8050
|
var formatThreshold = (metric) => metric.unit === "rate" ? `${(metric.threshold * 100).toFixed(2)}%` : metric.unit === "ms" ? `${Math.round(metric.threshold)}ms` : String(metric.threshold);
|
|
7970
8051
|
var renderVoiceQualityHTML = (report, options = {}) => {
|
|
7971
|
-
const rows = Object.entries(report.metrics).map(([key, metric]) => `<tr class="${metric.pass ? "pass" : "fail"}"><td>${
|
|
7972
|
-
const links = options.links?.length ? `<nav>${options.links.map((link) => `<a href="${
|
|
8052
|
+
const rows = Object.entries(report.metrics).map(([key, metric]) => `<tr class="${metric.pass ? "pass" : "fail"}"><td>${escapeHtml9(metric.label)}</td><td>${escapeHtml9(formatMetricValue(metric))}</td><td>${escapeHtml9(formatThreshold(metric))}</td><td>${metric.pass ? "pass" : "fail"}</td><td><code>${escapeHtml9(key)}</code></td></tr>`).join("");
|
|
8053
|
+
const links = options.links?.length ? `<nav>${options.links.map((link) => `<a href="${escapeHtml9(link.href)}">${escapeHtml9(link.label)}</a>`).join("")}</nav>` : "";
|
|
7973
8054
|
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>AbsoluteJS Voice Quality</title><style>body{font-family:ui-sans-serif,system-ui,sans-serif;margin:2rem;background:#f8f7f2;color:#181713}main{max-width:1100px;margin:auto}nav{display:flex;flex-wrap:wrap;gap:.5rem;margin:0 0 1.25rem}nav a{background:#181713;border-radius:999px;color:white;padding:.35rem .7rem;text-decoration:none}.status{border-radius:999px;display:inline-flex;padding:.35rem .75rem;font-weight:800}.status.pass{background:#dcfce7;color:#166534}.status.fail{background:#fee2e2;color:#991b1b}table{border-collapse:collapse;width:100%;background:white;margin-top:1rem}td,th{border-bottom:1px solid #eee;padding:.75rem;text-align:left}.pass td{border-left:4px solid #16a34a}.fail td{border-left:4px solid #dc2626}code{background:#f3f4f6;padding:.15rem .3rem;border-radius:.3rem}</style></head><body><main>${links}<h1>Voice quality gates</h1><p class="status ${report.status}">${report.status}</p><p>${report.eventCount} event(s) checked.</p><table><thead><tr><th>Metric</th><th>Actual</th><th>Threshold</th><th>Status</th><th>Key</th></tr></thead><tbody>${rows}</tbody></table></main></body></html>`;
|
|
7974
8055
|
};
|
|
7975
8056
|
var createVoiceQualityRoutes = (options) => {
|
|
7976
8057
|
const path = options.path ?? "/quality";
|
|
7977
|
-
const routes = new
|
|
8058
|
+
const routes = new Elysia7({
|
|
7978
8059
|
name: options.name ?? "absolutejs-voice-quality"
|
|
7979
8060
|
});
|
|
7980
8061
|
const getReport = () => evaluateVoiceQuality({
|
|
@@ -8003,7 +8084,7 @@ var createVoiceQualityRoutes = (options) => {
|
|
|
8003
8084
|
};
|
|
8004
8085
|
|
|
8005
8086
|
// src/evalRoutes.ts
|
|
8006
|
-
var
|
|
8087
|
+
var escapeHtml10 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
8007
8088
|
var rate2 = (count, total) => count / Math.max(1, total);
|
|
8008
8089
|
var normalizeSearchText = (value) => value.trim().toLowerCase();
|
|
8009
8090
|
var getString6 = (value) => typeof value === "string" ? value : undefined;
|
|
@@ -8312,44 +8393,44 @@ var formatTime = (value) => value === undefined ? "unknown" : new Date(value).to
|
|
|
8312
8393
|
var formatPercent = (value) => `${(value * 100).toFixed(2)}%`;
|
|
8313
8394
|
var renderVoiceEvalHTML = (report, options = {}) => {
|
|
8314
8395
|
const title = options.title ?? "AbsoluteJS Voice Evals";
|
|
8315
|
-
const links = options.links?.length ? `<nav>${options.links.map((link) => `<a href="${
|
|
8316
|
-
const trend = report.trend.length ? report.trend.map((bucket) => `<tr><td>${
|
|
8396
|
+
const links = options.links?.length ? `<nav>${options.links.map((link) => `<a href="${escapeHtml10(link.href)}">${escapeHtml10(link.label)}</a>`).join("")}</nav>` : "";
|
|
8397
|
+
const trend = report.trend.length ? report.trend.map((bucket) => `<tr><td>${escapeHtml10(bucket.key)}</td><td>${bucket.total}</td><td>${bucket.passed}</td><td>${bucket.failed}</td></tr>`).join("") : '<tr><td colspan="4">No eval buckets yet.</td></tr>';
|
|
8317
8398
|
const sessions = report.sessions.length ? report.sessions.map((session) => {
|
|
8318
8399
|
const failedMetrics = Object.entries(session.quality.metrics).filter(([, metric]) => !metric.pass).map(([, metric]) => metric.label).join(", ");
|
|
8319
|
-
return `<tr class="${session.status}"><td>${
|
|
8400
|
+
return `<tr class="${session.status}"><td>${escapeHtml10(session.sessionId)}</td><td>${escapeHtml10(session.status)}</td><td>${session.eventCount}</td><td>${session.summary.turnCount}</td><td>${session.summary.errorCount}</td><td>${escapeHtml10(formatTime(session.endedAt))}</td><td>${escapeHtml10(failedMetrics || "none")}</td></tr>`;
|
|
8320
8401
|
}).join("") : '<tr><td colspan="7">No sessions found.</td></tr>';
|
|
8321
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
8402
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml10(title)}</title><style>body{font-family:ui-sans-serif,system-ui,sans-serif;margin:2rem;background:#f8f7f2;color:#181713}main{max-width:1180px;margin:auto}nav{display:flex;gap:.5rem;flex-wrap:wrap;margin-bottom:1rem}nav a{background:#181713;border-radius:999px;color:white;padding:.35rem .7rem;text-decoration:none}.status{border-radius:999px;display:inline-flex;font-weight:800;padding:.35rem .75rem}.pass{color:#166534}.fail{color:#991b1b}.status.pass{background:#dcfce7}.status.fail{background:#fee2e2}.grid{display:grid;gap:1rem;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));margin:1rem 0}.card{background:white;border:1px solid #e7e5e4;border-radius:1rem;padding:1rem}.card strong{display:block;font-size:2rem}table{border-collapse:collapse;background:white;width:100%;margin:1rem 0 2rem}td,th{border-bottom:1px solid #eee;padding:.75rem;text-align:left}tr.fail td{border-left:4px solid #dc2626}tr.pass td{border-left:4px solid #16a34a}</style></head><body><main>${links}<h1>${escapeHtml10(title)}</h1><p class="status ${report.status}">${report.status}</p><div class="grid"><article class="card"><span>Total</span><strong>${report.total}</strong></article><article class="card"><span>Passed</span><strong>${report.passed}</strong></article><article class="card"><span>Failed</span><strong>${report.failed}</strong></article></div><h2>Trend</h2><table><thead><tr><th>Day</th><th>Total</th><th>Passed</th><th>Failed</th></tr></thead><tbody>${trend}</tbody></table><h2>Session Eval Results</h2><table><thead><tr><th>Session</th><th>Status</th><th>Events</th><th>Turns</th><th>Errors</th><th>Last event</th><th>Failed metrics</th></tr></thead><tbody>${sessions}</tbody></table></main></body></html>`;
|
|
8322
8403
|
};
|
|
8323
8404
|
var renderVoiceEvalBaselineHTML = (comparison, options = {}) => {
|
|
8324
8405
|
const title = options.title ?? "AbsoluteJS Voice Eval Baseline";
|
|
8325
|
-
const links = options.links?.length ? `<nav>${options.links.map((link) => `<a href="${
|
|
8326
|
-
const reasons = comparison.reasons.length ? comparison.reasons.map((reason) => `<li>${
|
|
8327
|
-
const newFailures = comparison.newFailedSessionIds.length ? comparison.newFailedSessionIds.map((id) => `<li>${
|
|
8328
|
-
const recovered = comparison.recoveredSessionIds.length ? comparison.recoveredSessionIds.map((id) => `<li>${
|
|
8329
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
8406
|
+
const links = options.links?.length ? `<nav>${options.links.map((link) => `<a href="${escapeHtml10(link.href)}">${escapeHtml10(link.label)}</a>`).join("")}</nav>` : "";
|
|
8407
|
+
const reasons = comparison.reasons.length ? comparison.reasons.map((reason) => `<li>${escapeHtml10(reason)}</li>`).join("") : "<li>No baseline regressions detected.</li>";
|
|
8408
|
+
const newFailures = comparison.newFailedSessionIds.length ? comparison.newFailedSessionIds.map((id) => `<li>${escapeHtml10(id)}</li>`).join("") : "<li>none</li>";
|
|
8409
|
+
const recovered = comparison.recoveredSessionIds.length ? comparison.recoveredSessionIds.map((id) => `<li>${escapeHtml10(id)}</li>`).join("") : "<li>none</li>";
|
|
8410
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml10(title)}</title><style>body{font-family:ui-sans-serif,system-ui,sans-serif;margin:2rem;background:#f8f7f2;color:#181713}main{max-width:1000px;margin:auto}nav{display:flex;gap:.5rem;flex-wrap:wrap;margin-bottom:1rem}nav a{background:#181713;border-radius:999px;color:white;padding:.35rem .7rem;text-decoration:none}.status{border-radius:999px;display:inline-flex;font-weight:800;padding:.35rem .75rem}.pass{background:#dcfce7;color:#166534}.fail{background:#fee2e2;color:#991b1b}.grid{display:grid;gap:1rem;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));margin:1rem 0}.card{background:white;border:1px solid #e7e5e4;border-radius:1rem;padding:1rem}.card strong{display:block;font-size:2rem}section{background:white;border:1px solid #e7e5e4;border-radius:1rem;margin:1rem 0;padding:1rem}</style></head><body><main>${links}<h1>${escapeHtml10(title)}</h1><p class="status ${comparison.status}">${comparison.status}</p><div class="grid"><article class="card"><span>Baseline pass rate</span><strong>${escapeHtml10(formatPercent(comparison.baseline.passRate))}</strong></article><article class="card"><span>Current pass rate</span><strong>${escapeHtml10(formatPercent(comparison.current.passRate))}</strong></article><article class="card"><span>Failed delta</span><strong>${comparison.deltas.failed}</strong></article><article class="card"><span>Pass rate delta</span><strong>${escapeHtml10(formatPercent(comparison.deltas.passRate))}</strong></article></div><section><h2>Regression Reasons</h2><ul>${reasons}</ul></section><section><h2>New Failed Sessions</h2><ul>${newFailures}</ul></section><section><h2>Recovered Sessions</h2><ul>${recovered}</ul></section></main></body></html>`;
|
|
8330
8411
|
};
|
|
8331
8412
|
var renderVoiceScenarioEvalHTML = (report, options = {}) => {
|
|
8332
8413
|
const title = options.title ?? "AbsoluteJS Voice Scenario Evals";
|
|
8333
|
-
const links = options.links?.length ? `<nav>${options.links.map((link) => `<a href="${
|
|
8414
|
+
const links = options.links?.length ? `<nav>${options.links.map((link) => `<a href="${escapeHtml10(link.href)}">${escapeHtml10(link.label)}</a>`).join("")}</nav>` : "";
|
|
8334
8415
|
const scenarios = report.scenarios.length ? report.scenarios.map((scenario) => {
|
|
8335
|
-
const scenarioIssues = scenario.issues.length ? `<ul>${scenario.issues.map((issue) => `<li>${
|
|
8336
|
-
const sessions = scenario.sessions.length ? scenario.sessions.map((session) => `<tr class="${session.status}"><td>${
|
|
8337
|
-
return `<section class="scenario ${scenario.status}"><h2>${
|
|
8416
|
+
const scenarioIssues = scenario.issues.length ? `<ul>${scenario.issues.map((issue) => `<li>${escapeHtml10(issue)}</li>`).join("")}</ul>` : "";
|
|
8417
|
+
const sessions = scenario.sessions.length ? scenario.sessions.map((session) => `<tr class="${session.status}"><td>${escapeHtml10(session.sessionId)}</td><td>${escapeHtml10(session.status)}</td><td>${session.eventCount}</td><td>${escapeHtml10(session.issues.join(", ") || "none")}</td></tr>`).join("") : '<tr><td colspan="4">No matching sessions.</td></tr>';
|
|
8418
|
+
return `<section class="scenario ${scenario.status}"><h2>${escapeHtml10(scenario.label)}</h2>${scenario.description ? `<p>${escapeHtml10(scenario.description)}</p>` : ""}<p class="status ${scenario.status}">${scenario.status}</p><p>${scenario.passed} passed, ${scenario.failed} failed, ${scenario.matchedSessions} matched.</p>${scenarioIssues}<table><thead><tr><th>Session</th><th>Status</th><th>Events</th><th>Issues</th></tr></thead><tbody>${sessions}</tbody></table></section>`;
|
|
8338
8419
|
}).join("") : "<section><p>No scenarios configured.</p></section>";
|
|
8339
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
8420
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml10(title)}</title><style>body{font-family:ui-sans-serif,system-ui,sans-serif;margin:2rem;background:#f8f7f2;color:#181713}main{max-width:1180px;margin:auto}nav{display:flex;gap:.5rem;flex-wrap:wrap;margin-bottom:1rem}nav a{background:#181713;border-radius:999px;color:white;padding:.35rem .7rem;text-decoration:none}.status{border-radius:999px;display:inline-flex;font-weight:800;padding:.35rem .75rem}.status.pass{background:#dcfce7;color:#166534}.status.fail{background:#fee2e2;color:#991b1b}.grid{display:grid;gap:1rem;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));margin:1rem 0}.card,section{background:white;border:1px solid #e7e5e4;border-radius:1rem;padding:1rem}.card strong{display:block;font-size:2rem}section{margin:1rem 0}table{border-collapse:collapse;width:100%;margin-top:1rem}td,th{border-bottom:1px solid #eee;padding:.75rem;text-align:left}tr.fail td{border-left:4px solid #dc2626}tr.pass td{border-left:4px solid #16a34a}</style></head><body><main>${links}<h1>${escapeHtml10(title)}</h1><p class="status ${report.status}">${report.status}</p><div class="grid"><article class="card"><span>Total</span><strong>${report.total}</strong></article><article class="card"><span>Passed</span><strong>${report.passed}</strong></article><article class="card"><span>Failed</span><strong>${report.failed}</strong></article></div>${scenarios}</main></body></html>`;
|
|
8340
8421
|
};
|
|
8341
8422
|
var renderVoiceScenarioFixtureEvalHTML = (report, options = {}) => {
|
|
8342
8423
|
const title = options.title ?? "AbsoluteJS Voice Fixture Evals";
|
|
8343
|
-
const links = options.links?.length ? `<nav>${options.links.map((link) => `<a href="${
|
|
8424
|
+
const links = options.links?.length ? `<nav>${options.links.map((link) => `<a href="${escapeHtml10(link.href)}">${escapeHtml10(link.label)}</a>`).join("")}</nav>` : "";
|
|
8344
8425
|
const fixtures = report.fixtures.length ? report.fixtures.map((fixture) => {
|
|
8345
|
-
const scenarios = fixture.report.scenarios.map((scenario) => `<tr class="${scenario.status}"><td>${
|
|
8346
|
-
return `<section class="${fixture.status}"><h2>${
|
|
8426
|
+
const scenarios = fixture.report.scenarios.map((scenario) => `<tr class="${scenario.status}"><td>${escapeHtml10(scenario.label)}</td><td>${escapeHtml10(scenario.status)}</td><td>${scenario.matchedSessions}</td><td>${escapeHtml10([...scenario.issues, ...scenario.sessions.flatMap((session) => session.issues)].join(", ") || "none")}</td></tr>`).join("");
|
|
8427
|
+
return `<section class="${fixture.status}"><h2>${escapeHtml10(fixture.label)}</h2>${fixture.description ? `<p>${escapeHtml10(fixture.description)}</p>` : ""}<p class="status ${fixture.status}">${fixture.status}</p><table><thead><tr><th>Scenario</th><th>Status</th><th>Sessions</th><th>Issues</th></tr></thead><tbody>${scenarios}</tbody></table></section>`;
|
|
8347
8428
|
}).join("") : "<section><p>No scenario fixtures configured.</p></section>";
|
|
8348
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
8429
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml10(title)}</title><style>body{font-family:ui-sans-serif,system-ui,sans-serif;margin:2rem;background:#f8f7f2;color:#181713}main{max-width:1180px;margin:auto}nav{display:flex;gap:.5rem;flex-wrap:wrap;margin-bottom:1rem}nav a{background:#181713;border-radius:999px;color:white;padding:.35rem .7rem;text-decoration:none}.status{border-radius:999px;display:inline-flex;font-weight:800;padding:.35rem .75rem}.status.pass{background:#dcfce7;color:#166534}.status.fail{background:#fee2e2;color:#991b1b}.grid{display:grid;gap:1rem;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));margin:1rem 0}.card,section{background:white;border:1px solid #e7e5e4;border-radius:1rem;padding:1rem}.card strong{display:block;font-size:2rem}section{margin:1rem 0}table{border-collapse:collapse;width:100%;margin-top:1rem}td,th{border-bottom:1px solid #eee;padding:.75rem;text-align:left}tr.fail td{border-left:4px solid #dc2626}tr.pass td{border-left:4px solid #16a34a}</style></head><body><main>${links}<h1>${escapeHtml10(title)}</h1><p class="status ${report.status}">${report.status}</p><div class="grid"><article class="card"><span>Total</span><strong>${report.total}</strong></article><article class="card"><span>Passed</span><strong>${report.passed}</strong></article><article class="card"><span>Failed</span><strong>${report.failed}</strong></article></div>${fixtures}</main></body></html>`;
|
|
8349
8430
|
};
|
|
8350
8431
|
var createVoiceEvalRoutes = (options) => {
|
|
8351
8432
|
const path = options.path ?? "/evals";
|
|
8352
|
-
const routes = new
|
|
8433
|
+
const routes = new Elysia8({
|
|
8353
8434
|
name: options.name ?? "absolutejs-voice-evals"
|
|
8354
8435
|
});
|
|
8355
8436
|
const getReport = () => runVoiceSessionEvals({
|
|
@@ -8484,11 +8565,11 @@ var createVoiceEvalRoutes = (options) => {
|
|
|
8484
8565
|
};
|
|
8485
8566
|
|
|
8486
8567
|
// src/opsConsoleRoutes.ts
|
|
8487
|
-
import { Elysia as
|
|
8568
|
+
import { Elysia as Elysia11 } from "elysia";
|
|
8488
8569
|
|
|
8489
8570
|
// src/resilienceRoutes.ts
|
|
8490
|
-
import { Elysia as
|
|
8491
|
-
var
|
|
8571
|
+
import { Elysia as Elysia9 } from "elysia";
|
|
8572
|
+
var escapeHtml11 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
8492
8573
|
var getString7 = (value) => typeof value === "string" ? value : undefined;
|
|
8493
8574
|
var getNumber4 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
8494
8575
|
var getBoolean2 = (value) => value === true;
|
|
@@ -8635,13 +8716,13 @@ var summarizeRoutingEvents = (events) => {
|
|
|
8635
8716
|
};
|
|
8636
8717
|
var renderProviderCards = (title, providers) => {
|
|
8637
8718
|
if (providers.length === 0) {
|
|
8638
|
-
return `<p class="muted">No ${
|
|
8719
|
+
return `<p class="muted">No ${escapeHtml11(title)} provider health yet.</p>`;
|
|
8639
8720
|
}
|
|
8640
8721
|
return `<div class="provider-grid">${providers.map((provider) => `
|
|
8641
|
-
<article class="card provider ${
|
|
8722
|
+
<article class="card provider ${escapeHtml11(provider.status)}">
|
|
8642
8723
|
<div class="card-header">
|
|
8643
|
-
<strong>${
|
|
8644
|
-
<span>${
|
|
8724
|
+
<strong>${escapeHtml11(provider.provider)}</strong>
|
|
8725
|
+
<span>${escapeHtml11(provider.status)}${provider.recommended ? " \xB7 recommended" : ""}</span>
|
|
8645
8726
|
</div>
|
|
8646
8727
|
<dl>
|
|
8647
8728
|
<div><dt>Runs</dt><dd>${provider.runCount}</dd></div>
|
|
@@ -8650,7 +8731,7 @@ var renderProviderCards = (title, providers) => {
|
|
|
8650
8731
|
<div><dt>Timeouts</dt><dd>${provider.timeoutCount}</dd></div>
|
|
8651
8732
|
<div><dt>Fallbacks</dt><dd>${provider.fallbackCount}</dd></div>
|
|
8652
8733
|
</dl>
|
|
8653
|
-
${provider.lastError ? `<p class="muted">${
|
|
8734
|
+
${provider.lastError ? `<p class="muted">${escapeHtml11(provider.lastError)}</p>` : ""}
|
|
8654
8735
|
</article>
|
|
8655
8736
|
`).join("")}</div>`;
|
|
8656
8737
|
};
|
|
@@ -8659,24 +8740,24 @@ var renderTimeline2 = (events) => {
|
|
|
8659
8740
|
return '<p class="muted">No provider routing events yet. Run the app or simulate provider failover.</p>';
|
|
8660
8741
|
}
|
|
8661
8742
|
return `<div class="timeline">${events.slice(0, 40).map((event) => `
|
|
8662
|
-
<article class="card event ${
|
|
8743
|
+
<article class="card event ${escapeHtml11(event.status ?? "unknown")}">
|
|
8663
8744
|
<div class="card-header">
|
|
8664
|
-
<strong>${
|
|
8745
|
+
<strong>${escapeHtml11(event.kind.toUpperCase())} ${escapeHtml11(event.operation ?? "generate")}</strong>
|
|
8665
8746
|
<span>${new Date(event.at).toLocaleString()}</span>
|
|
8666
8747
|
</div>
|
|
8667
8748
|
<p>
|
|
8668
|
-
<span class="pill">${
|
|
8669
|
-
<span class="pill">provider: ${
|
|
8670
|
-
${event.fallbackProvider ? `<span class="pill">fallback: ${
|
|
8749
|
+
<span class="pill">${escapeHtml11(event.status ?? "unknown")}</span>
|
|
8750
|
+
<span class="pill">provider: ${escapeHtml11(event.provider ?? "unknown")}</span>
|
|
8751
|
+
${event.fallbackProvider ? `<span class="pill">fallback: ${escapeHtml11(event.fallbackProvider)}</span>` : ""}
|
|
8671
8752
|
${event.timedOut ? '<span class="pill danger">timed out</span>' : ""}
|
|
8672
8753
|
</p>
|
|
8673
8754
|
<dl>
|
|
8674
8755
|
<div><dt>Attempt</dt><dd>${event.attempt ?? 0}</dd></div>
|
|
8675
8756
|
<div><dt>Elapsed</dt><dd>${event.elapsedMs ?? 0}ms</dd></div>
|
|
8676
8757
|
<div><dt>Budget</dt><dd>${event.latencyBudgetMs ?? 0}ms</dd></div>
|
|
8677
|
-
<div><dt>Session</dt><dd>${
|
|
8758
|
+
<div><dt>Session</dt><dd>${escapeHtml11(event.sessionId)}</dd></div>
|
|
8678
8759
|
</dl>
|
|
8679
|
-
${event.error ? `<p class="muted">${
|
|
8760
|
+
${event.error ? `<p class="muted">${escapeHtml11(event.error)}</p>` : ""}
|
|
8680
8761
|
</article>
|
|
8681
8762
|
`).join("")}</div>`;
|
|
8682
8763
|
};
|
|
@@ -8686,9 +8767,9 @@ var renderSessionKind = (kind, summary) => {
|
|
|
8686
8767
|
const status = latest?.status ?? "idle";
|
|
8687
8768
|
const fallback = latest?.fallbackProvider && latest.fallbackProvider !== provider ? ` -> ${latest.fallbackProvider}` : "";
|
|
8688
8769
|
return `<div>
|
|
8689
|
-
<dt>${
|
|
8690
|
-
<dd>${
|
|
8691
|
-
<small>${
|
|
8770
|
+
<dt>${escapeHtml11(kind.toUpperCase())}</dt>
|
|
8771
|
+
<dd>${escapeHtml11(provider)}${escapeHtml11(fallback)}</dd>
|
|
8772
|
+
<small>${escapeHtml11(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
8773
|
</div>`;
|
|
8693
8774
|
};
|
|
8694
8775
|
var renderSessionSummaries = (sessions) => {
|
|
@@ -8696,10 +8777,10 @@ var renderSessionSummaries = (sessions) => {
|
|
|
8696
8777
|
return '<p class="muted">No call-level routing summaries yet. Run a voice session or provider simulation.</p>';
|
|
8697
8778
|
}
|
|
8698
8779
|
return `<div class="session-grid">${sessions.slice(0, 12).map((session) => `
|
|
8699
|
-
<article class="card session ${
|
|
8780
|
+
<article class="card session ${escapeHtml11(session.status)}">
|
|
8700
8781
|
<div class="card-header">
|
|
8701
|
-
<strong>${
|
|
8702
|
-
<span>${
|
|
8782
|
+
<strong>${escapeHtml11(session.sessionId)}</strong>
|
|
8783
|
+
<span>${escapeHtml11(session.status)}</span>
|
|
8703
8784
|
</div>
|
|
8704
8785
|
<p>
|
|
8705
8786
|
<span class="pill">${session.eventCount} routing events</span>
|
|
@@ -8726,26 +8807,26 @@ var renderSimulationControls = (kind, simulation) => {
|
|
|
8726
8807
|
const pathPrefix = simulation.pathPrefix ?? `/api/${kind}-simulate`;
|
|
8727
8808
|
const failureProviders = simulation.failureProviders ?? configuredProviders.map(({ provider }) => provider);
|
|
8728
8809
|
const canFail = (provider) => configuredProviders.some((entry) => entry.provider === provider) && (!simulation.fallbackRequiredProvider || configuredProviders.some((entry) => entry.provider === simulation.fallbackRequiredProvider));
|
|
8729
|
-
return `<div class="simulate-panel" data-sim-kind="${kind}" data-sim-prefix="${
|
|
8730
|
-
<p class="muted">${
|
|
8810
|
+
return `<div class="simulate-panel" data-sim-kind="${kind}" data-sim-prefix="${escapeHtml11(pathPrefix)}">
|
|
8811
|
+
<p class="muted">${escapeHtml11(simulation.failureMessage ?? `Simulate ${kind.toUpperCase()} provider failure without changing provider credentials.`)}</p>
|
|
8731
8812
|
<div class="simulate-actions">
|
|
8732
|
-
${failureProviders.map((provider) => `<button type="button" data-provider-fail="${
|
|
8733
|
-
${configuredProviders.map((provider) => `<button type="button" data-provider-recover="${
|
|
8813
|
+
${failureProviders.map((provider) => `<button type="button" data-provider-fail="${escapeHtml11(provider)}"${canFail(provider) ? "" : " disabled"}>Simulate ${escapeHtml11(provider)} ${kind.toUpperCase()} failure</button>`).join("")}
|
|
8814
|
+
${configuredProviders.map((provider) => `<button type="button" data-provider-recover="${escapeHtml11(provider.provider)}">Mark ${escapeHtml11(provider.provider)} recovered</button>`).join("")}
|
|
8734
8815
|
</div>
|
|
8735
|
-
${simulation.fallbackRequiredProvider && !configuredProviders.some((entry) => entry.provider === simulation.fallbackRequiredProvider) ? `<p class="muted">${
|
|
8816
|
+
${simulation.fallbackRequiredProvider && !configuredProviders.some((entry) => entry.provider === simulation.fallbackRequiredProvider) ? `<p class="muted">${escapeHtml11(simulation.fallbackRequiredMessage ?? `Configure ${simulation.fallbackRequiredProvider} to enable fallback simulation.`)}</p>` : ""}
|
|
8736
8817
|
<pre class="simulate-output" hidden></pre>
|
|
8737
8818
|
</div>`;
|
|
8738
8819
|
};
|
|
8739
8820
|
var renderVoiceResilienceHTML = (input) => {
|
|
8740
8821
|
const summary = summarizeRoutingEvents(input.routingEvents);
|
|
8741
|
-
const kindCounts = [...summary.byKind.entries()].map(([kind, count]) => `<span class="pill">${
|
|
8742
|
-
const links = input.links?.length ? input.links.map((link) => `<a href="${
|
|
8822
|
+
const kindCounts = [...summary.byKind.entries()].map(([kind, count]) => `<span class="pill">${escapeHtml11(kind)}: ${String(count)}</span>`).join("");
|
|
8823
|
+
const links = input.links?.length ? input.links.map((link) => `<a href="${escapeHtml11(link.href)}">${escapeHtml11(link.label)}</a>`).join(" \xB7 ") : "";
|
|
8743
8824
|
return `<!doctype html>
|
|
8744
8825
|
<html lang="en">
|
|
8745
8826
|
<head>
|
|
8746
8827
|
<meta charset="utf-8" />
|
|
8747
8828
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
8748
|
-
<title>${
|
|
8829
|
+
<title>${escapeHtml11(input.title ?? "AbsoluteJS Voice Resilience")}</title>
|
|
8749
8830
|
<style>
|
|
8750
8831
|
:root { color-scheme: dark; }
|
|
8751
8832
|
body { background: radial-gradient(circle at top left, #172554, #09090b 36%, #050505); color: #f4f4f5; font-family: ui-sans-serif, system-ui, sans-serif; margin: 0; padding: 24px; }
|
|
@@ -8888,7 +8969,7 @@ var registerSimulationRoutes = (routes, simulation, defaultPathPrefix) => {
|
|
|
8888
8969
|
};
|
|
8889
8970
|
var createVoiceResilienceRoutes = (options) => {
|
|
8890
8971
|
const path = options.path ?? "/resilience";
|
|
8891
|
-
const routes = new
|
|
8972
|
+
const routes = new Elysia9({
|
|
8892
8973
|
name: options.name ?? "absolutejs-voice-resilience"
|
|
8893
8974
|
}).get(path, async () => {
|
|
8894
8975
|
const events = await options.store.list();
|
|
@@ -8929,9 +9010,9 @@ var createVoiceResilienceRoutes = (options) => {
|
|
|
8929
9010
|
};
|
|
8930
9011
|
|
|
8931
9012
|
// src/sessionReplay.ts
|
|
8932
|
-
import { Elysia as
|
|
9013
|
+
import { Elysia as Elysia10 } from "elysia";
|
|
8933
9014
|
var getString8 = (value) => typeof value === "string" ? value : undefined;
|
|
8934
|
-
var
|
|
9015
|
+
var escapeHtml12 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
8935
9016
|
var increment3 = (record, key) => {
|
|
8936
9017
|
record[key] = (record[key] ?? 0) + 1;
|
|
8937
9018
|
};
|
|
@@ -9090,10 +9171,10 @@ var summarizeVoiceSessions = async (options = {}) => {
|
|
|
9090
9171
|
var renderVoiceSessionsHTML = (sessions) => sessions.length === 0 ? '<p class="voice-sessions-empty">No voice sessions found.</p>' : [
|
|
9091
9172
|
'<div class="voice-sessions-list">',
|
|
9092
9173
|
...sessions.map((session) => [
|
|
9093
|
-
`<article class="voice-session-card ${
|
|
9174
|
+
`<article class="voice-session-card ${escapeHtml12(session.status)}">`,
|
|
9094
9175
|
'<div class="voice-session-card-header">',
|
|
9095
|
-
`<strong>${
|
|
9096
|
-
`<span>${
|
|
9176
|
+
`<strong>${escapeHtml12(session.sessionId)}</strong>`,
|
|
9177
|
+
`<span>${escapeHtml12(session.status)}</span>`,
|
|
9097
9178
|
"</div>",
|
|
9098
9179
|
"<dl>",
|
|
9099
9180
|
`<div><dt>Events</dt><dd>${String(session.eventCount)}</dd></div>`,
|
|
@@ -9101,9 +9182,9 @@ var renderVoiceSessionsHTML = (sessions) => sessions.length === 0 ? '<p class="v
|
|
|
9101
9182
|
`<div><dt>Transcripts</dt><dd>${String(session.transcriptCount)}</dd></div>`,
|
|
9102
9183
|
`<div><dt>Errors</dt><dd>${String(session.errorCount)}</dd></div>`,
|
|
9103
9184
|
"</dl>",
|
|
9104
|
-
session.latestOutcome ? `<p>Outcome: ${
|
|
9105
|
-
session.providers.length ? `<p>Providers: ${session.providers.map(
|
|
9106
|
-
session.replayHref ? `<p><a href="${
|
|
9185
|
+
session.latestOutcome ? `<p>Outcome: ${escapeHtml12(session.latestOutcome)}</p>` : "",
|
|
9186
|
+
session.providers.length ? `<p>Providers: ${session.providers.map(escapeHtml12).join(", ")}</p>` : "",
|
|
9187
|
+
session.replayHref ? `<p><a href="${escapeHtml12(session.replayHref)}">Open replay</a></p>` : "",
|
|
9107
9188
|
"</article>"
|
|
9108
9189
|
].join("")),
|
|
9109
9190
|
"</div>"
|
|
@@ -9134,7 +9215,7 @@ var createVoiceSessionsHTMLHandler = (options = {}) => async ({ query }) => {
|
|
|
9134
9215
|
var createVoiceSessionListRoutes = (options = {}) => {
|
|
9135
9216
|
const path = options.path ?? "/api/voice-sessions";
|
|
9136
9217
|
const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
|
|
9137
|
-
const routes = new
|
|
9218
|
+
const routes = new Elysia10({
|
|
9138
9219
|
name: options.name ?? "absolutejs-voice-session-list"
|
|
9139
9220
|
}).get(path, createVoiceSessionsJSONHandler(options));
|
|
9140
9221
|
if (htmlPath) {
|
|
@@ -9162,7 +9243,7 @@ var createVoiceSessionReplayHTMLHandler = (options) => async ({ params }) => {
|
|
|
9162
9243
|
var createVoiceSessionReplayRoutes = (options) => {
|
|
9163
9244
|
const path = options.path ?? "/api/voice-sessions/:sessionId/replay";
|
|
9164
9245
|
const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
|
|
9165
|
-
const routes = new
|
|
9246
|
+
const routes = new Elysia10({
|
|
9166
9247
|
name: options.name ?? "absolutejs-voice-session-replay"
|
|
9167
9248
|
}).get(path, createVoiceSessionReplayJSONHandler(options));
|
|
9168
9249
|
if (htmlPath) {
|
|
@@ -9206,7 +9287,7 @@ var DEFAULT_LINKS = [
|
|
|
9206
9287
|
label: "Handoffs"
|
|
9207
9288
|
}
|
|
9208
9289
|
];
|
|
9209
|
-
var
|
|
9290
|
+
var escapeHtml13 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
9210
9291
|
var countProviderStatuses = (providers) => {
|
|
9211
9292
|
const degradedStatuses = new Set(["degraded", "rate-limited", "suppressed"]);
|
|
9212
9293
|
const healthy = providers.filter((provider) => provider.status === "healthy").length;
|
|
@@ -9262,20 +9343,20 @@ var buildVoiceOpsConsoleReport = async (options) => {
|
|
|
9262
9343
|
trace
|
|
9263
9344
|
};
|
|
9264
9345
|
};
|
|
9265
|
-
var renderMetricCard = (input) => `<article class="metric"><span>${
|
|
9346
|
+
var renderMetricCard = (input) => `<article class="metric"><span>${escapeHtml13(input.label)}</span><strong>${escapeHtml13(String(input.value))}</strong>${input.status ? `<p class="${escapeHtml13(input.status)}">${escapeHtml13(input.status)}</p>` : ""}${input.href ? `<a href="${escapeHtml13(input.href)}">Open</a>` : ""}</article>`;
|
|
9266
9347
|
var renderVoiceOpsConsoleHTML = (report, options = {}) => {
|
|
9267
9348
|
const links = report.links.map((link) => `<article class="surface">
|
|
9268
|
-
<div><h2>${
|
|
9269
|
-
<p><a href="${
|
|
9349
|
+
<div><h2>${escapeHtml13(link.label)}</h2>${link.description ? `<p>${escapeHtml13(link.description)}</p>` : ""}</div>
|
|
9350
|
+
<p><a href="${escapeHtml13(link.href)}">Open ${escapeHtml13(link.label)}</a>${link.statusHref ? ` \xB7 <a href="${escapeHtml13(link.statusHref)}">Status</a>` : ""}</p>
|
|
9270
9351
|
</article>`).join("");
|
|
9271
|
-
const sessions = report.recentSessions.length ? report.recentSessions.map((session) => `<tr><td>${
|
|
9272
|
-
const routing = report.recentRoutingEvents.length ? report.recentRoutingEvents.map((event) => `<tr><td>${
|
|
9352
|
+
const sessions = report.recentSessions.length ? report.recentSessions.map((session) => `<tr><td>${escapeHtml13(session.sessionId)}</td><td>${escapeHtml13(session.status)}</td><td>${session.turnCount}</td><td>${session.errorCount}</td><td>${session.replayHref ? `<a href="${escapeHtml13(session.replayHref)}">Replay</a>` : ""}</td></tr>`).join("") : '<tr><td colspan="5">No sessions yet.</td></tr>';
|
|
9353
|
+
const routing = report.recentRoutingEvents.length ? report.recentRoutingEvents.map((event) => `<tr><td>${escapeHtml13(event.kind)}</td><td>${escapeHtml13(event.provider ?? "unknown")}</td><td>${escapeHtml13(event.status ?? "unknown")}</td><td>${event.elapsedMs ?? 0}ms</td><td>${escapeHtml13(event.sessionId)}</td></tr>`).join("") : '<tr><td colspan="5">No provider routing events yet.</td></tr>';
|
|
9273
9354
|
const title = options.title ?? "AbsoluteJS Voice Ops Console";
|
|
9274
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
9355
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml13(title)}</title><style>body{font-family:ui-sans-serif,system-ui,sans-serif;background:#101316;color:#f6f2e8;margin:0}main{max-width:1180px;margin:auto;padding:32px}a{color:#fbbf24}header{display:flex;justify-content:space-between;gap:24px;align-items:flex-start;margin-bottom:24px}.eyebrow{color:#fbbf24;font-weight:800;letter-spacing:.08em;text-transform:uppercase}h1{font-size:clamp(2.2rem,5vw,4.5rem);line-height:.95;margin:.2rem 0 1rem}.muted{color:#a8b0b8}.grid{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));margin:20px 0}.metric,.surface{background:#181d22;border:1px solid #2a323a;border-radius:20px;padding:18px}.metric strong{display:block;font-size:2.2rem;margin:.25rem 0}.pass,.healthy{color:#86efac}.fail,.failed,.degraded{color:#fca5a5}.surfaces{display:grid;gap:16px;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));margin:24px 0}table{width:100%;border-collapse:collapse;background:#181d22;border-radius:16px;overflow:hidden;margin:12px 0 28px}td,th{border-bottom:1px solid #2a323a;padding:12px;text-align:left}section{margin-top:30px}@media(max-width:700px){main{padding:20px}header{display:block}}</style></head><body><main><header><div><p class="eyebrow">Self-hosted voice operations</p><h1>${escapeHtml13(title)}</h1><p class="muted">One deployable control plane for quality gates, failover, traces, sessions, handoffs, and provider health.</p></div><p class="muted">Checked ${escapeHtml13(new Date(report.checkedAt).toLocaleString())}</p></header><div class="grid">${renderMetricCard({ label: "Quality", value: report.quality.status, status: report.quality.status, href: "/quality" })}${renderMetricCard({ label: "Events", value: report.eventCount, href: "/diagnostics" })}${renderMetricCard({ label: "Sessions", value: report.sessions.total, status: report.sessions.failed > 0 ? "failed" : "healthy", href: "/sessions" })}${renderMetricCard({ label: "Handoffs failed", value: report.handoffs.failed, status: report.handoffs.failed > 0 ? "failed" : "healthy", href: "/handoffs" })}${renderMetricCard({ label: "Providers degraded", value: report.providers.degraded, status: report.providers.degraded > 0 ? "degraded" : "healthy", href: "/resilience" })}</div><section><h2>Operational Surfaces</h2><div class="surfaces">${links}</div></section><section><h2>Recent Sessions</h2><table><thead><tr><th>Session</th><th>Status</th><th>Turns</th><th>Errors</th><th>Replay</th></tr></thead><tbody>${sessions}</tbody></table></section><section><h2>Recent Provider Routing</h2><table><thead><tr><th>Kind</th><th>Provider</th><th>Status</th><th>Elapsed</th><th>Session</th></tr></thead><tbody>${routing}</tbody></table></section></main></body></html>`;
|
|
9275
9356
|
};
|
|
9276
9357
|
var createVoiceOpsConsoleRoutes = (options) => {
|
|
9277
9358
|
const path = options.path ?? "/ops-console";
|
|
9278
|
-
const routes = new
|
|
9359
|
+
const routes = new Elysia11({
|
|
9279
9360
|
name: options.name ?? "absolutejs-voice-ops-console"
|
|
9280
9361
|
});
|
|
9281
9362
|
const getReport = () => buildVoiceOpsConsoleReport(options);
|
|
@@ -9293,8 +9374,8 @@ var createVoiceOpsConsoleRoutes = (options) => {
|
|
|
9293
9374
|
};
|
|
9294
9375
|
|
|
9295
9376
|
// src/providerCapabilities.ts
|
|
9296
|
-
import { Elysia as
|
|
9297
|
-
var
|
|
9377
|
+
import { Elysia as Elysia12 } from "elysia";
|
|
9378
|
+
var escapeHtml14 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
9298
9379
|
var fromProviderList = (kind, providers, options) => (providers ?? []).map((provider) => ({
|
|
9299
9380
|
configured: true,
|
|
9300
9381
|
features: options.features?.[provider],
|
|
@@ -9357,27 +9438,27 @@ var summarizeVoiceProviderCapabilities = async (options) => {
|
|
|
9357
9438
|
var renderVoiceProviderCapabilityHTML = (report, options = {}) => {
|
|
9358
9439
|
const title = options.title ?? "Voice Provider Capabilities";
|
|
9359
9440
|
const cards = report.capabilities.map((capability) => {
|
|
9360
|
-
const features = (capability.features ?? []).map((feature) => `<span class="pill">${
|
|
9361
|
-
return `<article class="card ${
|
|
9441
|
+
const features = (capability.features ?? []).map((feature) => `<span class="pill">${escapeHtml14(feature)}</span>`).join("");
|
|
9442
|
+
return `<article class="card ${escapeHtml14(capability.status)}">
|
|
9362
9443
|
<div class="card-header">
|
|
9363
9444
|
<div>
|
|
9364
|
-
<p class="eyebrow">${
|
|
9365
|
-
<h2>${
|
|
9445
|
+
<p class="eyebrow">${escapeHtml14(capability.kind)}</p>
|
|
9446
|
+
<h2>${escapeHtml14(capability.label ?? capability.provider)}</h2>
|
|
9366
9447
|
</div>
|
|
9367
|
-
<strong>${
|
|
9448
|
+
<strong>${escapeHtml14(capability.status)}</strong>
|
|
9368
9449
|
</div>
|
|
9369
|
-
${capability.description ? `<p>${
|
|
9450
|
+
${capability.description ? `<p>${escapeHtml14(capability.description)}</p>` : ""}
|
|
9370
9451
|
<dl>
|
|
9371
9452
|
<div><dt>Configured</dt><dd>${capability.configured ? "yes" : "no"}</dd></div>
|
|
9372
9453
|
<div><dt>Selected</dt><dd>${capability.selected ? "yes" : "no"}</dd></div>
|
|
9373
|
-
<div><dt>Model</dt><dd>${
|
|
9454
|
+
<div><dt>Model</dt><dd>${escapeHtml14(capability.model ?? "default")}</dd></div>
|
|
9374
9455
|
<div><dt>Runs</dt><dd>${String(capability.health?.runCount ?? 0)}</dd></div>
|
|
9375
9456
|
<div><dt>Errors</dt><dd>${String(capability.health?.errorCount ?? 0)}</dd></div>
|
|
9376
9457
|
</dl>
|
|
9377
9458
|
${features ? `<div class="features">${features}</div>` : ""}
|
|
9378
9459
|
</article>`;
|
|
9379
9460
|
}).join("");
|
|
9380
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
9461
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml14(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,.card{background:#181d22;border:1px solid #2a323a;border-radius:20px;margin-bottom:16px;padding:20px}.hero{background:linear-gradient(135deg,rgba(14,165,233,.16),rgba(34,197,94,.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 1rem}.summary,.features{display:flex;flex-wrap:wrap;gap:10px}.pill{background:#0f1217;border:1px solid #3f3f46;border-radius:999px;padding:7px 10px}.grid{display:grid;gap:16px;grid-template-columns:repeat(auto-fit,minmax(260px,1fr))}.card-header{align-items:flex-start;display:flex;gap:16px;justify-content:space-between}.selected,.healthy{color:#86efac}.unconfigured,.degraded,.rate-limited,.suppressed{color:#fca5a5}.idle,.recoverable{color:#fde68a}dl{display:grid;gap:8px;grid-template-columns:repeat(2,minmax(0,1fr))}dt{color:#a8b0b8;font-size:.8rem}dd{margin:0}@media(max-width:800px){main{padding:18px}.card-header{display:block}}</style></head><body><main><section class="hero"><p class="eyebrow">Provider Discovery</p><h1>${escapeHtml14(title)}</h1><div class="summary"><span class="pill">${String(report.configured)} configured</span><span class="pill">${String(report.selected)} selected</span><span class="pill">${String(report.unconfigured)} missing</span><span class="pill">${String(report.total)} total</span></div></section><section class="grid">${cards || '<article class="card"><p>No provider capabilities configured.</p></article>'}</section></main></body></html>`;
|
|
9381
9462
|
};
|
|
9382
9463
|
var createVoiceProviderCapabilityJSONHandler = (options) => async () => summarizeVoiceProviderCapabilities(options);
|
|
9383
9464
|
var createVoiceProviderCapabilityHTMLHandler = (options) => async () => {
|
|
@@ -9394,7 +9475,7 @@ var createVoiceProviderCapabilityHTMLHandler = (options) => async () => {
|
|
|
9394
9475
|
var createVoiceProviderCapabilityRoutes = (options) => {
|
|
9395
9476
|
const path = options.path ?? "/api/provider-capabilities";
|
|
9396
9477
|
const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
|
|
9397
|
-
const routes = new
|
|
9478
|
+
const routes = new Elysia12({
|
|
9398
9479
|
name: options.name ?? "absolutejs-voice-provider-capabilities"
|
|
9399
9480
|
}).get(path, createVoiceProviderCapabilityJSONHandler(options));
|
|
9400
9481
|
if (htmlPath) {
|
|
@@ -9404,10 +9485,10 @@ var createVoiceProviderCapabilityRoutes = (options) => {
|
|
|
9404
9485
|
};
|
|
9405
9486
|
|
|
9406
9487
|
// src/productionReadiness.ts
|
|
9407
|
-
import { Elysia as
|
|
9488
|
+
import { Elysia as Elysia14 } from "elysia";
|
|
9408
9489
|
|
|
9409
9490
|
// src/telephony/matrix.ts
|
|
9410
|
-
import { Elysia as
|
|
9491
|
+
import { Elysia as Elysia13 } from "elysia";
|
|
9411
9492
|
|
|
9412
9493
|
// src/telephony/contract.ts
|
|
9413
9494
|
var DEFAULT_REQUIREMENTS = [
|
|
@@ -9490,7 +9571,7 @@ var evaluateVoiceTelephonyContract = (input) => {
|
|
|
9490
9571
|
};
|
|
9491
9572
|
|
|
9492
9573
|
// src/telephony/matrix.ts
|
|
9493
|
-
var
|
|
9574
|
+
var escapeHtml15 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
9494
9575
|
var labelForProvider = (provider) => provider.split("-").map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`).join(" ");
|
|
9495
9576
|
var resolveEntryStatus = (contract, setup, smoke) => {
|
|
9496
9577
|
if (!contract.pass || !setup.ready || smoke?.pass === false) {
|
|
@@ -9551,13 +9632,13 @@ var badgeStyles = {
|
|
|
9551
9632
|
};
|
|
9552
9633
|
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
9634
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Carrier matrix</p>
|
|
9554
|
-
<h1 style="font-size: 34px; margin: 0 0 8px;">${
|
|
9635
|
+
<h1 style="font-size: 34px; margin: 0 0 8px;">${escapeHtml15(options.title ?? "AbsoluteJS Voice Carrier Matrix")}</h1>
|
|
9555
9636
|
<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
9637
|
<section style="display:grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 16px;">
|
|
9557
9638
|
${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
9639
|
<div style="display:flex; justify-content:space-between; gap:12px; align-items:center;">
|
|
9559
|
-
<h2 style="margin:0; font-size:20px;">${
|
|
9560
|
-
<span style="border:1px solid; border-radius:999px; padding:4px 10px; font-size:12px; font-weight:700; ${badgeStyles[entry.status]}">${
|
|
9640
|
+
<h2 style="margin:0; font-size:20px;">${escapeHtml15(entry.name)}</h2>
|
|
9641
|
+
<span style="border:1px solid; border-radius:999px; padding:4px 10px; font-size:12px; font-weight:700; ${badgeStyles[entry.status]}">${escapeHtml15(entry.status.toUpperCase())}</span>
|
|
9561
9642
|
</div>
|
|
9562
9643
|
<dl style="display:grid; grid-template-columns: 1fr 1fr; gap:8px 12px; margin:16px 0;">
|
|
9563
9644
|
<dt style="color:#64748b;">Setup</dt><dd style="margin:0; font-weight:700;">${entry.ready ? "Ready" : "Needs attention"}</dd>
|
|
@@ -9565,15 +9646,15 @@ ${matrix.entries.map((entry) => `<article style="border:1px solid #d9e2ec; borde
|
|
|
9565
9646
|
<dt style="color:#64748b;">Smoke</dt><dd style="margin:0; font-weight:700;">${entry.smoke ? entry.smoke.pass ? "Pass" : "Fail" : "Missing"}</dd>
|
|
9566
9647
|
<dt style="color:#64748b;">Contract</dt><dd style="margin:0; font-weight:700;">${entry.contract.pass ? "Pass" : "Fail"}</dd>
|
|
9567
9648
|
</dl>
|
|
9568
|
-
<p style="margin:0 0 8px; color:#475569;"><strong>Stream:</strong> <code>${
|
|
9569
|
-
<p style="margin:0 0 12px; color:#475569;"><strong>Webhook:</strong> <code>${
|
|
9570
|
-
${entry.issues.length ? `<ul style="margin:12px 0 0; padding-left:18px;">${entry.issues.map((issue) => `<li>${
|
|
9649
|
+
<p style="margin:0 0 8px; color:#475569;"><strong>Stream:</strong> <code>${escapeHtml15(entry.setup.urls.stream || "missing")}</code></p>
|
|
9650
|
+
<p style="margin:0 0 12px; color:#475569;"><strong>Webhook:</strong> <code>${escapeHtml15(entry.setup.urls.webhook || "missing")}</code></p>
|
|
9651
|
+
${entry.issues.length ? `<ul style="margin:12px 0 0; padding-left:18px;">${entry.issues.map((issue) => `<li>${escapeHtml15(issue.severity)}: ${escapeHtml15(issue.message)}</li>`).join("")}</ul>` : '<p style="margin:12px 0 0; color:#166534;">No contract issues.</p>'}
|
|
9571
9652
|
</article>`).join("")}
|
|
9572
9653
|
</section>
|
|
9573
9654
|
</main>`;
|
|
9574
9655
|
var createVoiceTelephonyCarrierMatrixRoutes = (options) => {
|
|
9575
9656
|
const path = options.path ?? "/api/voice/telephony/carriers";
|
|
9576
|
-
return new
|
|
9657
|
+
return new Elysia13({
|
|
9577
9658
|
name: options.name ?? "absolutejs-voice-telephony-carrier-matrix"
|
|
9578
9659
|
}).get(path, async ({ query, request }) => {
|
|
9579
9660
|
const providers = await options.load({ query, request });
|
|
@@ -9595,7 +9676,7 @@ var createVoiceTelephonyCarrierMatrixRoutes = (options) => {
|
|
|
9595
9676
|
};
|
|
9596
9677
|
|
|
9597
9678
|
// src/productionReadiness.ts
|
|
9598
|
-
var
|
|
9679
|
+
var escapeHtml16 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
9599
9680
|
var rollupStatus = (checks) => checks.some((check) => check.status === "fail") ? "fail" : checks.some((check) => check.status === "warn") ? "warn" : "pass";
|
|
9600
9681
|
var carrierStatus = (matrix) => matrix.summary.failing > 0 ? "fail" : matrix.summary.warnings > 0 || matrix.summary.ready < matrix.summary.providers ? "warn" : "pass";
|
|
9601
9682
|
var resolveCarriers = async (options, input) => {
|
|
@@ -9782,24 +9863,24 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
|
|
|
9782
9863
|
var renderVoiceProductionReadinessHTML = (report, options = {}) => {
|
|
9783
9864
|
const title = options.title ?? "AbsoluteJS Voice Production Readiness";
|
|
9784
9865
|
const checks = report.checks.map((check, index) => {
|
|
9785
|
-
const actions = (check.actions ?? []).map((action) => action.method === "POST" ? `<button type="button" data-readiness-action="${index}" data-action-url="${
|
|
9786
|
-
return `<article class="check ${
|
|
9866
|
+
const actions = (check.actions ?? []).map((action) => action.method === "POST" ? `<button type="button" data-readiness-action="${index}" data-action-url="${escapeHtml16(action.href)}">${escapeHtml16(action.label)}</button>` : `<a href="${escapeHtml16(action.href)}">${escapeHtml16(action.label)}</a>`).join("");
|
|
9867
|
+
return `<article class="check ${escapeHtml16(check.status)}">
|
|
9787
9868
|
<div>
|
|
9788
|
-
<span>${
|
|
9789
|
-
<h2>${
|
|
9790
|
-
${check.detail ? `<p>${
|
|
9869
|
+
<span>${escapeHtml16(check.status.toUpperCase())}</span>
|
|
9870
|
+
<h2>${escapeHtml16(check.label)}</h2>
|
|
9871
|
+
${check.detail ? `<p>${escapeHtml16(check.detail)}</p>` : ""}
|
|
9791
9872
|
${actions ? `<p class="actions">${actions}</p>` : ""}
|
|
9792
9873
|
</div>
|
|
9793
|
-
<strong>${
|
|
9794
|
-
${check.href ? `<a href="${
|
|
9874
|
+
<strong>${escapeHtml16(String(check.value ?? check.status))}</strong>
|
|
9875
|
+
${check.href ? `<a href="${escapeHtml16(check.href)}">Open surface</a>` : ""}
|
|
9795
9876
|
</article>`;
|
|
9796
9877
|
}).join("");
|
|
9797
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
9878
|
+
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:#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}.actions{display:flex;flex-wrap:wrap;gap:10px}.check a,a{color:#fbbf24}button{background:#fbbf24;border:0;border-radius:999px;color:#111827;cursor:pointer;font-weight:800;padding:9px 12px}button:disabled{cursor:wait;opacity:.65}@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>${escapeHtml16(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 ${escapeHtml16(report.status)}">Overall: ${escapeHtml16(report.status.toUpperCase())}</p><p>Checked ${escapeHtml16(new Date(report.checkedAt).toLocaleString())}</p></section><section class="checks">${checks}</section></main><script>document.querySelectorAll("[data-readiness-action]").forEach((button)=>{button.addEventListener("click",async()=>{const url=button.getAttribute("data-action-url");if(!url)return;button.disabled=true;const original=button.textContent;button.textContent="Running...";try{const response=await fetch(url,{method:"POST"});button.textContent=response.ok?"Done. Reloading...":"Failed";if(response.ok)setTimeout(()=>location.reload(),500)}catch{button.textContent="Failed"}finally{setTimeout(()=>{button.disabled=false;button.textContent=original},1500)}})});</script></body></html>`;
|
|
9798
9879
|
};
|
|
9799
9880
|
var createVoiceProductionReadinessRoutes = (options) => {
|
|
9800
9881
|
const path = options.path ?? "/api/production-readiness";
|
|
9801
9882
|
const htmlPath = options.htmlPath ?? "/production-readiness";
|
|
9802
|
-
const routes = new
|
|
9883
|
+
const routes = new Elysia14({
|
|
9803
9884
|
name: options.name ?? "absolutejs-voice-production-readiness"
|
|
9804
9885
|
});
|
|
9805
9886
|
routes.get(path, async ({ query, request }) => buildVoiceProductionReadinessReport(options, { query, request }));
|
|
@@ -9822,8 +9903,8 @@ var createVoiceProductionReadinessRoutes = (options) => {
|
|
|
9822
9903
|
};
|
|
9823
9904
|
|
|
9824
9905
|
// src/traceTimeline.ts
|
|
9825
|
-
import { Elysia as
|
|
9826
|
-
var
|
|
9906
|
+
import { Elysia as Elysia15 } from "elysia";
|
|
9907
|
+
var escapeHtml17 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
9827
9908
|
var getString9 = (value) => typeof value === "string" && value.trim() ? value : undefined;
|
|
9828
9909
|
var getNumber5 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
9829
9910
|
var firstString = (payload, keys) => {
|
|
@@ -9987,20 +10068,20 @@ var summarizeVoiceTraceTimeline = (events, options = {}) => {
|
|
|
9987
10068
|
};
|
|
9988
10069
|
};
|
|
9989
10070
|
var formatMs = (value) => value === undefined ? "n/a" : `${String(value)}ms`;
|
|
9990
|
-
var renderProviderCards2 = (session) => session.providers.length === 0 ? '<p class="muted">No provider events recorded for this session.</p>' : `<div class="providers">${session.providers.map((provider) => `<article><strong>${
|
|
10071
|
+
var renderProviderCards2 = (session) => session.providers.length === 0 ? '<p class="muted">No provider events recorded for this session.</p>' : `<div class="providers">${session.providers.map((provider) => `<article><strong>${escapeHtml17(provider.provider)}</strong><dl><div><dt>Events</dt><dd>${String(provider.eventCount)}</dd></div><div><dt>Avg</dt><dd>${formatMs(provider.averageElapsedMs)}</dd></div><div><dt>Max</dt><dd>${formatMs(provider.maxElapsedMs)}</dd></div><div><dt>Errors</dt><dd>${String(provider.errorCount)}</dd></div><div><dt>Fallbacks</dt><dd>${String(provider.fallbackCount)}</dd></div><div><dt>Timeouts</dt><dd>${String(provider.timeoutCount)}</dd></div></dl></article>`).join("")}</div>`;
|
|
9991
10072
|
var renderVoiceTraceTimelineSessionHTML = (session, options = {}) => {
|
|
9992
|
-
const events = session.events.map((event) => `<tr class="${
|
|
9993
|
-
const issues = session.evaluation.issues.length ? session.evaluation.issues.map((issue) => `<li class="${
|
|
9994
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
10073
|
+
const events = session.events.map((event) => `<tr class="${escapeHtml17(event.status ?? "")}"><td>+${String(event.offsetMs)}ms</td><td>${escapeHtml17(event.type)}</td><td>${escapeHtml17(event.label)}</td><td>${escapeHtml17(event.provider ?? "")}</td><td>${escapeHtml17(event.status ?? "")}</td><td>${formatMs(event.elapsedMs)}</td></tr>`).join("");
|
|
10074
|
+
const issues = session.evaluation.issues.length ? session.evaluation.issues.map((issue) => `<li class="${escapeHtml17(issue.severity)}">${escapeHtml17(issue.code)}: ${escapeHtml17(issue.message)}</li>`).join("") : "<li>none</li>";
|
|
10075
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml17(options.title ?? "Voice Trace Timeline")}</title><style>${timelineCSS}</style></head><body><main><a href="/traces">Back to traces</a><header><p class="eyebrow">Call timeline</p><h1>${escapeHtml17(session.sessionId)}</h1><p class="status ${escapeHtml17(session.status)}">${escapeHtml17(session.status)}</p></header><section class="metrics"><article><span>Events</span><strong>${String(session.summary.eventCount)}</strong></article><article><span>Turns</span><strong>${String(session.summary.turnCount)}</strong></article><article><span>Errors</span><strong>${String(session.summary.errorCount)}</strong></article><article><span>Duration</span><strong>${formatMs(session.summary.callDurationMs)}</strong></article></section><section><h2>Providers</h2>${renderProviderCards2(session)}</section><section><h2>Issues</h2><ul>${issues}</ul></section><section><h2>Timeline</h2><table><thead><tr><th>Offset</th><th>Type</th><th>Event</th><th>Provider</th><th>Status</th><th>Latency</th></tr></thead><tbody>${events}</tbody></table></section></main></body></html>`;
|
|
9995
10076
|
};
|
|
9996
|
-
var renderSessionRows = (report) => report.sessions.length === 0 ? '<tr><td colspan="7">No trace events recorded yet.</td></tr>' : report.sessions.map((session) => `<tr class="${
|
|
10077
|
+
var renderSessionRows = (report) => report.sessions.length === 0 ? '<tr><td colspan="7">No trace events recorded yet.</td></tr>' : report.sessions.map((session) => `<tr class="${escapeHtml17(session.status)}"><td><a href="/traces/${encodeURIComponent(session.sessionId)}">${escapeHtml17(session.sessionId)}</a></td><td>${escapeHtml17(session.status)}</td><td>${String(session.summary.eventCount)}</td><td>${String(session.summary.turnCount)}</td><td>${String(session.summary.errorCount)}</td><td>${formatMs(session.summary.callDurationMs)}</td><td>${session.providers.map((provider) => escapeHtml17(provider.provider)).join(", ")}</td></tr>`).join("");
|
|
9997
10078
|
var timelineCSS = "body{background:#0f1318;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}a{color:#fbbf24}.eyebrow{color:#fbbf24;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,4.5rem);line-height:.92;margin:.2rem 0 1rem}.status{border:1px solid #475569;border-radius:999px;display:inline-flex;padding:8px 12px}.healthy{color:#86efac}.warning{color:#fbbf24}.failed,.error{color:#fca5a5}.metrics,.providers{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(170px,1fr));margin:20px 0}.metrics article,.providers article{background:#181f27;border:1px solid #2b3642;border-radius:20px;padding:16px}.metrics span,dt,.muted{color:#a8b0b8}.metrics strong{display:block;font-size:2rem}dl{display:grid;gap:8px;grid-template-columns:repeat(2,minmax(0,1fr));margin:12px 0 0}dd{font-weight:800;margin:4px 0 0}table{background:#181f27;border-collapse:collapse;border-radius:18px;overflow:hidden;width:100%}td,th{border-bottom:1px solid #2b3642;padding:12px;text-align:left}section{margin-top:28px}@media(max-width:760px){main{padding:20px}table{font-size:.9rem}}";
|
|
9998
|
-
var renderVoiceTraceTimelineHTML = (report, options = {}) => `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
10079
|
+
var renderVoiceTraceTimelineHTML = (report, options = {}) => `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml17(options.title ?? "Voice Trace Timelines")}</title><style>${timelineCSS}</style></head><body><main><header><p class="eyebrow">Self-hosted voice debugging</p><h1>${escapeHtml17(options.title ?? "Voice Trace Timelines")}</h1><p class="muted">Per-call event timelines with provider latency, fallback, timeout, handoff, and error context.</p></header><section class="metrics"><article><span>Sessions</span><strong>${String(report.total)}</strong></article><article><span>Failed</span><strong>${String(report.failed)}</strong></article><article><span>Warnings</span><strong>${String(report.warnings)}</strong></article></section><table><thead><tr><th>Session</th><th>Status</th><th>Events</th><th>Turns</th><th>Errors</th><th>Duration</th><th>Providers</th></tr></thead><tbody>${renderSessionRows(report)}</tbody></table></main></body></html>`;
|
|
9999
10080
|
var createVoiceTraceTimelineRoutes = (options) => {
|
|
10000
10081
|
const path = options.path ?? "/api/voice-traces";
|
|
10001
10082
|
const htmlPath = options.htmlPath ?? "/traces";
|
|
10002
10083
|
const title = options.title ?? "AbsoluteJS Voice Trace Timelines";
|
|
10003
|
-
const routes = new
|
|
10084
|
+
const routes = new Elysia15({
|
|
10004
10085
|
name: options.name ?? "absolutejs-voice-trace-timelines"
|
|
10005
10086
|
});
|
|
10006
10087
|
const buildReport = async () => summarizeVoiceTraceTimeline(await options.store.list(), {
|
|
@@ -10209,7 +10290,7 @@ var summarizeVoiceAppKitStatus = async (options) => {
|
|
|
10209
10290
|
};
|
|
10210
10291
|
};
|
|
10211
10292
|
var createVoiceAppKitRoutes = (options) => {
|
|
10212
|
-
const routes = new
|
|
10293
|
+
const routes = new Elysia16({
|
|
10213
10294
|
name: options.name ?? "absolutejs-voice-app-kit"
|
|
10214
10295
|
});
|
|
10215
10296
|
const links = resolveLinks(options.links);
|
|
@@ -10304,6 +10385,16 @@ var createVoiceAppKitRoutes = (options) => {
|
|
|
10304
10385
|
...options.diagnostics
|
|
10305
10386
|
}));
|
|
10306
10387
|
}
|
|
10388
|
+
if (options.bargeIn !== false) {
|
|
10389
|
+
surfaces.push("bargeIn");
|
|
10390
|
+
routes.use(createVoiceBargeInRoutes({
|
|
10391
|
+
...common,
|
|
10392
|
+
htmlPath: "/barge-in",
|
|
10393
|
+
path: "/api/voice-barge-in",
|
|
10394
|
+
title: options.title ? `${options.title} Barge-In` : undefined,
|
|
10395
|
+
...options.bargeIn
|
|
10396
|
+
}));
|
|
10397
|
+
}
|
|
10307
10398
|
if (options.traceTimeline !== false) {
|
|
10308
10399
|
surfaces.push("traceTimeline");
|
|
10309
10400
|
routes.use(createVoiceTraceTimelineRoutes({
|
|
@@ -10854,7 +10945,7 @@ var createVoiceToolIdempotencyKey = (input) => {
|
|
|
10854
10945
|
].join(":");
|
|
10855
10946
|
};
|
|
10856
10947
|
// src/toolContract.ts
|
|
10857
|
-
import { Elysia as
|
|
10948
|
+
import { Elysia as Elysia17 } from "elysia";
|
|
10858
10949
|
var createDefaultSession = (contractId, caseId) => createVoiceSessionRecord(`tool-contract-${contractId}-${caseId}`);
|
|
10859
10950
|
var createDefaultTurn = (caseId) => ({
|
|
10860
10951
|
committedAt: Date.now(),
|
|
@@ -10864,7 +10955,7 @@ var createDefaultTurn = (caseId) => ({
|
|
|
10864
10955
|
});
|
|
10865
10956
|
var defaultApi = {};
|
|
10866
10957
|
var sameJSON = (left, right) => JSON.stringify(left) === JSON.stringify(right);
|
|
10867
|
-
var
|
|
10958
|
+
var escapeHtml18 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
10868
10959
|
var evaluateExpectation = (input) => {
|
|
10869
10960
|
const issues = [];
|
|
10870
10961
|
const expect = input.expect;
|
|
@@ -11030,19 +11121,19 @@ var renderVoiceToolContractHTML = (report, options = {}) => {
|
|
|
11030
11121
|
const title = options.title ?? "Voice Tool Contracts";
|
|
11031
11122
|
const contracts = report.contracts.map((contract) => {
|
|
11032
11123
|
const cases = contract.cases.map((testCase) => `<tr>
|
|
11033
|
-
<td>${
|
|
11124
|
+
<td>${escapeHtml18(testCase.label ?? testCase.caseId)}</td>
|
|
11034
11125
|
<td class="${testCase.pass ? "pass" : "fail"}">${testCase.pass ? "pass" : "fail"}</td>
|
|
11035
|
-
<td>${
|
|
11126
|
+
<td>${escapeHtml18(testCase.status)}</td>
|
|
11036
11127
|
<td>${String(testCase.attempts)}</td>
|
|
11037
11128
|
<td>${String(testCase.elapsedMs)}ms</td>
|
|
11038
11129
|
<td>${testCase.timedOut ? "yes" : "no"}</td>
|
|
11039
|
-
<td>${
|
|
11130
|
+
<td>${escapeHtml18(testCase.issues.map((issue) => issue.message).join(" ") || testCase.error || "")}</td>
|
|
11040
11131
|
</tr>`).join("");
|
|
11041
11132
|
return `<section class="contract ${contract.pass ? "pass" : "fail"}">
|
|
11042
11133
|
<div class="contract-header">
|
|
11043
11134
|
<div>
|
|
11044
|
-
<p class="eyebrow">${
|
|
11045
|
-
<h2>${
|
|
11135
|
+
<p class="eyebrow">${escapeHtml18(contract.toolName)}</p>
|
|
11136
|
+
<h2>${escapeHtml18(contract.label ?? contract.contractId)}</h2>
|
|
11046
11137
|
</div>
|
|
11047
11138
|
<strong class="${contract.pass ? "pass" : "fail"}">${contract.pass ? "Passing" : "Failing"}</strong>
|
|
11048
11139
|
</div>
|
|
@@ -11052,7 +11143,7 @@ var renderVoiceToolContractHTML = (report, options = {}) => {
|
|
|
11052
11143
|
</table>
|
|
11053
11144
|
</section>`;
|
|
11054
11145
|
}).join("");
|
|
11055
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
11146
|
+
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(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>${escapeHtml18(title)}</h1><div class="summary"><span class="pill ${report.status === "pass" ? "pass" : "fail"}">${escapeHtml18(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>`;
|
|
11056
11147
|
};
|
|
11057
11148
|
var createVoiceToolContractJSONHandler = (options) => () => runVoiceToolContractSuite(options);
|
|
11058
11149
|
var createVoiceToolContractHTMLHandler = (options) => async () => {
|
|
@@ -11069,7 +11160,7 @@ var createVoiceToolContractHTMLHandler = (options) => async () => {
|
|
|
11069
11160
|
var createVoiceToolContractRoutes = (options) => {
|
|
11070
11161
|
const path = options.path ?? "/api/tool-contracts";
|
|
11071
11162
|
const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
|
|
11072
|
-
const routes = new
|
|
11163
|
+
const routes = new Elysia17({
|
|
11073
11164
|
name: options.name ?? "absolutejs-voice-tool-contracts"
|
|
11074
11165
|
}).get(path, createVoiceToolContractJSONHandler(options));
|
|
11075
11166
|
if (htmlPath) {
|
|
@@ -11078,9 +11169,9 @@ var createVoiceToolContractRoutes = (options) => {
|
|
|
11078
11169
|
return routes;
|
|
11079
11170
|
};
|
|
11080
11171
|
// src/turnQuality.ts
|
|
11081
|
-
import { Elysia as
|
|
11172
|
+
import { Elysia as Elysia18 } from "elysia";
|
|
11082
11173
|
var DEFAULT_CONFIDENCE_WARN_THRESHOLD = 0.72;
|
|
11083
|
-
var
|
|
11174
|
+
var escapeHtml19 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
11084
11175
|
var getTurnLatencyMs = (turn) => {
|
|
11085
11176
|
const firstTranscriptAt = turn.transcripts.map((transcript) => transcript.endedAtMs ?? transcript.startedAtMs).filter((value) => typeof value === "number").sort((left, right) => left - right)[0];
|
|
11086
11177
|
if (firstTranscriptAt === undefined) {
|
|
@@ -11151,24 +11242,24 @@ var summarizeVoiceTurnQuality = async (options) => {
|
|
|
11151
11242
|
};
|
|
11152
11243
|
var renderVoiceTurnQualityHTML = (report, options = {}) => {
|
|
11153
11244
|
const title = options.title ?? "Voice Turn Quality";
|
|
11154
|
-
const turns = report.turns.map((turn) => `<article class="turn ${
|
|
11245
|
+
const turns = report.turns.map((turn) => `<article class="turn ${escapeHtml19(turn.status)}">
|
|
11155
11246
|
<div class="turn-header">
|
|
11156
11247
|
<div>
|
|
11157
|
-
<p class="eyebrow">${
|
|
11158
|
-
<h2>${
|
|
11248
|
+
<p class="eyebrow">${escapeHtml19(turn.sessionId)} \xB7 ${escapeHtml19(turn.turnId)}</p>
|
|
11249
|
+
<h2>${escapeHtml19(turn.text || "Empty turn")}</h2>
|
|
11159
11250
|
</div>
|
|
11160
|
-
<strong>${
|
|
11251
|
+
<strong>${escapeHtml19(turn.status)}</strong>
|
|
11161
11252
|
</div>
|
|
11162
11253
|
<dl>
|
|
11163
|
-
<div><dt>Source</dt><dd>${
|
|
11254
|
+
<div><dt>Source</dt><dd>${escapeHtml19(turn.source ?? "unknown")}</dd></div>
|
|
11164
11255
|
<div><dt>Confidence</dt><dd>${turn.averageConfidence === undefined ? "n/a" : `${Math.round(turn.averageConfidence * 100)}%`}</dd></div>
|
|
11165
|
-
<div><dt>Fallback</dt><dd>${turn.fallbackUsed ? `yes (${
|
|
11166
|
-
<div><dt>Correction</dt><dd>${turn.correctionChanged ? `changed${turn.correctionProvider ? ` by ${
|
|
11256
|
+
<div><dt>Fallback</dt><dd>${turn.fallbackUsed ? `yes (${escapeHtml19(turn.fallbackSelectionReason ?? "selected")})` : "no"}</dd></div>
|
|
11257
|
+
<div><dt>Correction</dt><dd>${turn.correctionChanged ? `changed${turn.correctionProvider ? ` by ${escapeHtml19(turn.correctionProvider)}` : ""}` : "none"}</dd></div>
|
|
11167
11258
|
<div><dt>Transcripts</dt><dd>${String(turn.selectedTranscriptCount)} selected \xB7 ${String(turn.finalTranscriptCount)} final \xB7 ${String(turn.partialTranscriptCount)} partial</dd></div>
|
|
11168
11259
|
<div><dt>Cost</dt><dd>${turn.costUnits === undefined ? "n/a" : String(turn.costUnits)}</dd></div>
|
|
11169
11260
|
</dl>
|
|
11170
11261
|
</article>`).join("");
|
|
11171
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
11262
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml19(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>${escapeHtml19(title)}</h1><div class="summary"><span class="pill ${escapeHtml19(report.status)}">${escapeHtml19(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>`;
|
|
11172
11263
|
};
|
|
11173
11264
|
var createVoiceTurnQualityJSONHandler = (options) => async () => summarizeVoiceTurnQuality(options);
|
|
11174
11265
|
var createVoiceTurnQualityHTMLHandler = (options) => async () => {
|
|
@@ -11185,7 +11276,7 @@ var createVoiceTurnQualityHTMLHandler = (options) => async () => {
|
|
|
11185
11276
|
var createVoiceTurnQualityRoutes = (options) => {
|
|
11186
11277
|
const path = options.path ?? "/api/turn-quality";
|
|
11187
11278
|
const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
|
|
11188
|
-
const routes = new
|
|
11279
|
+
const routes = new Elysia18({
|
|
11189
11280
|
name: options.name ?? "absolutejs-voice-turn-quality"
|
|
11190
11281
|
}).get(path, createVoiceTurnQualityJSONHandler(options));
|
|
11191
11282
|
if (htmlPath) {
|
|
@@ -11194,8 +11285,8 @@ var createVoiceTurnQualityRoutes = (options) => {
|
|
|
11194
11285
|
return routes;
|
|
11195
11286
|
};
|
|
11196
11287
|
// src/outcomeContract.ts
|
|
11197
|
-
import { Elysia as
|
|
11198
|
-
var
|
|
11288
|
+
import { Elysia as Elysia19 } from "elysia";
|
|
11289
|
+
var escapeHtml20 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
11199
11290
|
var getPayloadString = (event, key) => typeof event.payload[key] === "string" ? event.payload[key] : undefined;
|
|
11200
11291
|
var toList = async (input) => Array.isArray(input) ? input : await input?.list() ?? [];
|
|
11201
11292
|
var hydrateSessions = async (input) => {
|
|
@@ -11303,9 +11394,9 @@ var renderVoiceOutcomeContractHTML = (report, options = {}) => {
|
|
|
11303
11394
|
const contracts = report.contracts.map((contract) => `<section class="contract ${contract.pass ? "pass" : "fail"}">
|
|
11304
11395
|
<div class="contract-header">
|
|
11305
11396
|
<div>
|
|
11306
|
-
<p class="eyebrow">${
|
|
11307
|
-
<h2>${
|
|
11308
|
-
${contract.description ? `<p>${
|
|
11397
|
+
<p class="eyebrow">${escapeHtml20(contract.contractId)}</p>
|
|
11398
|
+
<h2>${escapeHtml20(contract.label ?? contract.contractId)}</h2>
|
|
11399
|
+
${contract.description ? `<p>${escapeHtml20(contract.description)}</p>` : ""}
|
|
11309
11400
|
</div>
|
|
11310
11401
|
<strong>${contract.pass ? "pass" : "fail"}</strong>
|
|
11311
11402
|
</div>
|
|
@@ -11316,9 +11407,9 @@ var renderVoiceOutcomeContractHTML = (report, options = {}) => {
|
|
|
11316
11407
|
<span>handoffs ${String(contract.matched.handoffs)}</span>
|
|
11317
11408
|
<span>events ${String(contract.matched.integrationEvents)}</span>
|
|
11318
11409
|
</div>
|
|
11319
|
-
${contract.issues.length ? `<ul>${contract.issues.map((issue) => `<li>${
|
|
11410
|
+
${contract.issues.length ? `<ul>${contract.issues.map((issue) => `<li>${escapeHtml20(issue.message)}</li>`).join("")}</ul>` : ""}
|
|
11320
11411
|
</section>`).join("");
|
|
11321
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
11412
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml20(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>${escapeHtml20(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>`;
|
|
11322
11413
|
};
|
|
11323
11414
|
var createVoiceOutcomeContractJSONHandler = (options) => async () => runVoiceOutcomeContractSuite(options);
|
|
11324
11415
|
var createVoiceOutcomeContractHTMLHandler = (options) => async () => {
|
|
@@ -11334,7 +11425,7 @@ var createVoiceOutcomeContractHTMLHandler = (options) => async () => {
|
|
|
11334
11425
|
var createVoiceOutcomeContractRoutes = (options) => {
|
|
11335
11426
|
const path = options.path ?? "/api/outcome-contracts";
|
|
11336
11427
|
const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
|
|
11337
|
-
const routes = new
|
|
11428
|
+
const routes = new Elysia19({
|
|
11338
11429
|
name: options.name ?? "absolutejs-voice-outcome-contracts"
|
|
11339
11430
|
}).get(path, createVoiceOutcomeContractJSONHandler(options));
|
|
11340
11431
|
if (htmlPath) {
|
|
@@ -11343,7 +11434,7 @@ var createVoiceOutcomeContractRoutes = (options) => {
|
|
|
11343
11434
|
return routes;
|
|
11344
11435
|
};
|
|
11345
11436
|
// src/telephonyOutcome.ts
|
|
11346
|
-
import { Elysia as
|
|
11437
|
+
import { Elysia as Elysia20 } from "elysia";
|
|
11347
11438
|
var DEFAULT_COMPLETED_STATUSES = [
|
|
11348
11439
|
"answered",
|
|
11349
11440
|
"completed",
|
|
@@ -11990,7 +12081,7 @@ var createVoiceTelephonyWebhookHandler = (options = {}) => async (input) => {
|
|
|
11990
12081
|
var createVoiceTelephonyWebhookRoutes = (options = {}) => {
|
|
11991
12082
|
const path = options.path ?? "/api/voice/telephony/webhook";
|
|
11992
12083
|
const handler = createVoiceTelephonyWebhookHandler(options);
|
|
11993
|
-
return new
|
|
12084
|
+
return new Elysia20({
|
|
11994
12085
|
name: options.name ?? "absolutejs-voice-telephony-webhooks"
|
|
11995
12086
|
}).post(path, async ({ query, request }) => {
|
|
11996
12087
|
try {
|
|
@@ -14258,7 +14349,7 @@ var createVoiceMemoryStore = () => {
|
|
|
14258
14349
|
return { get, getOrCreate, list, remove, set };
|
|
14259
14350
|
};
|
|
14260
14351
|
// src/opsWebhook.ts
|
|
14261
|
-
import { Elysia as
|
|
14352
|
+
import { Elysia as Elysia21 } from "elysia";
|
|
14262
14353
|
var toHex5 = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
14263
14354
|
var signVoiceOpsWebhookBody = async (input) => {
|
|
14264
14355
|
const encoder = new TextEncoder;
|
|
@@ -14388,7 +14479,7 @@ var verifyVoiceOpsWebhookSignature = async (input) => {
|
|
|
14388
14479
|
};
|
|
14389
14480
|
var createVoiceOpsWebhookReceiverRoutes = (options = {}) => {
|
|
14390
14481
|
const path = options.path ?? "/api/voice-ops/webhook";
|
|
14391
|
-
return new
|
|
14482
|
+
return new Elysia21().post(path, async ({ body, request, set }) => {
|
|
14392
14483
|
const bodyText = typeof body === "string" ? body : JSON.stringify(body);
|
|
14393
14484
|
if (options.signingSecret) {
|
|
14394
14485
|
const verification = await verifyVoiceOpsWebhookSignature({
|
|
@@ -16117,7 +16208,7 @@ var createVoiceSTTRoutingCorrectionHandler = (mode = "generic") => {
|
|
|
16117
16208
|
};
|
|
16118
16209
|
// src/telephony/twilio.ts
|
|
16119
16210
|
import { Buffer as Buffer3 } from "buffer";
|
|
16120
|
-
import { Elysia as
|
|
16211
|
+
import { Elysia as Elysia22 } from "elysia";
|
|
16121
16212
|
var TWILIO_MULAW_SAMPLE_RATE = 8000;
|
|
16122
16213
|
var VOICE_PCM_SAMPLE_RATE = 16000;
|
|
16123
16214
|
var escapeXml2 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
@@ -16147,7 +16238,7 @@ var resolveTwilioStreamParameters = async (parameters, input) => {
|
|
|
16147
16238
|
return parameters;
|
|
16148
16239
|
};
|
|
16149
16240
|
var joinUrlPath = (origin, path) => `${origin.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
|
|
16150
|
-
var
|
|
16241
|
+
var escapeHtml21 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
16151
16242
|
var getWebhookVerificationUrl = (webhook, input) => {
|
|
16152
16243
|
if (!webhook?.verificationUrl) {
|
|
16153
16244
|
return;
|
|
@@ -16190,23 +16281,23 @@ var buildTwilioVoiceSetupStatus = async (options, input) => {
|
|
|
16190
16281
|
};
|
|
16191
16282
|
var renderTwilioVoiceSetupHTML = (status, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
|
|
16192
16283
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Twilio setup</p>
|
|
16193
|
-
<h1>${
|
|
16284
|
+
<h1>${escapeHtml21(title)}</h1>
|
|
16194
16285
|
<p><strong>Status:</strong> ${status.ready ? "Ready" : "Needs attention"}</p>
|
|
16195
16286
|
<section>
|
|
16196
16287
|
<h2>URLs</h2>
|
|
16197
16288
|
<ul>
|
|
16198
|
-
<li><strong>TwiML:</strong> <code>${
|
|
16199
|
-
<li><strong>Media stream:</strong> <code>${
|
|
16200
|
-
<li><strong>Status webhook:</strong> <code>${
|
|
16289
|
+
<li><strong>TwiML:</strong> <code>${escapeHtml21(status.urls.twiml)}</code></li>
|
|
16290
|
+
<li><strong>Media stream:</strong> <code>${escapeHtml21(status.urls.stream)}</code></li>
|
|
16291
|
+
<li><strong>Status webhook:</strong> <code>${escapeHtml21(status.urls.webhook)}</code></li>
|
|
16201
16292
|
</ul>
|
|
16202
16293
|
</section>
|
|
16203
16294
|
<section>
|
|
16204
16295
|
<h2>Signing</h2>
|
|
16205
16296
|
<p>Mode: <code>${status.signing.mode}</code></p>
|
|
16206
|
-
${status.signing.verificationUrl ? `<p>Verification URL: <code>${
|
|
16297
|
+
${status.signing.verificationUrl ? `<p>Verification URL: <code>${escapeHtml21(status.signing.verificationUrl)}</code></p>` : ""}
|
|
16207
16298
|
</section>
|
|
16208
|
-
${status.missing.length ? `<section><h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${
|
|
16209
|
-
${status.warnings.length ? `<section><h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${
|
|
16299
|
+
${status.missing.length ? `<section><h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml21(name)}</code></li>`).join("")}</ul></section>` : ""}
|
|
16300
|
+
${status.warnings.length ? `<section><h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml21(warning)}</li>`).join("")}</ul></section>` : ""}
|
|
16210
16301
|
</main>`;
|
|
16211
16302
|
var extractTwilioStreamUrl = (twiml) => twiml.match(/<Stream\b[^>]*\surl="([^"]+)"/i)?.[1]?.replaceAll("&", "&");
|
|
16212
16303
|
var createSmokeCheck = (name, status, message, details) => ({
|
|
@@ -16217,20 +16308,20 @@ var createSmokeCheck = (name, status, message, details) => ({
|
|
|
16217
16308
|
});
|
|
16218
16309
|
var renderTwilioVoiceSmokeHTML = (report, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
|
|
16219
16310
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Twilio smoke test</p>
|
|
16220
|
-
<h1>${
|
|
16311
|
+
<h1>${escapeHtml21(title)}</h1>
|
|
16221
16312
|
<p><strong>Status:</strong> ${report.pass ? "Pass" : "Fail"}</p>
|
|
16222
16313
|
<section>
|
|
16223
16314
|
<h2>Checks</h2>
|
|
16224
16315
|
<ul>
|
|
16225
|
-
${report.checks.map((check) => `<li><strong>${
|
|
16316
|
+
${report.checks.map((check) => `<li><strong>${escapeHtml21(check.name)}</strong>: ${escapeHtml21(check.status)}${check.message ? ` - ${escapeHtml21(check.message)}` : ""}</li>`).join("")}
|
|
16226
16317
|
</ul>
|
|
16227
16318
|
</section>
|
|
16228
16319
|
<section>
|
|
16229
16320
|
<h2>Observed URLs</h2>
|
|
16230
16321
|
<ul>
|
|
16231
|
-
<li><strong>TwiML:</strong> <code>${
|
|
16232
|
-
<li><strong>Stream:</strong> <code>${
|
|
16233
|
-
<li><strong>Webhook:</strong> <code>${
|
|
16322
|
+
<li><strong>TwiML:</strong> <code>${escapeHtml21(report.setup.urls.twiml)}</code></li>
|
|
16323
|
+
<li><strong>Stream:</strong> <code>${escapeHtml21(report.twiml?.streamUrl ?? report.setup.urls.stream)}</code></li>
|
|
16324
|
+
<li><strong>Webhook:</strong> <code>${escapeHtml21(report.setup.urls.webhook)}</code></li>
|
|
16234
16325
|
</ul>
|
|
16235
16326
|
</section>
|
|
16236
16327
|
</main>`;
|
|
@@ -16690,7 +16781,7 @@ var createTwilioVoiceRoutes = (options) => {
|
|
|
16690
16781
|
const smokePath = options.smoke?.path === false ? false : options.smoke?.path ?? "/api/voice/twilio/smoke";
|
|
16691
16782
|
const bridges = new WeakMap;
|
|
16692
16783
|
const webhookPolicy = options.webhook?.policy ?? options.outcomePolicy ?? createVoiceTelephonyOutcomePolicy();
|
|
16693
|
-
const app = new
|
|
16784
|
+
const app = new Elysia22({
|
|
16694
16785
|
name: options.name ?? "absolutejs-voice-twilio"
|
|
16695
16786
|
}).get(twimlPath, async ({ query, request }) => {
|
|
16696
16787
|
const streamUrl = await resolveTwilioStreamUrl(options, {
|
|
@@ -16826,9 +16917,9 @@ var createTwilioVoiceRoutes = (options) => {
|
|
|
16826
16917
|
};
|
|
16827
16918
|
// src/telephony/telnyx.ts
|
|
16828
16919
|
import { Buffer as Buffer4 } from "buffer";
|
|
16829
|
-
import { Elysia as
|
|
16920
|
+
import { Elysia as Elysia23 } from "elysia";
|
|
16830
16921
|
var escapeXml3 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
16831
|
-
var
|
|
16922
|
+
var escapeHtml22 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
16832
16923
|
var joinUrlPath2 = (origin, path) => `${origin.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
|
|
16833
16924
|
var resolveRequestOrigin2 = (request) => {
|
|
16834
16925
|
const url = new URL(request.url);
|
|
@@ -17029,21 +17120,21 @@ var buildTelnyxVoiceSetupStatus = async (options, input) => {
|
|
|
17029
17120
|
};
|
|
17030
17121
|
var renderTelnyxSetupHTML = (status, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
|
|
17031
17122
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Telnyx setup</p>
|
|
17032
|
-
<h1>${
|
|
17123
|
+
<h1>${escapeHtml22(title)}</h1>
|
|
17033
17124
|
<p><strong>Status:</strong> ${status.ready ? "Ready" : "Needs attention"}</p>
|
|
17034
17125
|
<ul>
|
|
17035
|
-
<li><strong>TeXML:</strong> <code>${
|
|
17036
|
-
<li><strong>Media stream:</strong> <code>${
|
|
17037
|
-
<li><strong>Status webhook:</strong> <code>${
|
|
17126
|
+
<li><strong>TeXML:</strong> <code>${escapeHtml22(status.urls.texml)}</code></li>
|
|
17127
|
+
<li><strong>Media stream:</strong> <code>${escapeHtml22(status.urls.stream)}</code></li>
|
|
17128
|
+
<li><strong>Status webhook:</strong> <code>${escapeHtml22(status.urls.webhook)}</code></li>
|
|
17038
17129
|
</ul>
|
|
17039
|
-
${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${
|
|
17040
|
-
${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${
|
|
17130
|
+
${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml22(name)}</code></li>`).join("")}</ul>` : ""}
|
|
17131
|
+
${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml22(warning)}</li>`).join("")}</ul>` : ""}
|
|
17041
17132
|
</main>`;
|
|
17042
17133
|
var renderTelnyxSmokeHTML = (report, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
|
|
17043
17134
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Telnyx smoke test</p>
|
|
17044
|
-
<h1>${
|
|
17135
|
+
<h1>${escapeHtml22(title)}</h1>
|
|
17045
17136
|
<p><strong>Status:</strong> ${report.pass ? "Pass" : "Fail"}</p>
|
|
17046
|
-
<ul>${report.checks.map((check) => `<li><strong>${
|
|
17137
|
+
<ul>${report.checks.map((check) => `<li><strong>${escapeHtml22(check.name)}</strong>: ${escapeHtml22(check.status)}${check.message ? ` - ${escapeHtml22(check.message)}` : ""}</li>`).join("")}</ul>
|
|
17047
17138
|
</main>`;
|
|
17048
17139
|
var runTelnyxSmokeTest = async (input) => {
|
|
17049
17140
|
const setup = await buildTelnyxVoiceSetupStatus(input.options, input);
|
|
@@ -17137,7 +17228,7 @@ var createTelnyxVoiceRoutes = (options = {}) => {
|
|
|
17137
17228
|
publicKey: options.webhook?.publicKey,
|
|
17138
17229
|
toleranceSeconds: options.webhook?.toleranceSeconds
|
|
17139
17230
|
}) : undefined);
|
|
17140
|
-
const app = new
|
|
17231
|
+
const app = new Elysia23({
|
|
17141
17232
|
name: options.name ?? "absolutejs-voice-telnyx"
|
|
17142
17233
|
}).get(texmlPath, async ({ query, request }) => {
|
|
17143
17234
|
const streamUrl = await resolveTelnyxStreamUrl(options, {
|
|
@@ -17247,9 +17338,9 @@ var createTelnyxVoiceRoutes = (options = {}) => {
|
|
|
17247
17338
|
};
|
|
17248
17339
|
// src/telephony/plivo.ts
|
|
17249
17340
|
import { Buffer as Buffer5 } from "buffer";
|
|
17250
|
-
import { Elysia as
|
|
17341
|
+
import { Elysia as Elysia24 } from "elysia";
|
|
17251
17342
|
var escapeXml4 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
17252
|
-
var
|
|
17343
|
+
var escapeHtml23 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
17253
17344
|
var joinUrlPath3 = (origin, path) => `${origin.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
|
|
17254
17345
|
var resolveRequestOrigin3 = (request) => {
|
|
17255
17346
|
const url = new URL(request.url);
|
|
@@ -17500,21 +17591,21 @@ var buildPlivoVoiceSetupStatus = async (options, input) => {
|
|
|
17500
17591
|
};
|
|
17501
17592
|
var renderPlivoSetupHTML = (status, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
|
|
17502
17593
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Plivo setup</p>
|
|
17503
|
-
<h1>${
|
|
17594
|
+
<h1>${escapeHtml23(title)}</h1>
|
|
17504
17595
|
<p><strong>Status:</strong> ${status.ready ? "Ready" : "Needs attention"}</p>
|
|
17505
17596
|
<ul>
|
|
17506
|
-
<li><strong>Answer XML:</strong> <code>${
|
|
17507
|
-
<li><strong>Audio stream:</strong> <code>${
|
|
17508
|
-
<li><strong>Status webhook:</strong> <code>${
|
|
17597
|
+
<li><strong>Answer XML:</strong> <code>${escapeHtml23(status.urls.answer)}</code></li>
|
|
17598
|
+
<li><strong>Audio stream:</strong> <code>${escapeHtml23(status.urls.stream)}</code></li>
|
|
17599
|
+
<li><strong>Status webhook:</strong> <code>${escapeHtml23(status.urls.webhook)}</code></li>
|
|
17509
17600
|
</ul>
|
|
17510
|
-
${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${
|
|
17511
|
-
${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${
|
|
17601
|
+
${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml23(name)}</code></li>`).join("")}</ul>` : ""}
|
|
17602
|
+
${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml23(warning)}</li>`).join("")}</ul>` : ""}
|
|
17512
17603
|
</main>`;
|
|
17513
17604
|
var renderPlivoSmokeHTML = (report, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
|
|
17514
17605
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Plivo smoke test</p>
|
|
17515
|
-
<h1>${
|
|
17606
|
+
<h1>${escapeHtml23(title)}</h1>
|
|
17516
17607
|
<p><strong>Status:</strong> ${report.pass ? "Pass" : "Fail"}</p>
|
|
17517
|
-
<ul>${report.checks.map((check) => `<li><strong>${
|
|
17608
|
+
<ul>${report.checks.map((check) => `<li><strong>${escapeHtml23(check.name)}</strong>: ${escapeHtml23(check.status)}${check.message ? ` - ${escapeHtml23(check.message)}` : ""}</li>`).join("")}</ul>
|
|
17518
17609
|
</main>`;
|
|
17519
17610
|
var runPlivoSmokeTest = async (input) => {
|
|
17520
17611
|
const setup = await buildPlivoVoiceSetupStatus(input.options, input);
|
|
@@ -17609,7 +17700,7 @@ var createPlivoVoiceRoutes = (options = {}) => {
|
|
|
17609
17700
|
request: input.request
|
|
17610
17701
|
}) : verificationUrl ?? input.request.url
|
|
17611
17702
|
}) : undefined);
|
|
17612
|
-
const app = new
|
|
17703
|
+
const app = new Elysia24({
|
|
17613
17704
|
name: options.name ?? "absolutejs-voice-plivo"
|
|
17614
17705
|
}).get(answerPath, async ({ query, request }) => {
|
|
17615
17706
|
const streamUrl = await resolvePlivoStreamUrl(options, {
|
|
@@ -17797,6 +17888,7 @@ export {
|
|
|
17797
17888
|
summarizeVoiceIntegrationEvents,
|
|
17798
17889
|
summarizeVoiceHandoffHealth,
|
|
17799
17890
|
summarizeVoiceHandoffDeliveries,
|
|
17891
|
+
summarizeVoiceBargeIn,
|
|
17800
17892
|
summarizeVoiceAssistantRuns,
|
|
17801
17893
|
summarizeVoiceAssistantHealth,
|
|
17802
17894
|
summarizeVoiceAppKitStatus,
|
|
@@ -17849,6 +17941,7 @@ export {
|
|
|
17849
17941
|
renderVoiceEvalBaselineHTML,
|
|
17850
17942
|
renderVoiceCallReviewMarkdown,
|
|
17851
17943
|
renderVoiceCallReviewHTML,
|
|
17944
|
+
renderVoiceBargeInHTML,
|
|
17852
17945
|
renderVoiceAssistantHealthHTML,
|
|
17853
17946
|
redactVoiceTraceText,
|
|
17854
17947
|
redactVoiceTraceEvents,
|
|
@@ -18015,6 +18108,7 @@ export {
|
|
|
18015
18108
|
createVoiceCallReviewFromLiveTelephonyReport,
|
|
18016
18109
|
createVoiceCallCompletedEvent,
|
|
18017
18110
|
createVoiceCRMActivitySink,
|
|
18111
|
+
createVoiceBargeInRoutes,
|
|
18018
18112
|
createVoiceAssistantMemoryRecord,
|
|
18019
18113
|
createVoiceAssistantMemoryHandle,
|
|
18020
18114
|
createVoiceAssistantHealthRoutes,
|