@absolutejs/voice 0.0.22-beta.130 → 0.0.22-beta.131
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/README.md +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +387 -259
- package/dist/reconnectContract.d.ts +83 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9995,9 +9995,134 @@ var createVoiceBargeInRoutes = (options) => {
|
|
|
9995
9995
|
});
|
|
9996
9996
|
return routes;
|
|
9997
9997
|
};
|
|
9998
|
-
// src/
|
|
9998
|
+
// src/reconnectContract.ts
|
|
9999
9999
|
import { Elysia as Elysia8 } from "elysia";
|
|
10000
10000
|
var escapeHtml11 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
10001
|
+
var unique = (values) => [...new Set(values)];
|
|
10002
|
+
var findDuplicateTurnIds = (snapshots) => {
|
|
10003
|
+
const duplicates = new Set;
|
|
10004
|
+
for (const snapshot of snapshots) {
|
|
10005
|
+
const seen = new Set;
|
|
10006
|
+
for (const turnId of snapshot.turnIds ?? []) {
|
|
10007
|
+
if (seen.has(turnId)) {
|
|
10008
|
+
duplicates.add(turnId);
|
|
10009
|
+
}
|
|
10010
|
+
seen.add(turnId);
|
|
10011
|
+
}
|
|
10012
|
+
}
|
|
10013
|
+
return [...duplicates].sort();
|
|
10014
|
+
};
|
|
10015
|
+
var runVoiceReconnectContract = (options) => {
|
|
10016
|
+
const snapshots = [...options.snapshots].sort((left, right) => left.at - right.at);
|
|
10017
|
+
const issues = [];
|
|
10018
|
+
const statuses = unique(snapshots.map((snapshot) => snapshot.reconnect.status));
|
|
10019
|
+
const attempts = Math.max(0, ...snapshots.map((snapshot) => snapshot.reconnect.attempts));
|
|
10020
|
+
const maxAttempts = Math.max(0, ...snapshots.map((snapshot) => snapshot.reconnect.maxAttempts));
|
|
10021
|
+
const reconnected = statuses.includes("reconnecting");
|
|
10022
|
+
const resumed = statuses.includes("resumed");
|
|
10023
|
+
const exhausted = statuses.includes("exhausted");
|
|
10024
|
+
const duplicateTurnIds = findDuplicateTurnIds(snapshots);
|
|
10025
|
+
const requireReconnect = options.requireReconnect ?? true;
|
|
10026
|
+
const requireResume = options.requireResume ?? true;
|
|
10027
|
+
const requireReplayProtection = options.requireReplayProtection ?? true;
|
|
10028
|
+
if (snapshots.length === 0) {
|
|
10029
|
+
issues.push({
|
|
10030
|
+
code: "reconnect.no_snapshots",
|
|
10031
|
+
message: "No reconnect snapshots were provided.",
|
|
10032
|
+
severity: "error"
|
|
10033
|
+
});
|
|
10034
|
+
}
|
|
10035
|
+
if (requireReconnect && !reconnected) {
|
|
10036
|
+
issues.push({
|
|
10037
|
+
code: "reconnect.not_observed",
|
|
10038
|
+
message: "No reconnecting state was observed.",
|
|
10039
|
+
severity: "error"
|
|
10040
|
+
});
|
|
10041
|
+
}
|
|
10042
|
+
if (requireResume && reconnected && !resumed) {
|
|
10043
|
+
issues.push({
|
|
10044
|
+
code: exhausted ? "reconnect.exhausted_before_resume" : "reconnect.resume_not_observed",
|
|
10045
|
+
message: exhausted ? "Reconnect exhausted before a resumed state was observed." : "Reconnect started but no resumed state was observed.",
|
|
10046
|
+
severity: "error"
|
|
10047
|
+
});
|
|
10048
|
+
}
|
|
10049
|
+
for (const snapshot of snapshots) {
|
|
10050
|
+
const { reconnect } = snapshot;
|
|
10051
|
+
if (reconnect.maxAttempts > 0 && reconnect.attempts > reconnect.maxAttempts) {
|
|
10052
|
+
issues.push({
|
|
10053
|
+
code: "reconnect.max_attempts_exceeded",
|
|
10054
|
+
message: `Reconnect attempts exceeded maxAttempts at ${snapshot.at}.`,
|
|
10055
|
+
severity: "error"
|
|
10056
|
+
});
|
|
10057
|
+
}
|
|
10058
|
+
if (reconnect.status === "reconnecting" && reconnect.nextAttemptAt !== undefined && reconnect.nextAttemptAt < snapshot.at) {
|
|
10059
|
+
issues.push({
|
|
10060
|
+
code: "reconnect.stale_next_attempt",
|
|
10061
|
+
message: `Reconnect nextAttemptAt is older than the snapshot at ${snapshot.at}.`,
|
|
10062
|
+
severity: "warning"
|
|
10063
|
+
});
|
|
10064
|
+
}
|
|
10065
|
+
}
|
|
10066
|
+
if (requireReplayProtection && duplicateTurnIds.length > 0) {
|
|
10067
|
+
issues.push({
|
|
10068
|
+
code: "reconnect.duplicate_turn_ids",
|
|
10069
|
+
message: `Replay produced duplicate turn ids: ${duplicateTurnIds.join(", ")}.`,
|
|
10070
|
+
severity: "error"
|
|
10071
|
+
});
|
|
10072
|
+
}
|
|
10073
|
+
const pass = issues.every((issue) => issue.severity !== "error");
|
|
10074
|
+
return {
|
|
10075
|
+
checkedAt: Date.now(),
|
|
10076
|
+
issues,
|
|
10077
|
+
pass,
|
|
10078
|
+
snapshotCount: snapshots.length,
|
|
10079
|
+
statuses,
|
|
10080
|
+
summary: {
|
|
10081
|
+
attempts,
|
|
10082
|
+
duplicateTurnIds,
|
|
10083
|
+
exhausted,
|
|
10084
|
+
maxAttempts,
|
|
10085
|
+
reconnected,
|
|
10086
|
+
resumed
|
|
10087
|
+
}
|
|
10088
|
+
};
|
|
10089
|
+
};
|
|
10090
|
+
var renderVoiceReconnectContractHTML = (report) => {
|
|
10091
|
+
const issues = report.issues.map((issue) => `<li class="${escapeHtml11(issue.severity)}"><strong>${escapeHtml11(issue.code)}</strong>: ${escapeHtml11(issue.message)}</li>`).join("");
|
|
10092
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>Voice Reconnect Contract</title><style>body{background:#0d1117;color:#f8fafc;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:980px;padding:32px}.hero,.card{background:#151b23;border:1px solid #30363d;border-radius:18px;margin-bottom:16px;padding:20px}.eyebrow{color:#7dd3fc;font-size:.78rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}h1{font-size:clamp(2.25rem,7vw,4.5rem);letter-spacing:-.06em;line-height:.92;margin:.2rem 0 1rem}.summary{display:flex;flex-wrap:wrap;gap:10px}.pill{background:#0d1117;border:1px solid #30363d;border-radius:999px;padding:7px 10px}.pass{color:#86efac}.fail,.error{color:#fca5a5}.warning{color:#fde68a}li{margin:8px 0}</style></head><body><main><section class="hero"><p class="eyebrow">Reconnect Resume Proof</p><h1>Voice reconnect contract</h1><div class="summary"><span class="pill ${report.pass ? "pass" : "fail"}">${report.pass ? "pass" : "fail"}</span><span class="pill">${String(report.snapshotCount)} snapshots</span><span class="pill">${String(report.summary.attempts)} attempts</span><span class="pill">statuses ${escapeHtml11(report.statuses.join(", ") || "none")}</span></div></section><section class="card"><h2>Summary</h2><p>reconnected ${String(report.summary.reconnected)} \xB7 resumed ${String(report.summary.resumed)} \xB7 exhausted ${String(report.summary.exhausted)} \xB7 duplicate turns ${String(report.summary.duplicateTurnIds.length)}</p></section><section class="card"><h2>Issues</h2>${issues ? `<ul>${issues}</ul>` : '<p class="pass">No contract issues.</p>'}</section></main></body></html>`;
|
|
10093
|
+
};
|
|
10094
|
+
var createVoiceReconnectContractRoutes = (options) => {
|
|
10095
|
+
const path = options.path ?? "/api/voice/reconnect-contract";
|
|
10096
|
+
const htmlPath = options.htmlPath ?? "/voice/reconnect-contract";
|
|
10097
|
+
const buildReport = async () => runVoiceReconnectContract({
|
|
10098
|
+
requireReconnect: options.requireReconnect,
|
|
10099
|
+
requireReplayProtection: options.requireReplayProtection,
|
|
10100
|
+
requireResume: options.requireResume,
|
|
10101
|
+
snapshots: await options.getSnapshots()
|
|
10102
|
+
});
|
|
10103
|
+
const app = new Elysia8({ name: options.name ?? "absolutejs-voice-reconnect-contract" }).get(path, async () => new Response(JSON.stringify(await buildReport()), {
|
|
10104
|
+
headers: {
|
|
10105
|
+
"content-type": "application/json; charset=utf-8",
|
|
10106
|
+
...options.headers
|
|
10107
|
+
}
|
|
10108
|
+
}));
|
|
10109
|
+
if (htmlPath !== false) {
|
|
10110
|
+
app.get(htmlPath, async () => {
|
|
10111
|
+
const report = await buildReport();
|
|
10112
|
+
const html = options.render ? await options.render(report) : renderVoiceReconnectContractHTML(report);
|
|
10113
|
+
return new Response(html, {
|
|
10114
|
+
headers: {
|
|
10115
|
+
"content-type": "text/html; charset=utf-8",
|
|
10116
|
+
...options.headers
|
|
10117
|
+
}
|
|
10118
|
+
});
|
|
10119
|
+
});
|
|
10120
|
+
}
|
|
10121
|
+
return app;
|
|
10122
|
+
};
|
|
10123
|
+
// src/diagnosticsRoutes.ts
|
|
10124
|
+
import { Elysia as Elysia9 } from "elysia";
|
|
10125
|
+
var escapeHtml12 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
10001
10126
|
var getString5 = (value) => typeof value === "string" && value.trim() ? value : undefined;
|
|
10002
10127
|
var getNumber4 = (value) => {
|
|
10003
10128
|
const parsed = typeof value === "number" ? value : typeof value === "string" ? Number(value) : undefined;
|
|
@@ -10063,9 +10188,9 @@ var renderDiagnosticsIndex = (input) => {
|
|
|
10063
10188
|
const rows = [...sessions.entries()].sort(([, left], [, right]) => (right.at(-1)?.at ?? 0) - (left.at(-1)?.at ?? 0)).slice(0, 50).map(([sessionId, events]) => {
|
|
10064
10189
|
const summary = summarizeVoiceTrace(events);
|
|
10065
10190
|
const encoded = encodeURIComponent(sessionId);
|
|
10066
|
-
return `<tr><td>${
|
|
10191
|
+
return `<tr><td>${escapeHtml12(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>`;
|
|
10067
10192
|
}).join("");
|
|
10068
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
10193
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml12(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>${escapeHtml12(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>`;
|
|
10069
10194
|
};
|
|
10070
10195
|
var withRedaction = (events, query, defaultRedact) => {
|
|
10071
10196
|
const shouldRedact = query.redact === undefined ? defaultRedact : getBoolean(query.redact);
|
|
@@ -10074,7 +10199,7 @@ var withRedaction = (events, query, defaultRedact) => {
|
|
|
10074
10199
|
var createVoiceDiagnosticsRoutes = (options) => {
|
|
10075
10200
|
const path = options.path ?? "/diagnostics";
|
|
10076
10201
|
const title = options.title ?? "AbsoluteJS Voice Diagnostics";
|
|
10077
|
-
const routes = new
|
|
10202
|
+
const routes = new Elysia9({
|
|
10078
10203
|
name: options.name ?? "absolutejs-voice-diagnostics"
|
|
10079
10204
|
});
|
|
10080
10205
|
routes.get(path, async () => {
|
|
@@ -10131,8 +10256,8 @@ var createVoiceDiagnosticsRoutes = (options) => {
|
|
|
10131
10256
|
return routes;
|
|
10132
10257
|
};
|
|
10133
10258
|
// src/demoReadyRoutes.ts
|
|
10134
|
-
import { Elysia as
|
|
10135
|
-
var
|
|
10259
|
+
import { Elysia as Elysia10 } from "elysia";
|
|
10260
|
+
var escapeHtml13 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
10136
10261
|
var rollupStatus = (sections) => sections.some((section) => section.status === "fail") ? "fail" : sections.some((section) => section.status === "warn") ? "warn" : "pass";
|
|
10137
10262
|
var resolveLoader = async (loader, input) => typeof loader === "function" ? await loader(input) : loader;
|
|
10138
10263
|
var buildVoiceDemoReadyReport = async (options, input = {}) => {
|
|
@@ -10216,17 +10341,17 @@ var buildVoiceDemoReadyReport = async (options, input = {}) => {
|
|
|
10216
10341
|
};
|
|
10217
10342
|
};
|
|
10218
10343
|
var renderVoiceDemoReadyHTML = (report) => {
|
|
10219
|
-
const sections = report.sections.map((section) => `<article class="section ${
|
|
10220
|
-
<div><span>${
|
|
10221
|
-
<strong>${
|
|
10222
|
-
${section.href ? `<a href="${
|
|
10344
|
+
const sections = report.sections.map((section) => `<article class="section ${escapeHtml13(section.status)}">
|
|
10345
|
+
<div><span>${escapeHtml13(section.status.toUpperCase())}</span><h2>${escapeHtml13(section.label)}</h2>${section.description ? `<p>${escapeHtml13(section.description)}</p>` : ""}</div>
|
|
10346
|
+
<strong>${escapeHtml13(String(section.value ?? section.status))}</strong>
|
|
10347
|
+
${section.href ? `<a href="${escapeHtml13(section.href)}">Open</a>` : ""}
|
|
10223
10348
|
</article>`).join("");
|
|
10224
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
10349
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml13(report.title)}</title><style>body{background:#0d141b;color:#f8f3e7;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,.2),rgba(245,158,11,.12));border:1px solid #283544;border-radius:28px;margin-bottom:18px;padding:28px}.eyebrow{color:#5eead4;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{border:1px solid #3f3f46;border-radius:999px;display:inline-flex;font-weight:900;padding:8px 12px}.sections{display:grid;gap:14px}.section{align-items:center;background:#151d26;border:1px solid #283544;border-radius:22px;display:grid;gap:16px;grid-template-columns:1fr auto auto;padding:18px}.section span{color:#aab5c0;font-size:.78rem;font-weight:900;letter-spacing:.08em}.section h2{margin:.2rem 0}.section p{color:#b9c0c8;margin:.2rem 0 0}.section strong{font-size:1.4rem}.pass{border-color:rgba(34,197,94,.55)}.warn{border-color:rgba(245,158,11,.65)}.fail{border-color:rgba(239,68,68,.75)}a{color:#5eead4}@media(max-width:760px){main{padding:20px}.section{grid-template-columns:1fr}}</style></head><body><main><section class="hero"><p class="eyebrow">Demo readiness</p><h1>${escapeHtml13(report.title)}</h1><p>One customer-facing checklist for the self-hosted voice proof surfaces: ops status, production readiness, phone setup, and phone smoke traces.</p><p class="status ${escapeHtml13(report.status)}">Overall: ${escapeHtml13(report.status.toUpperCase())}</p><p>Checked ${escapeHtml13(new Date(report.checkedAt).toLocaleString())}</p></section><section class="sections">${sections || '<article class="section warn"><div><span>WARN</span><h2>No checks configured</h2><p>Add ops status, production readiness, phone setup, or phone smoke loaders.</p></div><strong>warn</strong></article>'}</section></main></body></html>`;
|
|
10225
10350
|
};
|
|
10226
10351
|
var createVoiceDemoReadyRoutes = (options) => {
|
|
10227
10352
|
const path = options.path ?? "/api/demo-ready";
|
|
10228
10353
|
const htmlPath = options.htmlPath ?? "/demo-ready";
|
|
10229
|
-
const routes = new
|
|
10354
|
+
const routes = new Elysia10({
|
|
10230
10355
|
name: options.name ?? "absolutejs-voice-demo-ready"
|
|
10231
10356
|
});
|
|
10232
10357
|
routes.get(path, async ({ query, request }) => buildVoiceDemoReadyReport(options, { query, request }));
|
|
@@ -10447,16 +10572,16 @@ var applyVoiceDataRetentionPolicy = async (options) => {
|
|
|
10447
10572
|
};
|
|
10448
10573
|
var buildVoiceDataRetentionPlan = (options) => applyVoiceDataRetentionPolicy({ ...options, dryRun: true });
|
|
10449
10574
|
// src/evalRoutes.ts
|
|
10450
|
-
import { Elysia as
|
|
10575
|
+
import { Elysia as Elysia13 } from "elysia";
|
|
10451
10576
|
import { mkdir } from "fs/promises";
|
|
10452
10577
|
import { dirname } from "path";
|
|
10453
10578
|
|
|
10454
10579
|
// src/qualityRoutes.ts
|
|
10455
|
-
import { Elysia as
|
|
10580
|
+
import { Elysia as Elysia12 } from "elysia";
|
|
10456
10581
|
|
|
10457
10582
|
// src/handoffHealth.ts
|
|
10458
|
-
import { Elysia as
|
|
10459
|
-
var
|
|
10583
|
+
import { Elysia as Elysia11 } from "elysia";
|
|
10584
|
+
var escapeHtml14 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
10460
10585
|
var getString6 = (value) => typeof value === "string" && value.length > 0 ? value : undefined;
|
|
10461
10586
|
var isStatus = (value) => value === "delivered" || value === "failed" || value === "skipped";
|
|
10462
10587
|
var increment3 = (record, key) => {
|
|
@@ -10574,10 +10699,10 @@ var renderActionSummary = (summary) => {
|
|
|
10574
10699
|
return [
|
|
10575
10700
|
'<section class="voice-handoff-health-columns">',
|
|
10576
10701
|
"<article><h3>Actions</h3>",
|
|
10577
|
-
actions.length === 0 ? "<p>No handoff actions yet.</p>" : `<ul>${actions.map(([action, count]) => `<li>${
|
|
10702
|
+
actions.length === 0 ? "<p>No handoff actions yet.</p>" : `<ul>${actions.map(([action, count]) => `<li>${escapeHtml14(action)}: ${String(count)}</li>`).join("")}</ul>`,
|
|
10578
10703
|
"</article>",
|
|
10579
10704
|
"<article><h3>Adapters</h3>",
|
|
10580
|
-
adapters.length === 0 ? "<p>No adapter deliveries yet.</p>" : `<ul>${adapters.map(([adapterId, counts]) => `<li>${
|
|
10705
|
+
adapters.length === 0 ? "<p>No adapter deliveries yet.</p>" : `<ul>${adapters.map(([adapterId, counts]) => `<li>${escapeHtml14(adapterId)}: ${String(counts.delivered)} delivered / ${String(counts.failed)} failed / ${String(counts.skipped)} skipped</li>`).join("")}</ul>`,
|
|
10581
10706
|
"</article>",
|
|
10582
10707
|
"</section>"
|
|
10583
10708
|
].join("");
|
|
@@ -10591,22 +10716,22 @@ var renderVoiceHandoffHealthHTML = (summary) => [
|
|
|
10591
10716
|
summary.events.length === 0 ? '<p class="voice-handoff-health-empty">No handoffs found.</p>' : [
|
|
10592
10717
|
'<div class="voice-handoff-health-events">',
|
|
10593
10718
|
...summary.events.map((event) => [
|
|
10594
|
-
`<article class="${
|
|
10719
|
+
`<article class="${escapeHtml14(event.status)}">`,
|
|
10595
10720
|
'<div class="voice-handoff-health-event-header">',
|
|
10596
|
-
`<strong>${
|
|
10597
|
-
`<span>${
|
|
10721
|
+
`<strong>${escapeHtml14(event.action ?? "handoff")}</strong>`,
|
|
10722
|
+
`<span>${escapeHtml14(event.status)}</span>`,
|
|
10598
10723
|
"</div>",
|
|
10599
|
-
`<p><small>${
|
|
10600
|
-
event.target ? `<p>Target: ${
|
|
10601
|
-
event.reason ? `<p>Reason: ${
|
|
10724
|
+
`<p><small>${escapeHtml14(event.sessionId)}</small></p>`,
|
|
10725
|
+
event.target ? `<p>Target: ${escapeHtml14(event.target)}</p>` : "",
|
|
10726
|
+
event.reason ? `<p>Reason: ${escapeHtml14(event.reason)}</p>` : "",
|
|
10602
10727
|
event.deliveries.length ? `<ul>${event.deliveries.map((delivery) => [
|
|
10603
10728
|
"<li>",
|
|
10604
|
-
`${
|
|
10605
|
-
delivery.deliveredTo ? ` to ${
|
|
10606
|
-
delivery.error ? ` (${
|
|
10729
|
+
`${escapeHtml14(delivery.adapterId)}: ${escapeHtml14(delivery.status)}`,
|
|
10730
|
+
delivery.deliveredTo ? ` to ${escapeHtml14(delivery.deliveredTo)}` : "",
|
|
10731
|
+
delivery.error ? ` (${escapeHtml14(delivery.error)})` : "",
|
|
10607
10732
|
"</li>"
|
|
10608
10733
|
].join("")).join("")}</ul>` : "",
|
|
10609
|
-
event.replayHref ? `<p><a href="${
|
|
10734
|
+
event.replayHref ? `<p><a href="${escapeHtml14(event.replayHref)}">Open replay</a></p>` : "",
|
|
10610
10735
|
"</article>"
|
|
10611
10736
|
].join("")),
|
|
10612
10737
|
"</div>"
|
|
@@ -10638,7 +10763,7 @@ var createVoiceHandoffHealthHTMLHandler = (options = {}) => async ({ query }) =>
|
|
|
10638
10763
|
var createVoiceHandoffHealthRoutes = (options = {}) => {
|
|
10639
10764
|
const path = options.path ?? "/api/voice-handoffs";
|
|
10640
10765
|
const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
|
|
10641
|
-
const routes = new
|
|
10766
|
+
const routes = new Elysia11({
|
|
10642
10767
|
name: options.name ?? "absolutejs-voice-handoff-health"
|
|
10643
10768
|
}).get(path, createVoiceHandoffHealthJSONHandler(options));
|
|
10644
10769
|
if (htmlPath) {
|
|
@@ -10759,17 +10884,17 @@ var evaluateVoiceQuality = async (input) => {
|
|
|
10759
10884
|
thresholds
|
|
10760
10885
|
};
|
|
10761
10886
|
};
|
|
10762
|
-
var
|
|
10887
|
+
var escapeHtml15 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
10763
10888
|
var formatMetricValue = (metric) => metric.unit === "rate" ? `${(metric.actual * 100).toFixed(2)}%` : metric.unit === "ms" ? `${Math.round(metric.actual)}ms` : String(metric.actual);
|
|
10764
10889
|
var formatThreshold = (metric) => metric.unit === "rate" ? `${(metric.threshold * 100).toFixed(2)}%` : metric.unit === "ms" ? `${Math.round(metric.threshold)}ms` : String(metric.threshold);
|
|
10765
10890
|
var renderVoiceQualityHTML = (report, options = {}) => {
|
|
10766
|
-
const rows = Object.entries(report.metrics).map(([key, metric]) => `<tr class="${metric.pass ? "pass" : "fail"}"><td>${
|
|
10767
|
-
const links = options.links?.length ? `<nav>${options.links.map((link) => `<a href="${
|
|
10891
|
+
const rows = Object.entries(report.metrics).map(([key, metric]) => `<tr class="${metric.pass ? "pass" : "fail"}"><td>${escapeHtml15(metric.label)}</td><td>${escapeHtml15(formatMetricValue(metric))}</td><td>${escapeHtml15(formatThreshold(metric))}</td><td>${metric.pass ? "pass" : "fail"}</td><td><code>${escapeHtml15(key)}</code></td></tr>`).join("");
|
|
10892
|
+
const links = options.links?.length ? `<nav>${options.links.map((link) => `<a href="${escapeHtml15(link.href)}">${escapeHtml15(link.label)}</a>`).join("")}</nav>` : "";
|
|
10768
10893
|
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>`;
|
|
10769
10894
|
};
|
|
10770
10895
|
var createVoiceQualityRoutes = (options) => {
|
|
10771
10896
|
const path = options.path ?? "/quality";
|
|
10772
|
-
const routes = new
|
|
10897
|
+
const routes = new Elysia12({
|
|
10773
10898
|
name: options.name ?? "absolutejs-voice-quality"
|
|
10774
10899
|
});
|
|
10775
10900
|
const getReport = () => evaluateVoiceQuality({
|
|
@@ -10798,7 +10923,7 @@ var createVoiceQualityRoutes = (options) => {
|
|
|
10798
10923
|
};
|
|
10799
10924
|
|
|
10800
10925
|
// src/evalRoutes.ts
|
|
10801
|
-
var
|
|
10926
|
+
var escapeHtml16 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
10802
10927
|
var rate2 = (count, total) => count / Math.max(1, total);
|
|
10803
10928
|
var normalizeSearchText = (value) => value.trim().toLowerCase();
|
|
10804
10929
|
var getString8 = (value) => typeof value === "string" ? value : undefined;
|
|
@@ -11107,44 +11232,44 @@ var formatTime = (value) => value === undefined ? "unknown" : new Date(value).to
|
|
|
11107
11232
|
var formatPercent = (value) => `${(value * 100).toFixed(2)}%`;
|
|
11108
11233
|
var renderVoiceEvalHTML = (report, options = {}) => {
|
|
11109
11234
|
const title = options.title ?? "AbsoluteJS Voice Evals";
|
|
11110
|
-
const links = options.links?.length ? `<nav>${options.links.map((link) => `<a href="${
|
|
11111
|
-
const trend = report.trend.length ? report.trend.map((bucket) => `<tr><td>${
|
|
11235
|
+
const links = options.links?.length ? `<nav>${options.links.map((link) => `<a href="${escapeHtml16(link.href)}">${escapeHtml16(link.label)}</a>`).join("")}</nav>` : "";
|
|
11236
|
+
const trend = report.trend.length ? report.trend.map((bucket) => `<tr><td>${escapeHtml16(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>';
|
|
11112
11237
|
const sessions = report.sessions.length ? report.sessions.map((session) => {
|
|
11113
11238
|
const failedMetrics = Object.entries(session.quality.metrics).filter(([, metric]) => !metric.pass).map(([, metric]) => metric.label).join(", ");
|
|
11114
|
-
return `<tr class="${session.status}"><td>${
|
|
11239
|
+
return `<tr class="${session.status}"><td>${escapeHtml16(session.sessionId)}</td><td>${escapeHtml16(session.status)}</td><td>${session.eventCount}</td><td>${session.summary.turnCount}</td><td>${session.summary.errorCount}</td><td>${escapeHtml16(formatTime(session.endedAt))}</td><td>${escapeHtml16(failedMetrics || "none")}</td></tr>`;
|
|
11115
11240
|
}).join("") : '<tr><td colspan="7">No sessions found.</td></tr>';
|
|
11116
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
11241
|
+
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{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>${escapeHtml16(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>`;
|
|
11117
11242
|
};
|
|
11118
11243
|
var renderVoiceEvalBaselineHTML = (comparison, options = {}) => {
|
|
11119
11244
|
const title = options.title ?? "AbsoluteJS Voice Eval Baseline";
|
|
11120
|
-
const links = options.links?.length ? `<nav>${options.links.map((link) => `<a href="${
|
|
11121
|
-
const reasons = comparison.reasons.length ? comparison.reasons.map((reason) => `<li>${
|
|
11122
|
-
const newFailures = comparison.newFailedSessionIds.length ? comparison.newFailedSessionIds.map((id) => `<li>${
|
|
11123
|
-
const recovered = comparison.recoveredSessionIds.length ? comparison.recoveredSessionIds.map((id) => `<li>${
|
|
11124
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
11245
|
+
const links = options.links?.length ? `<nav>${options.links.map((link) => `<a href="${escapeHtml16(link.href)}">${escapeHtml16(link.label)}</a>`).join("")}</nav>` : "";
|
|
11246
|
+
const reasons = comparison.reasons.length ? comparison.reasons.map((reason) => `<li>${escapeHtml16(reason)}</li>`).join("") : "<li>No baseline regressions detected.</li>";
|
|
11247
|
+
const newFailures = comparison.newFailedSessionIds.length ? comparison.newFailedSessionIds.map((id) => `<li>${escapeHtml16(id)}</li>`).join("") : "<li>none</li>";
|
|
11248
|
+
const recovered = comparison.recoveredSessionIds.length ? comparison.recoveredSessionIds.map((id) => `<li>${escapeHtml16(id)}</li>`).join("") : "<li>none</li>";
|
|
11249
|
+
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{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>${escapeHtml16(title)}</h1><p class="status ${comparison.status}">${comparison.status}</p><div class="grid"><article class="card"><span>Baseline pass rate</span><strong>${escapeHtml16(formatPercent(comparison.baseline.passRate))}</strong></article><article class="card"><span>Current pass rate</span><strong>${escapeHtml16(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>${escapeHtml16(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>`;
|
|
11125
11250
|
};
|
|
11126
11251
|
var renderVoiceScenarioEvalHTML = (report, options = {}) => {
|
|
11127
11252
|
const title = options.title ?? "AbsoluteJS Voice Scenario Evals";
|
|
11128
|
-
const links = options.links?.length ? `<nav>${options.links.map((link) => `<a href="${
|
|
11253
|
+
const links = options.links?.length ? `<nav>${options.links.map((link) => `<a href="${escapeHtml16(link.href)}">${escapeHtml16(link.label)}</a>`).join("")}</nav>` : "";
|
|
11129
11254
|
const scenarios = report.scenarios.length ? report.scenarios.map((scenario) => {
|
|
11130
|
-
const scenarioIssues = scenario.issues.length ? `<ul>${scenario.issues.map((issue) => `<li>${
|
|
11131
|
-
const sessions = scenario.sessions.length ? scenario.sessions.map((session) => `<tr class="${session.status}"><td>${
|
|
11132
|
-
return `<section class="scenario ${scenario.status}"><h2>${
|
|
11255
|
+
const scenarioIssues = scenario.issues.length ? `<ul>${scenario.issues.map((issue) => `<li>${escapeHtml16(issue)}</li>`).join("")}</ul>` : "";
|
|
11256
|
+
const sessions = scenario.sessions.length ? scenario.sessions.map((session) => `<tr class="${session.status}"><td>${escapeHtml16(session.sessionId)}</td><td>${escapeHtml16(session.status)}</td><td>${session.eventCount}</td><td>${escapeHtml16(session.issues.join(", ") || "none")}</td></tr>`).join("") : '<tr><td colspan="4">No matching sessions.</td></tr>';
|
|
11257
|
+
return `<section class="scenario ${scenario.status}"><h2>${escapeHtml16(scenario.label)}</h2>${scenario.description ? `<p>${escapeHtml16(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>`;
|
|
11133
11258
|
}).join("") : "<section><p>No scenarios configured.</p></section>";
|
|
11134
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
11259
|
+
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{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>${escapeHtml16(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>`;
|
|
11135
11260
|
};
|
|
11136
11261
|
var renderVoiceScenarioFixtureEvalHTML = (report, options = {}) => {
|
|
11137
11262
|
const title = options.title ?? "AbsoluteJS Voice Fixture Evals";
|
|
11138
|
-
const links = options.links?.length ? `<nav>${options.links.map((link) => `<a href="${
|
|
11263
|
+
const links = options.links?.length ? `<nav>${options.links.map((link) => `<a href="${escapeHtml16(link.href)}">${escapeHtml16(link.label)}</a>`).join("")}</nav>` : "";
|
|
11139
11264
|
const fixtures = report.fixtures.length ? report.fixtures.map((fixture) => {
|
|
11140
|
-
const scenarios = fixture.report.scenarios.map((scenario) => `<tr class="${scenario.status}"><td>${
|
|
11141
|
-
return `<section class="${fixture.status}"><h2>${
|
|
11265
|
+
const scenarios = fixture.report.scenarios.map((scenario) => `<tr class="${scenario.status}"><td>${escapeHtml16(scenario.label)}</td><td>${escapeHtml16(scenario.status)}</td><td>${scenario.matchedSessions}</td><td>${escapeHtml16([...scenario.issues, ...scenario.sessions.flatMap((session) => session.issues)].join(", ") || "none")}</td></tr>`).join("");
|
|
11266
|
+
return `<section class="${fixture.status}"><h2>${escapeHtml16(fixture.label)}</h2>${fixture.description ? `<p>${escapeHtml16(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>`;
|
|
11142
11267
|
}).join("") : "<section><p>No scenario fixtures configured.</p></section>";
|
|
11143
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
11268
|
+
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{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>${escapeHtml16(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>`;
|
|
11144
11269
|
};
|
|
11145
11270
|
var createVoiceEvalRoutes = (options) => {
|
|
11146
11271
|
const path = options.path ?? "/evals";
|
|
11147
|
-
const routes = new
|
|
11272
|
+
const routes = new Elysia13({
|
|
11148
11273
|
name: options.name ?? "absolutejs-voice-evals"
|
|
11149
11274
|
});
|
|
11150
11275
|
const getReport = () => runVoiceSessionEvals({
|
|
@@ -11278,11 +11403,11 @@ var createVoiceEvalRoutes = (options) => {
|
|
|
11278
11403
|
return routes;
|
|
11279
11404
|
};
|
|
11280
11405
|
// src/simulationSuite.ts
|
|
11281
|
-
import { Elysia as
|
|
11406
|
+
import { Elysia as Elysia16 } from "elysia";
|
|
11282
11407
|
|
|
11283
11408
|
// src/outcomeContract.ts
|
|
11284
|
-
import { Elysia as
|
|
11285
|
-
var
|
|
11409
|
+
import { Elysia as Elysia14 } from "elysia";
|
|
11410
|
+
var escapeHtml17 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
11286
11411
|
var getPayloadString = (event, key) => typeof event.payload[key] === "string" ? event.payload[key] : undefined;
|
|
11287
11412
|
var toList = async (input) => Array.isArray(input) ? input : await input?.list() ?? [];
|
|
11288
11413
|
var hydrateSessions = async (input) => {
|
|
@@ -11390,9 +11515,9 @@ var renderVoiceOutcomeContractHTML = (report, options = {}) => {
|
|
|
11390
11515
|
const contracts = report.contracts.map((contract) => `<section class="contract ${contract.pass ? "pass" : "fail"}">
|
|
11391
11516
|
<div class="contract-header">
|
|
11392
11517
|
<div>
|
|
11393
|
-
<p class="eyebrow">${
|
|
11394
|
-
<h2>${
|
|
11395
|
-
${contract.description ? `<p>${
|
|
11518
|
+
<p class="eyebrow">${escapeHtml17(contract.contractId)}</p>
|
|
11519
|
+
<h2>${escapeHtml17(contract.label ?? contract.contractId)}</h2>
|
|
11520
|
+
${contract.description ? `<p>${escapeHtml17(contract.description)}</p>` : ""}
|
|
11396
11521
|
</div>
|
|
11397
11522
|
<strong>${contract.pass ? "pass" : "fail"}</strong>
|
|
11398
11523
|
</div>
|
|
@@ -11403,9 +11528,9 @@ var renderVoiceOutcomeContractHTML = (report, options = {}) => {
|
|
|
11403
11528
|
<span>handoffs ${String(contract.matched.handoffs)}</span>
|
|
11404
11529
|
<span>events ${String(contract.matched.integrationEvents)}</span>
|
|
11405
11530
|
</div>
|
|
11406
|
-
${contract.issues.length ? `<ul>${contract.issues.map((issue) => `<li>${
|
|
11531
|
+
${contract.issues.length ? `<ul>${contract.issues.map((issue) => `<li>${escapeHtml17(issue.message)}</li>`).join("")}</ul>` : ""}
|
|
11407
11532
|
</section>`).join("");
|
|
11408
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
11533
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml17(title)}</title><style>body{background:#101316;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}.hero,.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>${escapeHtml17(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>`;
|
|
11409
11534
|
};
|
|
11410
11535
|
var createVoiceOutcomeContractJSONHandler = (options) => async () => runVoiceOutcomeContractSuite(options);
|
|
11411
11536
|
var createVoiceOutcomeContractHTMLHandler = (options) => async () => {
|
|
@@ -11421,7 +11546,7 @@ var createVoiceOutcomeContractHTMLHandler = (options) => async () => {
|
|
|
11421
11546
|
var createVoiceOutcomeContractRoutes = (options) => {
|
|
11422
11547
|
const path = options.path ?? "/api/outcome-contracts";
|
|
11423
11548
|
const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
|
|
11424
|
-
const routes = new
|
|
11549
|
+
const routes = new Elysia14({
|
|
11425
11550
|
name: options.name ?? "absolutejs-voice-outcome-contracts"
|
|
11426
11551
|
}).get(path, createVoiceOutcomeContractJSONHandler(options));
|
|
11427
11552
|
if (htmlPath) {
|
|
@@ -11431,7 +11556,7 @@ var createVoiceOutcomeContractRoutes = (options) => {
|
|
|
11431
11556
|
};
|
|
11432
11557
|
|
|
11433
11558
|
// src/toolContract.ts
|
|
11434
|
-
import { Elysia as
|
|
11559
|
+
import { Elysia as Elysia15 } from "elysia";
|
|
11435
11560
|
|
|
11436
11561
|
// src/toolRuntime.ts
|
|
11437
11562
|
var toErrorMessage4 = (error) => error instanceof Error ? error.message : String(error);
|
|
@@ -11640,7 +11765,7 @@ var createDefaultTurn = (caseId) => ({
|
|
|
11640
11765
|
});
|
|
11641
11766
|
var defaultApi = {};
|
|
11642
11767
|
var sameJSON = (left, right) => JSON.stringify(left) === JSON.stringify(right);
|
|
11643
|
-
var
|
|
11768
|
+
var escapeHtml18 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
11644
11769
|
var evaluateExpectation = (input) => {
|
|
11645
11770
|
const issues = [];
|
|
11646
11771
|
const expect = input.expect;
|
|
@@ -11806,19 +11931,19 @@ var renderVoiceToolContractHTML = (report, options = {}) => {
|
|
|
11806
11931
|
const title = options.title ?? "Voice Tool Contracts";
|
|
11807
11932
|
const contracts = report.contracts.map((contract) => {
|
|
11808
11933
|
const cases = contract.cases.map((testCase) => `<tr>
|
|
11809
|
-
<td>${
|
|
11934
|
+
<td>${escapeHtml18(testCase.label ?? testCase.caseId)}</td>
|
|
11810
11935
|
<td class="${testCase.pass ? "pass" : "fail"}">${testCase.pass ? "pass" : "fail"}</td>
|
|
11811
|
-
<td>${
|
|
11936
|
+
<td>${escapeHtml18(testCase.status)}</td>
|
|
11812
11937
|
<td>${String(testCase.attempts)}</td>
|
|
11813
11938
|
<td>${String(testCase.elapsedMs)}ms</td>
|
|
11814
11939
|
<td>${testCase.timedOut ? "yes" : "no"}</td>
|
|
11815
|
-
<td>${
|
|
11940
|
+
<td>${escapeHtml18(testCase.issues.map((issue) => issue.message).join(" ") || testCase.error || "")}</td>
|
|
11816
11941
|
</tr>`).join("");
|
|
11817
11942
|
return `<section class="contract ${contract.pass ? "pass" : "fail"}">
|
|
11818
11943
|
<div class="contract-header">
|
|
11819
11944
|
<div>
|
|
11820
|
-
<p class="eyebrow">${
|
|
11821
|
-
<h2>${
|
|
11945
|
+
<p class="eyebrow">${escapeHtml18(contract.toolName)}</p>
|
|
11946
|
+
<h2>${escapeHtml18(contract.label ?? contract.contractId)}</h2>
|
|
11822
11947
|
</div>
|
|
11823
11948
|
<strong class="${contract.pass ? "pass" : "fail"}">${contract.pass ? "Passing" : "Failing"}</strong>
|
|
11824
11949
|
</div>
|
|
@@ -11828,7 +11953,7 @@ var renderVoiceToolContractHTML = (report, options = {}) => {
|
|
|
11828
11953
|
</table>
|
|
11829
11954
|
</section>`;
|
|
11830
11955
|
}).join("");
|
|
11831
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
11956
|
+
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>`;
|
|
11832
11957
|
};
|
|
11833
11958
|
var createVoiceToolContractJSONHandler = (options) => () => runVoiceToolContractSuite(options);
|
|
11834
11959
|
var createVoiceToolContractHTMLHandler = (options) => async () => {
|
|
@@ -11845,7 +11970,7 @@ var createVoiceToolContractHTMLHandler = (options) => async () => {
|
|
|
11845
11970
|
var createVoiceToolContractRoutes = (options) => {
|
|
11846
11971
|
const path = options.path ?? "/api/tool-contracts";
|
|
11847
11972
|
const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
|
|
11848
|
-
const routes = new
|
|
11973
|
+
const routes = new Elysia15({
|
|
11849
11974
|
name: options.name ?? "absolutejs-voice-tool-contracts"
|
|
11850
11975
|
}).get(path, createVoiceToolContractJSONHandler(options));
|
|
11851
11976
|
if (htmlPath) {
|
|
@@ -11855,7 +11980,7 @@ var createVoiceToolContractRoutes = (options) => {
|
|
|
11855
11980
|
};
|
|
11856
11981
|
|
|
11857
11982
|
// src/simulationSuite.ts
|
|
11858
|
-
var
|
|
11983
|
+
var escapeHtml19 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
11859
11984
|
var summarizeSection = (report) => ({
|
|
11860
11985
|
failed: report.failed,
|
|
11861
11986
|
passed: report.passed,
|
|
@@ -11991,20 +12116,20 @@ var renderSection = (label, summary) => {
|
|
|
11991
12116
|
if (!summary) {
|
|
11992
12117
|
return "";
|
|
11993
12118
|
}
|
|
11994
|
-
return `<article class="${
|
|
12119
|
+
return `<article class="${escapeHtml19(summary.status)}"><span>${escapeHtml19(label)}</span><strong>${escapeHtml19(summary.status)}</strong><p>${summary.passed}/${summary.total} passed, ${summary.failed} failed.</p></article>`;
|
|
11995
12120
|
};
|
|
11996
12121
|
var renderAction = (action) => {
|
|
11997
|
-
const content = `<strong>${
|
|
11998
|
-
return action.href ? `<a class="action" href="${
|
|
12122
|
+
const content = `<strong>${escapeHtml19(action.label)}</strong><p>${escapeHtml19(action.description)}</p><span>${escapeHtml19(action.section)} / ${escapeHtml19(action.severity)}</span>`;
|
|
12123
|
+
return action.href ? `<a class="action" href="${escapeHtml19(action.href)}">${content}</a>` : `<article class="action">${content}</article>`;
|
|
11999
12124
|
};
|
|
12000
12125
|
var renderVoiceSimulationSuiteHTML = (report, options = {}) => {
|
|
12001
12126
|
const title = options.title ?? "Voice Simulation Suite";
|
|
12002
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
12127
|
+
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:#10151c;color:#f8f3e7;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1080px;padding:32px}.hero{background:linear-gradient(135deg,rgba(34,197,94,.18),rgba(59,130,246,.12));border:1px solid #283544;border-radius:28px;margin-bottom:18px;padding:28px}.eyebrow{color:#93c5fd;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,5rem);line-height:.9;margin:.2rem 0 1rem}.badge{border:1px solid #3f3f46;border-radius:999px;display:inline-flex;padding:8px 12px}.pass{color:#86efac}.fail{color:#fca5a5}.grid,.actions{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));margin:18px 0}.grid article,.action{background:#151d27;border:1px solid #283544;border-radius:18px;color:inherit;padding:16px;text-decoration:none}.grid span,.action span{color:#aab5c0}.grid strong{display:block;font-size:2rem;margin:.25rem 0;text-transform:uppercase}.action strong{display:block;color:#f8f3e7;margin-bottom:.35rem}.action p{color:#d8dee6;margin:.3rem 0 .6rem}pre{background:#151d27;border:1px solid #283544;border-radius:18px;overflow:auto;padding:16px}</style></head><body><main><section class="hero"><p class="eyebrow">Pre-production proof</p><h1>${escapeHtml19(title)}</h1><p>One report for session quality, scenario evals, fixture simulations, tool contracts, and outcome contracts.</p><p class="badge ${escapeHtml19(report.status)}">Status: ${escapeHtml19(report.status)}</p><section class="grid">${renderSection("Sessions", report.summary.sessions)}${renderSection("Scenarios", report.summary.scenarios)}${renderSection("Fixtures", report.summary.fixtures)}${renderSection("Tools", report.summary.tools)}${renderSection("Outcomes", report.summary.outcomes)}</section></section><h2>Actions</h2><section class="actions">${report.actions.length > 0 ? report.actions.map(renderAction).join("") : '<article class="action"><strong>No action required</strong><p>All enabled simulation sections are passing.</p></article>'}</section><pre>${escapeHtml19(JSON.stringify({ summary: report.summary, actions: report.actions }, null, 2))}</pre></main></body></html>`;
|
|
12003
12128
|
};
|
|
12004
12129
|
var createVoiceSimulationSuiteRoutes = (options) => {
|
|
12005
12130
|
const path = options.path ?? "/api/voice/simulations";
|
|
12006
12131
|
const htmlPath = options.htmlPath === undefined ? "/voice/simulations" : options.htmlPath;
|
|
12007
|
-
const app = new
|
|
12132
|
+
const app = new Elysia16({
|
|
12008
12133
|
name: options.name ?? "absolutejs-voice-simulation-suite"
|
|
12009
12134
|
}).get(path, () => runVoiceSimulationSuite(options));
|
|
12010
12135
|
if (htmlPath) {
|
|
@@ -12316,9 +12441,9 @@ var createVoiceWorkflowContractHandler = (input) => {
|
|
|
12316
12441
|
};
|
|
12317
12442
|
};
|
|
12318
12443
|
// src/sessionReplay.ts
|
|
12319
|
-
import { Elysia as
|
|
12444
|
+
import { Elysia as Elysia17 } from "elysia";
|
|
12320
12445
|
var getString9 = (value) => typeof value === "string" ? value : undefined;
|
|
12321
|
-
var
|
|
12446
|
+
var escapeHtml20 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
12322
12447
|
var increment4 = (record, key) => {
|
|
12323
12448
|
record[key] = (record[key] ?? 0) + 1;
|
|
12324
12449
|
};
|
|
@@ -12477,10 +12602,10 @@ var summarizeVoiceSessions = async (options = {}) => {
|
|
|
12477
12602
|
var renderVoiceSessionsHTML = (sessions) => sessions.length === 0 ? '<p class="voice-sessions-empty">No voice sessions found.</p>' : [
|
|
12478
12603
|
'<div class="voice-sessions-list">',
|
|
12479
12604
|
...sessions.map((session) => [
|
|
12480
|
-
`<article class="voice-session-card ${
|
|
12605
|
+
`<article class="voice-session-card ${escapeHtml20(session.status)}">`,
|
|
12481
12606
|
'<div class="voice-session-card-header">',
|
|
12482
|
-
`<strong>${
|
|
12483
|
-
`<span>${
|
|
12607
|
+
`<strong>${escapeHtml20(session.sessionId)}</strong>`,
|
|
12608
|
+
`<span>${escapeHtml20(session.status)}</span>`,
|
|
12484
12609
|
"</div>",
|
|
12485
12610
|
"<dl>",
|
|
12486
12611
|
`<div><dt>Events</dt><dd>${String(session.eventCount)}</dd></div>`,
|
|
@@ -12488,9 +12613,9 @@ var renderVoiceSessionsHTML = (sessions) => sessions.length === 0 ? '<p class="v
|
|
|
12488
12613
|
`<div><dt>Transcripts</dt><dd>${String(session.transcriptCount)}</dd></div>`,
|
|
12489
12614
|
`<div><dt>Errors</dt><dd>${String(session.errorCount)}</dd></div>`,
|
|
12490
12615
|
"</dl>",
|
|
12491
|
-
session.latestOutcome ? `<p>Outcome: ${
|
|
12492
|
-
session.providers.length ? `<p>Providers: ${session.providers.map(
|
|
12493
|
-
session.replayHref ? `<p><a href="${
|
|
12616
|
+
session.latestOutcome ? `<p>Outcome: ${escapeHtml20(session.latestOutcome)}</p>` : "",
|
|
12617
|
+
session.providers.length ? `<p>Providers: ${session.providers.map(escapeHtml20).join(", ")}</p>` : "",
|
|
12618
|
+
session.replayHref ? `<p><a href="${escapeHtml20(session.replayHref)}">Open replay</a></p>` : "",
|
|
12494
12619
|
"</article>"
|
|
12495
12620
|
].join("")),
|
|
12496
12621
|
"</div>"
|
|
@@ -12521,7 +12646,7 @@ var createVoiceSessionsHTMLHandler = (options = {}) => async ({ query }) => {
|
|
|
12521
12646
|
var createVoiceSessionListRoutes = (options = {}) => {
|
|
12522
12647
|
const path = options.path ?? "/api/voice-sessions";
|
|
12523
12648
|
const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
|
|
12524
|
-
const routes = new
|
|
12649
|
+
const routes = new Elysia17({
|
|
12525
12650
|
name: options.name ?? "absolutejs-voice-session-list"
|
|
12526
12651
|
}).get(path, createVoiceSessionsJSONHandler(options));
|
|
12527
12652
|
if (htmlPath) {
|
|
@@ -12549,7 +12674,7 @@ var createVoiceSessionReplayHTMLHandler = (options) => async ({ params }) => {
|
|
|
12549
12674
|
var createVoiceSessionReplayRoutes = (options) => {
|
|
12550
12675
|
const path = options.path ?? "/api/voice-sessions/:sessionId/replay";
|
|
12551
12676
|
const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
|
|
12552
|
-
const routes = new
|
|
12677
|
+
const routes = new Elysia17({
|
|
12553
12678
|
name: options.name ?? "absolutejs-voice-session-replay"
|
|
12554
12679
|
}).get(path, createVoiceSessionReplayJSONHandler(options));
|
|
12555
12680
|
if (htmlPath) {
|
|
@@ -12716,10 +12841,10 @@ var assertVoiceAgentSquadContract = async (options) => {
|
|
|
12716
12841
|
return report;
|
|
12717
12842
|
};
|
|
12718
12843
|
// src/turnLatency.ts
|
|
12719
|
-
import { Elysia as
|
|
12844
|
+
import { Elysia as Elysia18 } from "elysia";
|
|
12720
12845
|
var DEFAULT_WARN_AFTER_MS = 1800;
|
|
12721
12846
|
var DEFAULT_FAIL_AFTER_MS = 3200;
|
|
12722
|
-
var
|
|
12847
|
+
var escapeHtml21 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
12723
12848
|
var firstNumber = (values) => values.filter((value) => typeof value === "number").sort((left, right) => left - right)[0];
|
|
12724
12849
|
var getString10 = (value) => typeof value === "string" && value.trim() ? value : undefined;
|
|
12725
12850
|
var createTraceStageIndex = (events) => {
|
|
@@ -12833,11 +12958,11 @@ var summarizeVoiceTurnLatency = async (options) => {
|
|
|
12833
12958
|
var formatMs = (value) => typeof value === "number" ? `${Math.round(value)}ms` : "n/a";
|
|
12834
12959
|
var renderVoiceTurnLatencyHTML = (report, options = {}) => {
|
|
12835
12960
|
const title = options.title ?? "Voice Turn Latency";
|
|
12836
|
-
const turns = report.turns.map((turn) => `<article class="turn ${
|
|
12837
|
-
<header><div><p class="eyebrow">${
|
|
12838
|
-
<dl>${turn.stages.map((stage) => `<div><dt>${
|
|
12961
|
+
const turns = report.turns.map((turn) => `<article class="turn ${escapeHtml21(turn.status)}">
|
|
12962
|
+
<header><div><p class="eyebrow">${escapeHtml21(turn.sessionId)} \xB7 ${escapeHtml21(turn.turnId)}</p><h2>${escapeHtml21(turn.text || "Empty turn")}</h2></div><strong>${escapeHtml21(turn.status)}</strong></header>
|
|
12963
|
+
<dl>${turn.stages.map((stage) => `<div><dt>${escapeHtml21(stage.label)}</dt><dd>${escapeHtml21(formatMs(stage.valueMs))}</dd></div>`).join("")}</dl>
|
|
12839
12964
|
</article>`).join("");
|
|
12840
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
12965
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml21(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(94,234,212,.16),rgba(251,191,36,.1))}.eyebrow{color:#5eead4;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,.empty{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{font-weight:900;margin:0}@media(max-width:800px){main{padding:18px}.turn header{display:block}}</style></head><body><main><section class="hero"><p class="eyebrow">End-to-end responsiveness</p><h1>${escapeHtml21(title)}</h1><div class="summary"><span class="pill ${escapeHtml21(report.status)}">${escapeHtml21(report.status)}</span><span class="pill">${String(report.total)} turns</span><span class="pill">avg ${escapeHtml21(formatMs(report.averageTotalMs))}</span><span class="pill">${String(report.warnings)} warnings</span><span class="pill">${String(report.failed)} failed</span></div></section>${turns || '<section class="turn"><p>No committed turns found.</p></section>'}</main></body></html>`;
|
|
12841
12966
|
};
|
|
12842
12967
|
var createVoiceTurnLatencyJSONHandler = (options) => async () => summarizeVoiceTurnLatency(options);
|
|
12843
12968
|
var createVoiceTurnLatencyHTMLHandler = (options) => async () => {
|
|
@@ -12854,7 +12979,7 @@ var createVoiceTurnLatencyHTMLHandler = (options) => async () => {
|
|
|
12854
12979
|
var createVoiceTurnLatencyRoutes = (options) => {
|
|
12855
12980
|
const path = options.path ?? "/api/turn-latency";
|
|
12856
12981
|
const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
|
|
12857
|
-
const routes = new
|
|
12982
|
+
const routes = new Elysia18({
|
|
12858
12983
|
name: options.name ?? "absolutejs-voice-turn-latency"
|
|
12859
12984
|
}).get(path, createVoiceTurnLatencyJSONHandler(options));
|
|
12860
12985
|
if (htmlPath) {
|
|
@@ -12863,8 +12988,8 @@ var createVoiceTurnLatencyRoutes = (options) => {
|
|
|
12863
12988
|
return routes;
|
|
12864
12989
|
};
|
|
12865
12990
|
// src/liveLatency.ts
|
|
12866
|
-
import { Elysia as
|
|
12867
|
-
var
|
|
12991
|
+
import { Elysia as Elysia19 } from "elysia";
|
|
12992
|
+
var escapeHtml22 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
12868
12993
|
var percentile = (values, percentileValue) => {
|
|
12869
12994
|
if (values.length === 0) {
|
|
12870
12995
|
return;
|
|
@@ -12912,13 +13037,13 @@ var summarizeVoiceLiveLatency = async (options) => {
|
|
|
12912
13037
|
var formatMs2 = (value) => typeof value === "number" ? `${Math.round(value)}ms` : "n/a";
|
|
12913
13038
|
var renderVoiceLiveLatencyHTML = (report, options = {}) => {
|
|
12914
13039
|
const title = options.title ?? "Voice Live Latency";
|
|
12915
|
-
const rows = report.recent.map((sample) => `<tr><td>${
|
|
12916
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
13040
|
+
const rows = report.recent.map((sample) => `<tr><td>${escapeHtml22(sample.sessionId)}</td><td>${escapeHtml22(formatMs2(sample.latencyMs))}</td><td>${escapeHtml22(sample.status ?? "unknown")}</td><td>${escapeHtml22(new Date(sample.at).toLocaleString())}</td></tr>`).join("");
|
|
13041
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml22(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(94,234,212,.16),rgba(245,158,11,.1));border:1px solid #26313d;border-radius:28px;margin-bottom:18px;padding:28px}.eyebrow{color:#5eead4;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{border:1px solid #3f3f46;border-radius:999px;display:inline-flex;padding:8px 12px}.pass{color:#86efac}.warn,.empty{color:#fbbf24}.fail{color:#fca5a5}.metrics{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));margin:18px 0}.metrics article,table{background:#141922;border:1px solid #26313d;border-radius:18px}.metrics article{padding:16px}.metrics span{color:#a8b0b8}.metrics strong{display:block;font-size:2rem;margin-top:.25rem}table{border-collapse:collapse;overflow:hidden;width:100%}td,th{border-bottom:1px solid #26313d;padding:12px;text-align:left}@media(max-width:760px){main{padding:20px}}</style></head><body><main><section class="hero"><p class="eyebrow">Browser proof</p><h1>${escapeHtml22(title)}</h1><p>Recent real browser speech-to-assistant response measurements from persisted <code>client.live_latency</code> traces.</p><p class="status ${escapeHtml22(report.status)}">Status: ${escapeHtml22(report.status)}</p><section class="metrics"><article><span>p50</span><strong>${escapeHtml22(formatMs2(report.p50LatencyMs))}</strong></article><article><span>p95</span><strong>${escapeHtml22(formatMs2(report.p95LatencyMs))}</strong></article><article><span>Average</span><strong>${escapeHtml22(formatMs2(report.averageLatencyMs))}</strong></article><article><span>Samples</span><strong>${String(report.total)}</strong></article></section></section><table><thead><tr><th>Session</th><th>Latency</th><th>Status</th><th>Measured</th></tr></thead><tbody>${rows || '<tr><td colspan="4">No live latency samples yet.</td></tr>'}</tbody></table></main></body></html>`;
|
|
12917
13042
|
};
|
|
12918
13043
|
var createVoiceLiveLatencyRoutes = (options) => {
|
|
12919
13044
|
const path = options.path ?? "/api/live-latency";
|
|
12920
13045
|
const htmlPath = options.htmlPath === undefined ? "/live-latency" : options.htmlPath;
|
|
12921
|
-
const routes = new
|
|
13046
|
+
const routes = new Elysia19({
|
|
12922
13047
|
name: options.name ?? "absolutejs-voice-live-latency"
|
|
12923
13048
|
}).get(path, () => summarizeVoiceLiveLatency(options));
|
|
12924
13049
|
if (htmlPath) {
|
|
@@ -12935,9 +13060,9 @@ var createVoiceLiveLatencyRoutes = (options) => {
|
|
|
12935
13060
|
return routes;
|
|
12936
13061
|
};
|
|
12937
13062
|
// src/turnQuality.ts
|
|
12938
|
-
import { Elysia as
|
|
13063
|
+
import { Elysia as Elysia20 } from "elysia";
|
|
12939
13064
|
var DEFAULT_CONFIDENCE_WARN_THRESHOLD = 0.72;
|
|
12940
|
-
var
|
|
13065
|
+
var escapeHtml23 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
12941
13066
|
var getTurnLatencyMs = (turn) => {
|
|
12942
13067
|
const firstTranscriptAt = turn.transcripts.map((transcript) => transcript.endedAtMs ?? transcript.startedAtMs).filter((value) => typeof value === "number").sort((left, right) => left - right)[0];
|
|
12943
13068
|
if (firstTranscriptAt === undefined) {
|
|
@@ -13008,24 +13133,24 @@ var summarizeVoiceTurnQuality = async (options) => {
|
|
|
13008
13133
|
};
|
|
13009
13134
|
var renderVoiceTurnQualityHTML = (report, options = {}) => {
|
|
13010
13135
|
const title = options.title ?? "Voice Turn Quality";
|
|
13011
|
-
const turns = report.turns.map((turn) => `<article class="turn ${
|
|
13136
|
+
const turns = report.turns.map((turn) => `<article class="turn ${escapeHtml23(turn.status)}">
|
|
13012
13137
|
<div class="turn-header">
|
|
13013
13138
|
<div>
|
|
13014
|
-
<p class="eyebrow">${
|
|
13015
|
-
<h2>${
|
|
13139
|
+
<p class="eyebrow">${escapeHtml23(turn.sessionId)} \xB7 ${escapeHtml23(turn.turnId)}</p>
|
|
13140
|
+
<h2>${escapeHtml23(turn.text || "Empty turn")}</h2>
|
|
13016
13141
|
</div>
|
|
13017
|
-
<strong>${
|
|
13142
|
+
<strong>${escapeHtml23(turn.status)}</strong>
|
|
13018
13143
|
</div>
|
|
13019
13144
|
<dl>
|
|
13020
|
-
<div><dt>Source</dt><dd>${
|
|
13145
|
+
<div><dt>Source</dt><dd>${escapeHtml23(turn.source ?? "unknown")}</dd></div>
|
|
13021
13146
|
<div><dt>Confidence</dt><dd>${turn.averageConfidence === undefined ? "n/a" : `${Math.round(turn.averageConfidence * 100)}%`}</dd></div>
|
|
13022
|
-
<div><dt>Fallback</dt><dd>${turn.fallbackUsed ? `yes (${
|
|
13023
|
-
<div><dt>Correction</dt><dd>${turn.correctionChanged ? `changed${turn.correctionProvider ? ` by ${
|
|
13147
|
+
<div><dt>Fallback</dt><dd>${turn.fallbackUsed ? `yes (${escapeHtml23(turn.fallbackSelectionReason ?? "selected")})` : "no"}</dd></div>
|
|
13148
|
+
<div><dt>Correction</dt><dd>${turn.correctionChanged ? `changed${turn.correctionProvider ? ` by ${escapeHtml23(turn.correctionProvider)}` : ""}` : "none"}</dd></div>
|
|
13024
13149
|
<div><dt>Transcripts</dt><dd>${String(turn.selectedTranscriptCount)} selected \xB7 ${String(turn.finalTranscriptCount)} final \xB7 ${String(turn.partialTranscriptCount)} partial</dd></div>
|
|
13025
13150
|
<div><dt>Cost</dt><dd>${turn.costUnits === undefined ? "n/a" : String(turn.costUnits)}</dd></div>
|
|
13026
13151
|
</dl>
|
|
13027
13152
|
</article>`).join("");
|
|
13028
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
13153
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml23(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>${escapeHtml23(title)}</h1><div class="summary"><span class="pill ${escapeHtml23(report.status)}">${escapeHtml23(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>`;
|
|
13029
13154
|
};
|
|
13030
13155
|
var createVoiceTurnQualityJSONHandler = (options) => async () => summarizeVoiceTurnQuality(options);
|
|
13031
13156
|
var createVoiceTurnQualityHTMLHandler = (options) => async () => {
|
|
@@ -13042,7 +13167,7 @@ var createVoiceTurnQualityHTMLHandler = (options) => async () => {
|
|
|
13042
13167
|
var createVoiceTurnQualityRoutes = (options) => {
|
|
13043
13168
|
const path = options.path ?? "/api/turn-quality";
|
|
13044
13169
|
const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
|
|
13045
|
-
const routes = new
|
|
13170
|
+
const routes = new Elysia20({
|
|
13046
13171
|
name: options.name ?? "absolutejs-voice-turn-quality"
|
|
13047
13172
|
}).get(path, createVoiceTurnQualityJSONHandler(options));
|
|
13048
13173
|
if (htmlPath) {
|
|
@@ -13051,7 +13176,7 @@ var createVoiceTurnQualityRoutes = (options) => {
|
|
|
13051
13176
|
return routes;
|
|
13052
13177
|
};
|
|
13053
13178
|
// src/telephonyOutcome.ts
|
|
13054
|
-
import { Elysia as
|
|
13179
|
+
import { Elysia as Elysia21 } from "elysia";
|
|
13055
13180
|
var DEFAULT_COMPLETED_STATUSES = [
|
|
13056
13181
|
"answered",
|
|
13057
13182
|
"completed",
|
|
@@ -13701,7 +13826,7 @@ var createVoiceTelephonyWebhookHandler = (options = {}) => async (input) => {
|
|
|
13701
13826
|
var createVoiceTelephonyWebhookRoutes = (options = {}) => {
|
|
13702
13827
|
const path = options.path ?? "/api/voice/telephony/webhook";
|
|
13703
13828
|
const handler = createVoiceTelephonyWebhookHandler(options);
|
|
13704
|
-
return new
|
|
13829
|
+
return new Elysia21({
|
|
13705
13830
|
name: options.name ?? "absolutejs-voice-telephony-webhooks"
|
|
13706
13831
|
}).post(path, async ({ query, request }) => {
|
|
13707
13832
|
try {
|
|
@@ -13722,11 +13847,11 @@ var createVoiceTelephonyWebhookRoutes = (options = {}) => {
|
|
|
13722
13847
|
});
|
|
13723
13848
|
};
|
|
13724
13849
|
// src/phoneAgent.ts
|
|
13725
|
-
import { Elysia as
|
|
13850
|
+
import { Elysia as Elysia27 } from "elysia";
|
|
13726
13851
|
|
|
13727
13852
|
// src/telephony/plivo.ts
|
|
13728
13853
|
import { Buffer as Buffer5 } from "buffer";
|
|
13729
|
-
import { Elysia as
|
|
13854
|
+
import { Elysia as Elysia23 } from "elysia";
|
|
13730
13855
|
|
|
13731
13856
|
// src/telephony/contract.ts
|
|
13732
13857
|
var DEFAULT_REQUIREMENTS = [
|
|
@@ -13810,7 +13935,7 @@ var evaluateVoiceTelephonyContract = (input) => {
|
|
|
13810
13935
|
|
|
13811
13936
|
// src/telephony/twilio.ts
|
|
13812
13937
|
import { Buffer as Buffer4 } from "buffer";
|
|
13813
|
-
import { Elysia as
|
|
13938
|
+
import { Elysia as Elysia22 } from "elysia";
|
|
13814
13939
|
var TWILIO_MULAW_SAMPLE_RATE = 8000;
|
|
13815
13940
|
var VOICE_PCM_SAMPLE_RATE = 16000;
|
|
13816
13941
|
var escapeXml2 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
@@ -13840,7 +13965,7 @@ var resolveTwilioStreamParameters = async (parameters, input) => {
|
|
|
13840
13965
|
return parameters;
|
|
13841
13966
|
};
|
|
13842
13967
|
var joinUrlPath2 = (origin, path) => `${origin.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
|
|
13843
|
-
var
|
|
13968
|
+
var escapeHtml24 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
13844
13969
|
var getWebhookVerificationUrl = (webhook, input) => {
|
|
13845
13970
|
if (!webhook?.verificationUrl) {
|
|
13846
13971
|
return;
|
|
@@ -13883,23 +14008,23 @@ var buildTwilioVoiceSetupStatus = async (options, input) => {
|
|
|
13883
14008
|
};
|
|
13884
14009
|
var renderTwilioVoiceSetupHTML = (status, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
|
|
13885
14010
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Twilio setup</p>
|
|
13886
|
-
<h1>${
|
|
14011
|
+
<h1>${escapeHtml24(title)}</h1>
|
|
13887
14012
|
<p><strong>Status:</strong> ${status.ready ? "Ready" : "Needs attention"}</p>
|
|
13888
14013
|
<section>
|
|
13889
14014
|
<h2>URLs</h2>
|
|
13890
14015
|
<ul>
|
|
13891
|
-
<li><strong>TwiML:</strong> <code>${
|
|
13892
|
-
<li><strong>Media stream:</strong> <code>${
|
|
13893
|
-
<li><strong>Status webhook:</strong> <code>${
|
|
14016
|
+
<li><strong>TwiML:</strong> <code>${escapeHtml24(status.urls.twiml)}</code></li>
|
|
14017
|
+
<li><strong>Media stream:</strong> <code>${escapeHtml24(status.urls.stream)}</code></li>
|
|
14018
|
+
<li><strong>Status webhook:</strong> <code>${escapeHtml24(status.urls.webhook)}</code></li>
|
|
13894
14019
|
</ul>
|
|
13895
14020
|
</section>
|
|
13896
14021
|
<section>
|
|
13897
14022
|
<h2>Signing</h2>
|
|
13898
14023
|
<p>Mode: <code>${status.signing.mode}</code></p>
|
|
13899
|
-
${status.signing.verificationUrl ? `<p>Verification URL: <code>${
|
|
14024
|
+
${status.signing.verificationUrl ? `<p>Verification URL: <code>${escapeHtml24(status.signing.verificationUrl)}</code></p>` : ""}
|
|
13900
14025
|
</section>
|
|
13901
|
-
${status.missing.length ? `<section><h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${
|
|
13902
|
-
${status.warnings.length ? `<section><h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${
|
|
14026
|
+
${status.missing.length ? `<section><h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml24(name)}</code></li>`).join("")}</ul></section>` : ""}
|
|
14027
|
+
${status.warnings.length ? `<section><h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml24(warning)}</li>`).join("")}</ul></section>` : ""}
|
|
13903
14028
|
</main>`;
|
|
13904
14029
|
var extractTwilioStreamUrl = (twiml) => twiml.match(/<Stream\b[^>]*\surl="([^"]+)"/i)?.[1]?.replaceAll("&", "&");
|
|
13905
14030
|
var createSmokeCheck = (name, status, message, details) => ({
|
|
@@ -13910,20 +14035,20 @@ var createSmokeCheck = (name, status, message, details) => ({
|
|
|
13910
14035
|
});
|
|
13911
14036
|
var renderTwilioVoiceSmokeHTML = (report, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
|
|
13912
14037
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Twilio smoke test</p>
|
|
13913
|
-
<h1>${
|
|
14038
|
+
<h1>${escapeHtml24(title)}</h1>
|
|
13914
14039
|
<p><strong>Status:</strong> ${report.pass ? "Pass" : "Fail"}</p>
|
|
13915
14040
|
<section>
|
|
13916
14041
|
<h2>Checks</h2>
|
|
13917
14042
|
<ul>
|
|
13918
|
-
${report.checks.map((check) => `<li><strong>${
|
|
14043
|
+
${report.checks.map((check) => `<li><strong>${escapeHtml24(check.name)}</strong>: ${escapeHtml24(check.status)}${check.message ? ` - ${escapeHtml24(check.message)}` : ""}</li>`).join("")}
|
|
13919
14044
|
</ul>
|
|
13920
14045
|
</section>
|
|
13921
14046
|
<section>
|
|
13922
14047
|
<h2>Observed URLs</h2>
|
|
13923
14048
|
<ul>
|
|
13924
|
-
<li><strong>TwiML:</strong> <code>${
|
|
13925
|
-
<li><strong>Stream:</strong> <code>${
|
|
13926
|
-
<li><strong>Webhook:</strong> <code>${
|
|
14049
|
+
<li><strong>TwiML:</strong> <code>${escapeHtml24(report.setup.urls.twiml)}</code></li>
|
|
14050
|
+
<li><strong>Stream:</strong> <code>${escapeHtml24(report.twiml?.streamUrl ?? report.setup.urls.stream)}</code></li>
|
|
14051
|
+
<li><strong>Webhook:</strong> <code>${escapeHtml24(report.setup.urls.webhook)}</code></li>
|
|
13927
14052
|
</ul>
|
|
13928
14053
|
</section>
|
|
13929
14054
|
</main>`;
|
|
@@ -14383,7 +14508,7 @@ var createTwilioVoiceRoutes = (options) => {
|
|
|
14383
14508
|
const smokePath = options.smoke?.path === false ? false : options.smoke?.path ?? "/api/voice/twilio/smoke";
|
|
14384
14509
|
const bridges = new WeakMap;
|
|
14385
14510
|
const webhookPolicy = options.webhook?.policy ?? options.outcomePolicy ?? createVoiceTelephonyOutcomePolicy();
|
|
14386
|
-
const app = new
|
|
14511
|
+
const app = new Elysia22({
|
|
14387
14512
|
name: options.name ?? "absolutejs-voice-twilio"
|
|
14388
14513
|
}).get(twimlPath, async ({ query, request }) => {
|
|
14389
14514
|
const streamUrl = await resolveTwilioStreamUrl(options, {
|
|
@@ -14520,7 +14645,7 @@ var createTwilioVoiceRoutes = (options) => {
|
|
|
14520
14645
|
|
|
14521
14646
|
// src/telephony/plivo.ts
|
|
14522
14647
|
var escapeXml3 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
14523
|
-
var
|
|
14648
|
+
var escapeHtml25 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
14524
14649
|
var joinUrlPath3 = (origin, path) => `${origin.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
|
|
14525
14650
|
var resolveRequestOrigin2 = (request) => {
|
|
14526
14651
|
const url = new URL(request.url);
|
|
@@ -14771,21 +14896,21 @@ var buildPlivoVoiceSetupStatus = async (options, input) => {
|
|
|
14771
14896
|
};
|
|
14772
14897
|
var renderPlivoSetupHTML = (status, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
|
|
14773
14898
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Plivo setup</p>
|
|
14774
|
-
<h1>${
|
|
14899
|
+
<h1>${escapeHtml25(title)}</h1>
|
|
14775
14900
|
<p><strong>Status:</strong> ${status.ready ? "Ready" : "Needs attention"}</p>
|
|
14776
14901
|
<ul>
|
|
14777
|
-
<li><strong>Answer XML:</strong> <code>${
|
|
14778
|
-
<li><strong>Audio stream:</strong> <code>${
|
|
14779
|
-
<li><strong>Status webhook:</strong> <code>${
|
|
14902
|
+
<li><strong>Answer XML:</strong> <code>${escapeHtml25(status.urls.answer)}</code></li>
|
|
14903
|
+
<li><strong>Audio stream:</strong> <code>${escapeHtml25(status.urls.stream)}</code></li>
|
|
14904
|
+
<li><strong>Status webhook:</strong> <code>${escapeHtml25(status.urls.webhook)}</code></li>
|
|
14780
14905
|
</ul>
|
|
14781
|
-
${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${
|
|
14782
|
-
${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${
|
|
14906
|
+
${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml25(name)}</code></li>`).join("")}</ul>` : ""}
|
|
14907
|
+
${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml25(warning)}</li>`).join("")}</ul>` : ""}
|
|
14783
14908
|
</main>`;
|
|
14784
14909
|
var renderPlivoSmokeHTML = (report, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
|
|
14785
14910
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Plivo smoke test</p>
|
|
14786
|
-
<h1>${
|
|
14911
|
+
<h1>${escapeHtml25(title)}</h1>
|
|
14787
14912
|
<p><strong>Status:</strong> ${report.pass ? "Pass" : "Fail"}</p>
|
|
14788
|
-
<ul>${report.checks.map((check) => `<li><strong>${
|
|
14913
|
+
<ul>${report.checks.map((check) => `<li><strong>${escapeHtml25(check.name)}</strong>: ${escapeHtml25(check.status)}${check.message ? ` - ${escapeHtml25(check.message)}` : ""}</li>`).join("")}</ul>
|
|
14789
14914
|
</main>`;
|
|
14790
14915
|
var runPlivoSmokeTest = async (input) => {
|
|
14791
14916
|
const setup = await buildPlivoVoiceSetupStatus(input.options, input);
|
|
@@ -14880,7 +15005,7 @@ var createPlivoVoiceRoutes = (options = {}) => {
|
|
|
14880
15005
|
request: input.request
|
|
14881
15006
|
}) : verificationUrl ?? input.request.url
|
|
14882
15007
|
}) : undefined);
|
|
14883
|
-
const app = new
|
|
15008
|
+
const app = new Elysia23({
|
|
14884
15009
|
name: options.name ?? "absolutejs-voice-plivo"
|
|
14885
15010
|
}).get(answerPath, async ({ query, request }) => {
|
|
14886
15011
|
const streamUrl = await resolvePlivoStreamUrl(options, {
|
|
@@ -14991,9 +15116,9 @@ var createPlivoVoiceRoutes = (options = {}) => {
|
|
|
14991
15116
|
|
|
14992
15117
|
// src/telephony/telnyx.ts
|
|
14993
15118
|
import { Buffer as Buffer6 } from "buffer";
|
|
14994
|
-
import { Elysia as
|
|
15119
|
+
import { Elysia as Elysia24 } from "elysia";
|
|
14995
15120
|
var escapeXml4 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
14996
|
-
var
|
|
15121
|
+
var escapeHtml26 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
14997
15122
|
var joinUrlPath4 = (origin, path) => `${origin.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
|
|
14998
15123
|
var resolveRequestOrigin3 = (request) => {
|
|
14999
15124
|
const url = new URL(request.url);
|
|
@@ -15194,21 +15319,21 @@ var buildTelnyxVoiceSetupStatus = async (options, input) => {
|
|
|
15194
15319
|
};
|
|
15195
15320
|
var renderTelnyxSetupHTML = (status, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
|
|
15196
15321
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Telnyx setup</p>
|
|
15197
|
-
<h1>${
|
|
15322
|
+
<h1>${escapeHtml26(title)}</h1>
|
|
15198
15323
|
<p><strong>Status:</strong> ${status.ready ? "Ready" : "Needs attention"}</p>
|
|
15199
15324
|
<ul>
|
|
15200
|
-
<li><strong>TeXML:</strong> <code>${
|
|
15201
|
-
<li><strong>Media stream:</strong> <code>${
|
|
15202
|
-
<li><strong>Status webhook:</strong> <code>${
|
|
15325
|
+
<li><strong>TeXML:</strong> <code>${escapeHtml26(status.urls.texml)}</code></li>
|
|
15326
|
+
<li><strong>Media stream:</strong> <code>${escapeHtml26(status.urls.stream)}</code></li>
|
|
15327
|
+
<li><strong>Status webhook:</strong> <code>${escapeHtml26(status.urls.webhook)}</code></li>
|
|
15203
15328
|
</ul>
|
|
15204
|
-
${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${
|
|
15205
|
-
${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${
|
|
15329
|
+
${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml26(name)}</code></li>`).join("")}</ul>` : ""}
|
|
15330
|
+
${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml26(warning)}</li>`).join("")}</ul>` : ""}
|
|
15206
15331
|
</main>`;
|
|
15207
15332
|
var renderTelnyxSmokeHTML = (report, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
|
|
15208
15333
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Telnyx smoke test</p>
|
|
15209
|
-
<h1>${
|
|
15334
|
+
<h1>${escapeHtml26(title)}</h1>
|
|
15210
15335
|
<p><strong>Status:</strong> ${report.pass ? "Pass" : "Fail"}</p>
|
|
15211
|
-
<ul>${report.checks.map((check) => `<li><strong>${
|
|
15336
|
+
<ul>${report.checks.map((check) => `<li><strong>${escapeHtml26(check.name)}</strong>: ${escapeHtml26(check.status)}${check.message ? ` - ${escapeHtml26(check.message)}` : ""}</li>`).join("")}</ul>
|
|
15212
15337
|
</main>`;
|
|
15213
15338
|
var runTelnyxSmokeTest = async (input) => {
|
|
15214
15339
|
const setup = await buildTelnyxVoiceSetupStatus(input.options, input);
|
|
@@ -15302,7 +15427,7 @@ var createTelnyxVoiceRoutes = (options = {}) => {
|
|
|
15302
15427
|
publicKey: options.webhook?.publicKey,
|
|
15303
15428
|
toleranceSeconds: options.webhook?.toleranceSeconds
|
|
15304
15429
|
}) : undefined);
|
|
15305
|
-
const app = new
|
|
15430
|
+
const app = new Elysia24({
|
|
15306
15431
|
name: options.name ?? "absolutejs-voice-telnyx"
|
|
15307
15432
|
}).get(texmlPath, async ({ query, request }) => {
|
|
15308
15433
|
const streamUrl = await resolveTelnyxStreamUrl(options, {
|
|
@@ -15412,8 +15537,8 @@ var createTelnyxVoiceRoutes = (options = {}) => {
|
|
|
15412
15537
|
};
|
|
15413
15538
|
|
|
15414
15539
|
// src/telephony/matrix.ts
|
|
15415
|
-
import { Elysia as
|
|
15416
|
-
var
|
|
15540
|
+
import { Elysia as Elysia25 } from "elysia";
|
|
15541
|
+
var escapeHtml27 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
15417
15542
|
var labelForProvider = (provider) => provider.split("-").map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`).join(" ");
|
|
15418
15543
|
var resolveEntryStatus = (contract, setup, smoke) => {
|
|
15419
15544
|
if (!contract.pass || !setup.ready || smoke?.pass === false) {
|
|
@@ -15474,13 +15599,13 @@ var badgeStyles = {
|
|
|
15474
15599
|
};
|
|
15475
15600
|
var renderVoiceTelephonyCarrierMatrixHTML = (matrix, options = {}) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 1040px; margin: 40px auto; padding: 0 20px; color: #172033;">
|
|
15476
15601
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Carrier matrix</p>
|
|
15477
|
-
<h1 style="font-size: 34px; margin: 0 0 8px;">${
|
|
15602
|
+
<h1 style="font-size: 34px; margin: 0 0 8px;">${escapeHtml27(options.title ?? "AbsoluteJS Voice Carrier Matrix")}</h1>
|
|
15478
15603
|
<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>
|
|
15479
15604
|
<section style="display:grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 16px;">
|
|
15480
15605
|
${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);">
|
|
15481
15606
|
<div style="display:flex; justify-content:space-between; gap:12px; align-items:center;">
|
|
15482
|
-
<h2 style="margin:0; font-size:20px;">${
|
|
15483
|
-
<span style="border:1px solid; border-radius:999px; padding:4px 10px; font-size:12px; font-weight:700; ${badgeStyles[entry.status]}">${
|
|
15607
|
+
<h2 style="margin:0; font-size:20px;">${escapeHtml27(entry.name)}</h2>
|
|
15608
|
+
<span style="border:1px solid; border-radius:999px; padding:4px 10px; font-size:12px; font-weight:700; ${badgeStyles[entry.status]}">${escapeHtml27(entry.status.toUpperCase())}</span>
|
|
15484
15609
|
</div>
|
|
15485
15610
|
<dl style="display:grid; grid-template-columns: 1fr 1fr; gap:8px 12px; margin:16px 0;">
|
|
15486
15611
|
<dt style="color:#64748b;">Setup</dt><dd style="margin:0; font-weight:700;">${entry.ready ? "Ready" : "Needs attention"}</dd>
|
|
@@ -15488,15 +15613,15 @@ ${matrix.entries.map((entry) => `<article style="border:1px solid #d9e2ec; borde
|
|
|
15488
15613
|
<dt style="color:#64748b;">Smoke</dt><dd style="margin:0; font-weight:700;">${entry.smoke ? entry.smoke.pass ? "Pass" : "Fail" : "Missing"}</dd>
|
|
15489
15614
|
<dt style="color:#64748b;">Contract</dt><dd style="margin:0; font-weight:700;">${entry.contract.pass ? "Pass" : "Fail"}</dd>
|
|
15490
15615
|
</dl>
|
|
15491
|
-
<p style="margin:0 0 8px; color:#475569;"><strong>Stream:</strong> <code>${
|
|
15492
|
-
<p style="margin:0 0 12px; color:#475569;"><strong>Webhook:</strong> <code>${
|
|
15493
|
-
${entry.issues.length ? `<ul style="margin:12px 0 0; padding-left:18px;">${entry.issues.map((issue) => `<li>${
|
|
15616
|
+
<p style="margin:0 0 8px; color:#475569;"><strong>Stream:</strong> <code>${escapeHtml27(entry.setup.urls.stream || "missing")}</code></p>
|
|
15617
|
+
<p style="margin:0 0 12px; color:#475569;"><strong>Webhook:</strong> <code>${escapeHtml27(entry.setup.urls.webhook || "missing")}</code></p>
|
|
15618
|
+
${entry.issues.length ? `<ul style="margin:12px 0 0; padding-left:18px;">${entry.issues.map((issue) => `<li>${escapeHtml27(issue.severity)}: ${escapeHtml27(issue.message)}</li>`).join("")}</ul>` : '<p style="margin:12px 0 0; color:#166534;">No contract issues.</p>'}
|
|
15494
15619
|
</article>`).join("")}
|
|
15495
15620
|
</section>
|
|
15496
15621
|
</main>`;
|
|
15497
15622
|
var createVoiceTelephonyCarrierMatrixRoutes = (options) => {
|
|
15498
15623
|
const path = options.path ?? "/api/voice/telephony/carriers";
|
|
15499
|
-
return new
|
|
15624
|
+
return new Elysia25({
|
|
15500
15625
|
name: options.name ?? "absolutejs-voice-telephony-carrier-matrix"
|
|
15501
15626
|
}).get(path, async ({ query, request }) => {
|
|
15502
15627
|
const providers = await options.load({ query, request });
|
|
@@ -15518,7 +15643,7 @@ var createVoiceTelephonyCarrierMatrixRoutes = (options) => {
|
|
|
15518
15643
|
};
|
|
15519
15644
|
|
|
15520
15645
|
// src/phoneAgentProductionSmoke.ts
|
|
15521
|
-
import { Elysia as
|
|
15646
|
+
import { Elysia as Elysia26 } from "elysia";
|
|
15522
15647
|
var defaultRequirements = [
|
|
15523
15648
|
"media-started",
|
|
15524
15649
|
"transcript",
|
|
@@ -15526,7 +15651,7 @@ var defaultRequirements = [
|
|
|
15526
15651
|
"lifecycle-outcome",
|
|
15527
15652
|
"no-session-error"
|
|
15528
15653
|
];
|
|
15529
|
-
var
|
|
15654
|
+
var escapeHtml28 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
15530
15655
|
var payloadType = (event) => typeof event.payload.type === "string" ? event.payload.type : undefined;
|
|
15531
15656
|
var hasTextPayload = (event) => ["text", "assistantText", "transcript"].some((key) => {
|
|
15532
15657
|
const value = event.payload[key];
|
|
@@ -15635,10 +15760,10 @@ var resolveHandlerOptions = async (options, input) => ({
|
|
|
15635
15760
|
});
|
|
15636
15761
|
var renderVoicePhoneAgentProductionSmokeHTML = (report, options = {}) => {
|
|
15637
15762
|
const title = options.title ?? "AbsoluteJS Voice Phone Smoke Contract";
|
|
15638
|
-
const issues = report.issues.map((issue) => `<li><strong>${
|
|
15639
|
-
const outcomes = report.observed.lifecycleOutcomes.map((outcome) => `<span class="pill">${
|
|
15640
|
-
const requirements = report.required.map((requirement) => `<span class="pill">${
|
|
15641
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
15763
|
+
const issues = report.issues.map((issue) => `<li><strong>${escapeHtml28(issue.requirement)}</strong>: ${escapeHtml28(issue.message)}</li>`).join("");
|
|
15764
|
+
const outcomes = report.observed.lifecycleOutcomes.map((outcome) => `<span class="pill">${escapeHtml28(outcome)}</span>`).join("");
|
|
15765
|
+
const requirements = report.required.map((requirement) => `<span class="pill">${escapeHtml28(requirement)}</span>`).join("");
|
|
15766
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml28(title)}</title><style>body{background:#0e141b;color:#f8f3e7;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1050px;padding:32px}.hero,.panel{background:#151d26;border:1px solid #283544;border-radius:24px;margin-bottom:16px;padding:22px}.hero{background:linear-gradient(135deg,rgba(20,184,166,.18),rgba(245,158,11,.12))}.eyebrow{color:#5eead4;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.2rem,6vw,4.8rem);line-height:.92;margin:.2rem 0 1rem}.status{border:1px solid #3f3f46;border-radius:999px;display:inline-flex;font-weight:900;padding:8px 12px}.pass{color:#86efac}.fail{color:#fca5a5}.grid{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr))}.metric{background:#0f151d;border:1px solid #283544;border-radius:16px;padding:14px}.metric strong{display:block;font-size:1.8rem}.pill{background:#0f151d;border:1px solid #3f3f46;border-radius:999px;display:inline-flex;margin:4px;padding:7px 10px}.issues{color:#fca5a5}code{color:#fde68a}@media(max-width:720px){main{padding:18px}}</style></head><body><main><section class="hero"><p class="eyebrow">Phone agent production smoke</p><h1>${escapeHtml28(title)}</h1><p class="status ${report.pass ? "pass" : "fail"}">${report.pass ? "PASS" : "FAIL"}</p><p>Contract <code>${escapeHtml28(report.contractId)}</code>${report.provider ? ` for <code>${escapeHtml28(report.provider)}</code>` : ""}${report.sessionId ? ` on session <code>${escapeHtml28(report.sessionId)}</code>` : ""}.</p></section><section class="panel"><h2>Observed Trace Evidence</h2><div class="grid"><div class="metric"><span>Media starts</span><strong>${String(report.observed.mediaStarts)}</strong></div><div class="metric"><span>Transcripts</span><strong>${String(report.observed.transcripts)}</strong></div><div class="metric"><span>Assistant responses</span><strong>${String(report.observed.assistantResponses)}</strong></div><div class="metric"><span>Session errors</span><strong>${String(report.observed.sessionErrors)}</strong></div></div><p>${outcomes || '<span class="pill">No lifecycle outcome</span>'}</p></section><section class="panel"><h2>Requirements</h2><p>${requirements}</p>${issues ? `<ul class="issues">${issues}</ul>` : '<p class="pass">All required phone-agent smoke evidence is present.</p>'}</section></main></body></html>`;
|
|
15642
15767
|
};
|
|
15643
15768
|
var createVoicePhoneAgentProductionSmokeJSONHandler = (options) => async ({
|
|
15644
15769
|
query,
|
|
@@ -15661,7 +15786,7 @@ var createVoicePhoneAgentProductionSmokeHTMLHandler = (options) => async ({
|
|
|
15661
15786
|
var createVoicePhoneAgentProductionSmokeRoutes = (options) => {
|
|
15662
15787
|
const path = options.path ?? "/api/voice/phone/smoke-contract";
|
|
15663
15788
|
const htmlPath = options.htmlPath === undefined ? "/voice/phone/smoke-contract" : options.htmlPath;
|
|
15664
|
-
const routes = new
|
|
15789
|
+
const routes = new Elysia26({
|
|
15665
15790
|
name: options.name ?? "absolutejs-voice-phone-smoke-contract"
|
|
15666
15791
|
}).get(path, createVoicePhoneAgentProductionSmokeJSONHandler(options));
|
|
15667
15792
|
if (htmlPath) {
|
|
@@ -15704,7 +15829,7 @@ var PHONE_AGENT_LIFECYCLE_STAGES = [
|
|
|
15704
15829
|
"completed",
|
|
15705
15830
|
"failed"
|
|
15706
15831
|
];
|
|
15707
|
-
var
|
|
15832
|
+
var escapeHtml29 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
15708
15833
|
var loadRouteJson = async (input) => {
|
|
15709
15834
|
const response = await input.app.handle(new Request(new URL(input.path, input.origin).toString(), {
|
|
15710
15835
|
headers: {
|
|
@@ -15764,18 +15889,18 @@ var renderVoicePhoneAgentSetupHTML = (report) => {
|
|
|
15764
15889
|
const entry = report.matrix?.entries.find((candidate) => candidate.provider === carrier.provider && (candidate.name === carrier.name || candidate.name === (carrier.name ?? carrier.provider)));
|
|
15765
15890
|
const urls = entry?.setup.urls;
|
|
15766
15891
|
const primaryUrl = carrier.provider === "plivo" ? urls?.twiml : urls?.twiml;
|
|
15767
|
-
return `<tr><td>${
|
|
15892
|
+
return `<tr><td>${escapeHtml29(carrier.name ?? carrier.provider)}</td><td>${escapeHtml29(carrier.provider)}</td><td><code>${escapeHtml29(carrier.setupPath || "disabled")}</code></td><td><code>${escapeHtml29(carrier.smokePath || "disabled")}</code></td><td>${entry ? `<span class="${escapeHtml29(entry.status)}">${escapeHtml29(entry.status.toUpperCase())}</span>` : "unknown"}</td><td>${primaryUrl ? `<code>${escapeHtml29(primaryUrl)}</code>` : '<span class="muted">missing</span>'}</td><td>${urls?.webhook ? `<code>${escapeHtml29(urls.webhook)}</code>` : '<span class="muted">missing</span>'}</td><td>${urls?.stream ? `<code>${escapeHtml29(urls.stream)}</code>` : '<span class="muted">missing</span>'}</td></tr>`;
|
|
15768
15893
|
}).join("");
|
|
15769
|
-
const stageList = report.lifecycleStages.map((stage) => `<li><code>${
|
|
15894
|
+
const stageList = report.lifecycleStages.map((stage) => `<li><code>${escapeHtml29(stage)}</code></li>`).join("");
|
|
15770
15895
|
const checklist = report.carriers.map((carrier) => {
|
|
15771
15896
|
const entry = report.matrix?.entries.find((candidate) => candidate.provider === carrier.provider && (candidate.name === carrier.name || candidate.name === (carrier.name ?? carrier.provider)));
|
|
15772
15897
|
const urls = entry?.setup.urls;
|
|
15773
15898
|
const answerLabel = carrier.provider === "telnyx" ? "TeXML URL" : carrier.provider === "plivo" ? "Answer URL" : "TwiML URL";
|
|
15774
15899
|
const answerUrl = urls?.twiml;
|
|
15775
|
-
const issueList = entry?.issues.map((issue) => `<li>${
|
|
15776
|
-
return `<article><h3>${
|
|
15900
|
+
const issueList = entry?.issues.map((issue) => `<li>${escapeHtml29(issue.severity)}: ${escapeHtml29(issue.message)}</li>`).join("") ?? "";
|
|
15901
|
+
return `<article><h3>${escapeHtml29(carrier.name ?? carrier.provider)}</h3><ol><li>Set ${escapeHtml29(answerLabel)} to <code>${escapeHtml29(answerUrl ?? "missing")}</code>.</li><li>Set status webhook to <code>${escapeHtml29(urls?.webhook ?? "missing")}</code>.</li><li>Allow media stream URL <code>${escapeHtml29(urls?.stream ?? "missing")}</code>.</li><li>Open setup: ${carrier.setupPath ? `<a href="${escapeHtml29(carrier.setupPath)}?format=html">${escapeHtml29(carrier.setupPath)}</a>` : '<span class="muted">disabled</span>'}.</li><li>Run smoke: ${carrier.smokePath ? `<a href="${escapeHtml29(carrier.smokePath)}?format=html">${escapeHtml29(carrier.smokePath)}</a>` : '<span class="muted">disabled</span>'}.</li>${report.productionSmokePath ? `<li>Certify production smoke traces: <a href="${escapeHtml29(report.productionSmokePath.replace("/api/", "/"))}?sessionId=">${escapeHtml29(report.productionSmokePath)}</a>.</li>` : ""}</ol>${issueList ? `<ul class="issues">${issueList}</ul>` : '<p class="pass">No carrier contract issues.</p>'}</article>`;
|
|
15777
15902
|
}).join("");
|
|
15778
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
15903
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml29(report.title)}</title><style>body{background:#10151c;color:#f8f3e7;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}.hero{background:linear-gradient(135deg,rgba(20,184,166,.2),rgba(245,158,11,.12));border:1px solid #283544;border-radius:28px;margin-bottom:18px;padding:28px}.eyebrow{color:#5eead4;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.3rem,6vw,4.8rem);line-height:.92;margin:.2rem 0 1rem}.badge{border:1px solid #3f3f46;border-radius:999px;display:inline-flex;padding:8px 12px}.pass{color:#86efac}.fail{color:#fca5a5}.warn{color:#fde68a}.muted{color:#aab5c0}table{background:#151d27;border:1px solid #283544;border-collapse:collapse;border-radius:18px;display:block;overflow:auto;width:100%}td,th{border-bottom:1px solid #283544;padding:12px;text-align:left;vertical-align:top}code{color:#fde68a;overflow-wrap:anywhere}.checklist{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));margin:18px 0}.checklist article{background:#151d27;border:1px solid #283544;border-radius:18px;padding:18px}.checklist ol{padding-left:20px}.issues{color:#fca5a5}.stages{display:grid;gap:8px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));padding-left:18px}a{color:#5eead4}</style></head><body><main><section class="hero"><p class="eyebrow">Phone agent setup</p><h1>${escapeHtml29(report.title)}</h1><p>One self-hosted entrypoint for carrier routes, setup reports, smoke checks, and normalized call lifecycle stages.</p><p class="badge ${report.ready ? "pass" : "fail"}">Ready: ${String(report.ready)}</p>${report.matrixPath ? `<p><a href="${escapeHtml29(report.matrixPath)}?format=html">Open carrier matrix</a></p>` : ""}</section><h2>Carrier Setup Checklist</h2><section class="checklist">${checklist}</section><h2>Carrier URLs</h2><table><thead><tr><th>Name</th><th>Provider</th><th>Setup</th><th>Smoke</th><th>Status</th><th>Answer/TwiML/TeXML</th><th>Webhook</th><th>Stream</th></tr></thead><tbody>${carrierRows}</tbody></table><h2>Lifecycle Schema</h2><ul class="stages">${stageList}</ul></main></body></html>`;
|
|
15779
15904
|
};
|
|
15780
15905
|
var createVoicePhoneAgent = (options) => {
|
|
15781
15906
|
const carrierSummaries = options.carriers.map((carrier) => ({
|
|
@@ -15784,7 +15909,7 @@ var createVoicePhoneAgent = (options) => {
|
|
|
15784
15909
|
setupPath: resolveSetupPath(carrier),
|
|
15785
15910
|
smokePath: resolveSmokePath(carrier)
|
|
15786
15911
|
}));
|
|
15787
|
-
const app = new
|
|
15912
|
+
const app = new Elysia27({
|
|
15788
15913
|
name: options.name ?? "absolutejs-voice-phone-agent"
|
|
15789
15914
|
});
|
|
15790
15915
|
for (const carrier of options.carriers) {
|
|
@@ -17243,8 +17368,8 @@ var phraseHintPrompt = (options) => {
|
|
|
17243
17368
|
hint.text,
|
|
17244
17369
|
...hint.aliases ?? []
|
|
17245
17370
|
]);
|
|
17246
|
-
const
|
|
17247
|
-
return
|
|
17371
|
+
const unique2 = terms.filter((value, index) => terms.indexOf(value) === index);
|
|
17372
|
+
return unique2.length ? `Prioritize accurate recovery of these phrases when heard: ${unique2.join(", ")}.` : undefined;
|
|
17248
17373
|
};
|
|
17249
17374
|
var lexiconPrompt = (options) => {
|
|
17250
17375
|
const entries = (options.lexicon ?? []).flatMap((entry) => {
|
|
@@ -17796,8 +17921,8 @@ var createOpenAIVoiceTTS = (options) => {
|
|
|
17796
17921
|
};
|
|
17797
17922
|
};
|
|
17798
17923
|
// src/providerCapabilities.ts
|
|
17799
|
-
import { Elysia as
|
|
17800
|
-
var
|
|
17924
|
+
import { Elysia as Elysia28 } from "elysia";
|
|
17925
|
+
var escapeHtml30 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
17801
17926
|
var fromProviderList = (kind, providers, options) => (providers ?? []).map((provider) => ({
|
|
17802
17927
|
configured: true,
|
|
17803
17928
|
features: options.features?.[provider],
|
|
@@ -17860,27 +17985,27 @@ var summarizeVoiceProviderCapabilities = async (options) => {
|
|
|
17860
17985
|
var renderVoiceProviderCapabilityHTML = (report, options = {}) => {
|
|
17861
17986
|
const title = options.title ?? "Voice Provider Capabilities";
|
|
17862
17987
|
const cards = report.capabilities.map((capability) => {
|
|
17863
|
-
const features = (capability.features ?? []).map((feature) => `<span class="pill">${
|
|
17864
|
-
return `<article class="card ${
|
|
17988
|
+
const features = (capability.features ?? []).map((feature) => `<span class="pill">${escapeHtml30(feature)}</span>`).join("");
|
|
17989
|
+
return `<article class="card ${escapeHtml30(capability.status)}">
|
|
17865
17990
|
<div class="card-header">
|
|
17866
17991
|
<div>
|
|
17867
|
-
<p class="eyebrow">${
|
|
17868
|
-
<h2>${
|
|
17992
|
+
<p class="eyebrow">${escapeHtml30(capability.kind)}</p>
|
|
17993
|
+
<h2>${escapeHtml30(capability.label ?? capability.provider)}</h2>
|
|
17869
17994
|
</div>
|
|
17870
|
-
<strong>${
|
|
17995
|
+
<strong>${escapeHtml30(capability.status)}</strong>
|
|
17871
17996
|
</div>
|
|
17872
|
-
${capability.description ? `<p>${
|
|
17997
|
+
${capability.description ? `<p>${escapeHtml30(capability.description)}</p>` : ""}
|
|
17873
17998
|
<dl>
|
|
17874
17999
|
<div><dt>Configured</dt><dd>${capability.configured ? "yes" : "no"}</dd></div>
|
|
17875
18000
|
<div><dt>Selected</dt><dd>${capability.selected ? "yes" : "no"}</dd></div>
|
|
17876
|
-
<div><dt>Model</dt><dd>${
|
|
18001
|
+
<div><dt>Model</dt><dd>${escapeHtml30(capability.model ?? "default")}</dd></div>
|
|
17877
18002
|
<div><dt>Runs</dt><dd>${String(capability.health?.runCount ?? 0)}</dd></div>
|
|
17878
18003
|
<div><dt>Errors</dt><dd>${String(capability.health?.errorCount ?? 0)}</dd></div>
|
|
17879
18004
|
</dl>
|
|
17880
18005
|
${features ? `<div class="features">${features}</div>` : ""}
|
|
17881
18006
|
</article>`;
|
|
17882
18007
|
}).join("");
|
|
17883
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
18008
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml30(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>${escapeHtml30(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>`;
|
|
17884
18009
|
};
|
|
17885
18010
|
var createVoiceProviderCapabilityJSONHandler = (options) => async () => summarizeVoiceProviderCapabilities(options);
|
|
17886
18011
|
var createVoiceProviderCapabilityHTMLHandler = (options) => async () => {
|
|
@@ -17897,7 +18022,7 @@ var createVoiceProviderCapabilityHTMLHandler = (options) => async () => {
|
|
|
17897
18022
|
var createVoiceProviderCapabilityRoutes = (options) => {
|
|
17898
18023
|
const path = options.path ?? "/api/provider-capabilities";
|
|
17899
18024
|
const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
|
|
17900
|
-
const routes = new
|
|
18025
|
+
const routes = new Elysia28({
|
|
17901
18026
|
name: options.name ?? "absolutejs-voice-provider-capabilities"
|
|
17902
18027
|
}).get(path, createVoiceProviderCapabilityJSONHandler(options));
|
|
17903
18028
|
if (htmlPath) {
|
|
@@ -17906,8 +18031,8 @@ var createVoiceProviderCapabilityRoutes = (options) => {
|
|
|
17906
18031
|
return routes;
|
|
17907
18032
|
};
|
|
17908
18033
|
// src/resilienceRoutes.ts
|
|
17909
|
-
import { Elysia as
|
|
17910
|
-
var
|
|
18034
|
+
import { Elysia as Elysia29 } from "elysia";
|
|
18035
|
+
var escapeHtml31 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
17911
18036
|
var getString11 = (value) => typeof value === "string" ? value : undefined;
|
|
17912
18037
|
var getNumber6 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
17913
18038
|
var getBoolean2 = (value) => value === true;
|
|
@@ -18054,13 +18179,13 @@ var summarizeRoutingEvents = (events) => {
|
|
|
18054
18179
|
};
|
|
18055
18180
|
var renderProviderCards = (title, providers) => {
|
|
18056
18181
|
if (providers.length === 0) {
|
|
18057
|
-
return `<p class="muted">No ${
|
|
18182
|
+
return `<p class="muted">No ${escapeHtml31(title)} provider health yet.</p>`;
|
|
18058
18183
|
}
|
|
18059
18184
|
return `<div class="provider-grid">${providers.map((provider) => `
|
|
18060
|
-
<article class="card provider ${
|
|
18185
|
+
<article class="card provider ${escapeHtml31(provider.status)}">
|
|
18061
18186
|
<div class="card-header">
|
|
18062
|
-
<strong>${
|
|
18063
|
-
<span>${
|
|
18187
|
+
<strong>${escapeHtml31(provider.provider)}</strong>
|
|
18188
|
+
<span>${escapeHtml31(provider.status)}${provider.recommended ? " \xB7 recommended" : ""}</span>
|
|
18064
18189
|
</div>
|
|
18065
18190
|
<dl>
|
|
18066
18191
|
<div><dt>Runs</dt><dd>${provider.runCount}</dd></div>
|
|
@@ -18069,7 +18194,7 @@ var renderProviderCards = (title, providers) => {
|
|
|
18069
18194
|
<div><dt>Timeouts</dt><dd>${provider.timeoutCount}</dd></div>
|
|
18070
18195
|
<div><dt>Fallbacks</dt><dd>${provider.fallbackCount}</dd></div>
|
|
18071
18196
|
</dl>
|
|
18072
|
-
${provider.lastError ? `<p class="muted">${
|
|
18197
|
+
${provider.lastError ? `<p class="muted">${escapeHtml31(provider.lastError)}</p>` : ""}
|
|
18073
18198
|
</article>
|
|
18074
18199
|
`).join("")}</div>`;
|
|
18075
18200
|
};
|
|
@@ -18078,24 +18203,24 @@ var renderTimeline2 = (events) => {
|
|
|
18078
18203
|
return '<p class="muted">No provider routing events yet. Run the app or simulate provider failover.</p>';
|
|
18079
18204
|
}
|
|
18080
18205
|
return `<div class="timeline">${events.slice(0, 40).map((event) => `
|
|
18081
|
-
<article class="card event ${
|
|
18206
|
+
<article class="card event ${escapeHtml31(event.status ?? "unknown")}">
|
|
18082
18207
|
<div class="card-header">
|
|
18083
|
-
<strong>${
|
|
18208
|
+
<strong>${escapeHtml31(event.kind.toUpperCase())} ${escapeHtml31(event.operation ?? "generate")}</strong>
|
|
18084
18209
|
<span>${new Date(event.at).toLocaleString()}</span>
|
|
18085
18210
|
</div>
|
|
18086
18211
|
<p>
|
|
18087
|
-
<span class="pill">${
|
|
18088
|
-
<span class="pill">provider: ${
|
|
18089
|
-
${event.fallbackProvider ? `<span class="pill">fallback: ${
|
|
18212
|
+
<span class="pill">${escapeHtml31(event.status ?? "unknown")}</span>
|
|
18213
|
+
<span class="pill">provider: ${escapeHtml31(event.provider ?? "unknown")}</span>
|
|
18214
|
+
${event.fallbackProvider ? `<span class="pill">fallback: ${escapeHtml31(event.fallbackProvider)}</span>` : ""}
|
|
18090
18215
|
${event.timedOut ? '<span class="pill danger">timed out</span>' : ""}
|
|
18091
18216
|
</p>
|
|
18092
18217
|
<dl>
|
|
18093
18218
|
<div><dt>Attempt</dt><dd>${event.attempt ?? 0}</dd></div>
|
|
18094
18219
|
<div><dt>Elapsed</dt><dd>${event.elapsedMs ?? 0}ms</dd></div>
|
|
18095
18220
|
<div><dt>Budget</dt><dd>${event.latencyBudgetMs ?? 0}ms</dd></div>
|
|
18096
|
-
<div><dt>Session</dt><dd>${
|
|
18221
|
+
<div><dt>Session</dt><dd>${escapeHtml31(event.sessionId)}</dd></div>
|
|
18097
18222
|
</dl>
|
|
18098
|
-
${event.error ? `<p class="muted">${
|
|
18223
|
+
${event.error ? `<p class="muted">${escapeHtml31(event.error)}</p>` : ""}
|
|
18099
18224
|
</article>
|
|
18100
18225
|
`).join("")}</div>`;
|
|
18101
18226
|
};
|
|
@@ -18105,9 +18230,9 @@ var renderSessionKind = (kind, summary) => {
|
|
|
18105
18230
|
const status = latest?.status ?? "idle";
|
|
18106
18231
|
const fallback = latest?.fallbackProvider && latest.fallbackProvider !== provider ? ` -> ${latest.fallbackProvider}` : "";
|
|
18107
18232
|
return `<div>
|
|
18108
|
-
<dt>${
|
|
18109
|
-
<dd>${
|
|
18110
|
-
<small>${
|
|
18233
|
+
<dt>${escapeHtml31(kind.toUpperCase())}</dt>
|
|
18234
|
+
<dd>${escapeHtml31(provider)}${escapeHtml31(fallback)}</dd>
|
|
18235
|
+
<small>${escapeHtml31(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>
|
|
18111
18236
|
</div>`;
|
|
18112
18237
|
};
|
|
18113
18238
|
var renderSessionSummaries = (sessions) => {
|
|
@@ -18115,10 +18240,10 @@ var renderSessionSummaries = (sessions) => {
|
|
|
18115
18240
|
return '<p class="muted">No call-level routing summaries yet. Run a voice session or provider simulation.</p>';
|
|
18116
18241
|
}
|
|
18117
18242
|
return `<div class="session-grid">${sessions.slice(0, 12).map((session) => `
|
|
18118
|
-
<article class="card session ${
|
|
18243
|
+
<article class="card session ${escapeHtml31(session.status)}">
|
|
18119
18244
|
<div class="card-header">
|
|
18120
|
-
<strong>${
|
|
18121
|
-
<span>${
|
|
18245
|
+
<strong>${escapeHtml31(session.sessionId)}</strong>
|
|
18246
|
+
<span>${escapeHtml31(session.status)}</span>
|
|
18122
18247
|
</div>
|
|
18123
18248
|
<p>
|
|
18124
18249
|
<span class="pill">${session.eventCount} routing events</span>
|
|
@@ -18145,26 +18270,26 @@ var renderSimulationControls = (kind, simulation) => {
|
|
|
18145
18270
|
const pathPrefix = simulation.pathPrefix ?? `/api/${kind}-simulate`;
|
|
18146
18271
|
const failureProviders = simulation.failureProviders ?? configuredProviders.map(({ provider }) => provider);
|
|
18147
18272
|
const canFail = (provider) => configuredProviders.some((entry) => entry.provider === provider) && (!simulation.fallbackRequiredProvider || configuredProviders.some((entry) => entry.provider === simulation.fallbackRequiredProvider));
|
|
18148
|
-
return `<div class="simulate-panel" data-sim-kind="${kind}" data-sim-prefix="${
|
|
18149
|
-
<p class="muted">${
|
|
18273
|
+
return `<div class="simulate-panel" data-sim-kind="${kind}" data-sim-prefix="${escapeHtml31(pathPrefix)}">
|
|
18274
|
+
<p class="muted">${escapeHtml31(simulation.failureMessage ?? `Simulate ${kind.toUpperCase()} provider failure without changing provider credentials.`)}</p>
|
|
18150
18275
|
<div class="simulate-actions">
|
|
18151
|
-
${failureProviders.map((provider) => `<button type="button" data-provider-fail="${
|
|
18152
|
-
${configuredProviders.map((provider) => `<button type="button" data-provider-recover="${
|
|
18276
|
+
${failureProviders.map((provider) => `<button type="button" data-provider-fail="${escapeHtml31(provider)}"${canFail(provider) ? "" : " disabled"}>Simulate ${escapeHtml31(provider)} ${kind.toUpperCase()} failure</button>`).join("")}
|
|
18277
|
+
${configuredProviders.map((provider) => `<button type="button" data-provider-recover="${escapeHtml31(provider.provider)}">Mark ${escapeHtml31(provider.provider)} recovered</button>`).join("")}
|
|
18153
18278
|
</div>
|
|
18154
|
-
${simulation.fallbackRequiredProvider && !configuredProviders.some((entry) => entry.provider === simulation.fallbackRequiredProvider) ? `<p class="muted">${
|
|
18279
|
+
${simulation.fallbackRequiredProvider && !configuredProviders.some((entry) => entry.provider === simulation.fallbackRequiredProvider) ? `<p class="muted">${escapeHtml31(simulation.fallbackRequiredMessage ?? `Configure ${simulation.fallbackRequiredProvider} to enable fallback simulation.`)}</p>` : ""}
|
|
18155
18280
|
<pre class="simulate-output" hidden></pre>
|
|
18156
18281
|
</div>`;
|
|
18157
18282
|
};
|
|
18158
18283
|
var renderVoiceResilienceHTML = (input) => {
|
|
18159
18284
|
const summary = summarizeRoutingEvents(input.routingEvents);
|
|
18160
|
-
const kindCounts = [...summary.byKind.entries()].map(([kind, count]) => `<span class="pill">${
|
|
18161
|
-
const links = input.links?.length ? input.links.map((link) => `<a href="${
|
|
18285
|
+
const kindCounts = [...summary.byKind.entries()].map(([kind, count]) => `<span class="pill">${escapeHtml31(kind)}: ${String(count)}</span>`).join("");
|
|
18286
|
+
const links = input.links?.length ? input.links.map((link) => `<a href="${escapeHtml31(link.href)}">${escapeHtml31(link.label)}</a>`).join(" \xB7 ") : "";
|
|
18162
18287
|
return `<!doctype html>
|
|
18163
18288
|
<html lang="en">
|
|
18164
18289
|
<head>
|
|
18165
18290
|
<meta charset="utf-8" />
|
|
18166
18291
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
18167
|
-
<title>${
|
|
18292
|
+
<title>${escapeHtml31(input.title ?? "AbsoluteJS Voice Resilience")}</title>
|
|
18168
18293
|
<style>
|
|
18169
18294
|
:root { color-scheme: dark; }
|
|
18170
18295
|
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; }
|
|
@@ -18307,7 +18432,7 @@ var registerSimulationRoutes = (routes, simulation, defaultPathPrefix) => {
|
|
|
18307
18432
|
};
|
|
18308
18433
|
var createVoiceResilienceRoutes = (options) => {
|
|
18309
18434
|
const path = options.path ?? "/resilience";
|
|
18310
|
-
const routes = new
|
|
18435
|
+
const routes = new Elysia29({
|
|
18311
18436
|
name: options.name ?? "absolutejs-voice-resilience"
|
|
18312
18437
|
}).get(path, async () => {
|
|
18313
18438
|
const events = await options.store.list();
|
|
@@ -18385,7 +18510,7 @@ var assertVoiceProviderRoutingContract = async (options) => {
|
|
|
18385
18510
|
return report;
|
|
18386
18511
|
};
|
|
18387
18512
|
// src/productionReadiness.ts
|
|
18388
|
-
import { Elysia as
|
|
18513
|
+
import { Elysia as Elysia30 } from "elysia";
|
|
18389
18514
|
|
|
18390
18515
|
// src/queue.ts
|
|
18391
18516
|
var releaseLeaseScript = `
|
|
@@ -19328,7 +19453,7 @@ var createVoiceOpsTaskProcessorWorkerLoop = (options) => {
|
|
|
19328
19453
|
};
|
|
19329
19454
|
|
|
19330
19455
|
// src/productionReadiness.ts
|
|
19331
|
-
var
|
|
19456
|
+
var escapeHtml32 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
19332
19457
|
var rollupStatus2 = (checks) => checks.some((check) => check.status === "fail") ? "fail" : checks.some((check) => check.status === "warn") ? "warn" : "pass";
|
|
19333
19458
|
var carrierStatus = (matrix) => matrix.summary.failing > 0 ? "fail" : matrix.summary.warnings > 0 || matrix.summary.ready < matrix.summary.providers ? "warn" : "pass";
|
|
19334
19459
|
var resolveCarriers = async (options, input) => {
|
|
@@ -19834,24 +19959,24 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
|
|
|
19834
19959
|
var renderVoiceProductionReadinessHTML = (report, options = {}) => {
|
|
19835
19960
|
const title = options.title ?? "AbsoluteJS Voice Production Readiness";
|
|
19836
19961
|
const checks = report.checks.map((check, index) => {
|
|
19837
|
-
const actions = (check.actions ?? []).map((action) => action.method === "POST" ? `<button type="button" data-readiness-action="${index}" data-action-url="${
|
|
19838
|
-
return `<article class="check ${
|
|
19962
|
+
const actions = (check.actions ?? []).map((action) => action.method === "POST" ? `<button type="button" data-readiness-action="${index}" data-action-url="${escapeHtml32(action.href)}">${escapeHtml32(action.label)}</button>` : `<a href="${escapeHtml32(action.href)}">${escapeHtml32(action.label)}</a>`).join("");
|
|
19963
|
+
return `<article class="check ${escapeHtml32(check.status)}">
|
|
19839
19964
|
<div>
|
|
19840
|
-
<span>${
|
|
19841
|
-
<h2>${
|
|
19842
|
-
${check.detail ? `<p>${
|
|
19965
|
+
<span>${escapeHtml32(check.status.toUpperCase())}</span>
|
|
19966
|
+
<h2>${escapeHtml32(check.label)}</h2>
|
|
19967
|
+
${check.detail ? `<p>${escapeHtml32(check.detail)}</p>` : ""}
|
|
19843
19968
|
${actions ? `<p class="actions">${actions}</p>` : ""}
|
|
19844
19969
|
</div>
|
|
19845
|
-
<strong>${
|
|
19846
|
-
${check.href ? `<a href="${
|
|
19970
|
+
<strong>${escapeHtml32(String(check.value ?? check.status))}</strong>
|
|
19971
|
+
${check.href ? `<a href="${escapeHtml32(check.href)}">Open surface</a>` : ""}
|
|
19847
19972
|
</article>`;
|
|
19848
19973
|
}).join("");
|
|
19849
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
19974
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml32(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>${escapeHtml32(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 ${escapeHtml32(report.status)}">Overall: ${escapeHtml32(report.status.toUpperCase())}</p><p>Checked ${escapeHtml32(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>`;
|
|
19850
19975
|
};
|
|
19851
19976
|
var createVoiceProductionReadinessRoutes = (options) => {
|
|
19852
19977
|
const path = options.path ?? "/api/production-readiness";
|
|
19853
19978
|
const htmlPath = options.htmlPath ?? "/production-readiness";
|
|
19854
|
-
const routes = new
|
|
19979
|
+
const routes = new Elysia30({
|
|
19855
19980
|
name: options.name ?? "absolutejs-voice-production-readiness"
|
|
19856
19981
|
});
|
|
19857
19982
|
routes.get(path, async ({ query, request }) => buildVoiceProductionReadinessReport(options, { query, request }));
|
|
@@ -19873,7 +19998,7 @@ var createVoiceProductionReadinessRoutes = (options) => {
|
|
|
19873
19998
|
return routes;
|
|
19874
19999
|
};
|
|
19875
20000
|
// src/opsConsoleRoutes.ts
|
|
19876
|
-
import { Elysia as
|
|
20001
|
+
import { Elysia as Elysia31 } from "elysia";
|
|
19877
20002
|
var DEFAULT_LINKS = [
|
|
19878
20003
|
{
|
|
19879
20004
|
description: "Quality gates for CI, deploy checks, and production readiness.",
|
|
@@ -19908,7 +20033,7 @@ var DEFAULT_LINKS = [
|
|
|
19908
20033
|
label: "Handoffs"
|
|
19909
20034
|
}
|
|
19910
20035
|
];
|
|
19911
|
-
var
|
|
20036
|
+
var escapeHtml33 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
19912
20037
|
var countProviderStatuses = (providers) => {
|
|
19913
20038
|
const degradedStatuses = new Set(["degraded", "rate-limited", "suppressed"]);
|
|
19914
20039
|
const healthy = providers.filter((provider) => provider.status === "healthy").length;
|
|
@@ -19964,20 +20089,20 @@ var buildVoiceOpsConsoleReport = async (options) => {
|
|
|
19964
20089
|
trace
|
|
19965
20090
|
};
|
|
19966
20091
|
};
|
|
19967
|
-
var renderMetricCard = (input) => `<article class="metric"><span>${
|
|
20092
|
+
var renderMetricCard = (input) => `<article class="metric"><span>${escapeHtml33(input.label)}</span><strong>${escapeHtml33(String(input.value))}</strong>${input.status ? `<p class="${escapeHtml33(input.status)}">${escapeHtml33(input.status)}</p>` : ""}${input.href ? `<a href="${escapeHtml33(input.href)}">Open</a>` : ""}</article>`;
|
|
19968
20093
|
var renderVoiceOpsConsoleHTML = (report, options = {}) => {
|
|
19969
20094
|
const links = report.links.map((link) => `<article class="surface">
|
|
19970
|
-
<div><h2>${
|
|
19971
|
-
<p><a href="${
|
|
20095
|
+
<div><h2>${escapeHtml33(link.label)}</h2>${link.description ? `<p>${escapeHtml33(link.description)}</p>` : ""}</div>
|
|
20096
|
+
<p><a href="${escapeHtml33(link.href)}">Open ${escapeHtml33(link.label)}</a>${link.statusHref ? ` \xB7 <a href="${escapeHtml33(link.statusHref)}">Status</a>` : ""}</p>
|
|
19972
20097
|
</article>`).join("");
|
|
19973
|
-
const sessions = report.recentSessions.length ? report.recentSessions.map((session) => `<tr><td>${
|
|
19974
|
-
const routing = report.recentRoutingEvents.length ? report.recentRoutingEvents.map((event) => `<tr><td>${
|
|
20098
|
+
const sessions = report.recentSessions.length ? report.recentSessions.map((session) => `<tr><td>${escapeHtml33(session.sessionId)}</td><td>${escapeHtml33(session.status)}</td><td>${session.turnCount}</td><td>${session.errorCount}</td><td>${session.replayHref ? `<a href="${escapeHtml33(session.replayHref)}">Replay</a>` : ""}</td></tr>`).join("") : '<tr><td colspan="5">No sessions yet.</td></tr>';
|
|
20099
|
+
const routing = report.recentRoutingEvents.length ? report.recentRoutingEvents.map((event) => `<tr><td>${escapeHtml33(event.kind)}</td><td>${escapeHtml33(event.provider ?? "unknown")}</td><td>${escapeHtml33(event.status ?? "unknown")}</td><td>${event.elapsedMs ?? 0}ms</td><td>${escapeHtml33(event.sessionId)}</td></tr>`).join("") : '<tr><td colspan="5">No provider routing events yet.</td></tr>';
|
|
19975
20100
|
const title = options.title ?? "AbsoluteJS Voice Ops Console";
|
|
19976
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
20101
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml33(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>${escapeHtml33(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 ${escapeHtml33(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>`;
|
|
19977
20102
|
};
|
|
19978
20103
|
var createVoiceOpsConsoleRoutes = (options) => {
|
|
19979
20104
|
const path = options.path ?? "/ops-console";
|
|
19980
|
-
const routes = new
|
|
20105
|
+
const routes = new Elysia31({
|
|
19981
20106
|
name: options.name ?? "absolutejs-voice-ops-console"
|
|
19982
20107
|
});
|
|
19983
20108
|
const getReport = () => buildVoiceOpsConsoleReport(options);
|
|
@@ -20139,19 +20264,19 @@ var summarizeVoiceOpsStatus = async (options) => {
|
|
|
20139
20264
|
};
|
|
20140
20265
|
};
|
|
20141
20266
|
// src/opsStatusRoutes.ts
|
|
20142
|
-
import { Elysia as
|
|
20143
|
-
var
|
|
20267
|
+
import { Elysia as Elysia32 } from "elysia";
|
|
20268
|
+
var escapeHtml34 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
20144
20269
|
var renderVoiceOpsStatusHTML = (report, options = {}) => {
|
|
20145
20270
|
const title = options.title ?? "AbsoluteJS Voice Ops Status";
|
|
20146
20271
|
const surfaces = Object.entries(report.surfaces).map(([key, surface]) => {
|
|
20147
20272
|
const value = "total" in surface ? `${Math.max(surface.total - ("failed" in surface ? surface.failed : ("degraded" in surface) ? surface.degraded : 0), 0)}/${surface.total}` : surface.status;
|
|
20148
|
-
return `<article class="surface ${
|
|
20273
|
+
return `<article class="surface ${escapeHtml34(surface.status)}"><span>${escapeHtml34(surface.status.toUpperCase())}</span><h2>${escapeHtml34(key)}</h2><strong>${escapeHtml34(value)}</strong></article>`;
|
|
20149
20274
|
}).join("");
|
|
20150
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
20275
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml34(title)}</title><style>body{background:#0d141b;color:#f8f3e7;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:980px;padding:32px}.hero{background:linear-gradient(135deg,rgba(20,184,166,.2),rgba(245,158,11,.12));border:1px solid #283544;border-radius:28px;margin-bottom:18px;padding:28px}.eyebrow{color:#5eead4;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{border:1px solid #3f3f46;border-radius:999px;display:inline-flex;font-weight:900;padding:8px 12px}.surfaces{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr))}.surface{background:#151d26;border:1px solid #283544;border-radius:20px;padding:18px}.surface span{color:#aab5c0;font-size:.78rem;font-weight:900;letter-spacing:.08em}.surface strong{font-size:1.5rem}.pass{border-color:rgba(34,197,94,.55)}.fail{border-color:rgba(239,68,68,.75)}a{color:#5eead4}</style></head><body><main><section class="hero"><p class="eyebrow">Ops status</p><h1>${escapeHtml34(title)}</h1><p>Compact pass/fail status for framework widgets, demos, and small customer-facing health badges.</p><p class="status ${escapeHtml34(report.status)}">Overall: ${escapeHtml34(report.status.toUpperCase())}</p><p>${report.passed}/${report.total} checks passing</p></section><section class="surfaces">${surfaces || '<article class="surface pass"><span>PASS</span><h2>No checks configured</h2><strong>0/0</strong></article>'}</section></main></body></html>`;
|
|
20151
20276
|
};
|
|
20152
20277
|
var createVoiceOpsStatusRoutes = (options) => {
|
|
20153
20278
|
const path = options.path ?? "/api/voice/ops-status";
|
|
20154
|
-
const routes = new
|
|
20279
|
+
const routes = new Elysia32({
|
|
20155
20280
|
name: options.name ?? "absolutejs-voice-ops-status"
|
|
20156
20281
|
});
|
|
20157
20282
|
routes.get(path, async () => summarizeVoiceOpsStatus(options));
|
|
@@ -20584,8 +20709,8 @@ var createVoiceTTSProviderRouter = (options) => {
|
|
|
20584
20709
|
};
|
|
20585
20710
|
};
|
|
20586
20711
|
// src/traceDeliveryRoutes.ts
|
|
20587
|
-
import { Elysia as
|
|
20588
|
-
var
|
|
20712
|
+
import { Elysia as Elysia33 } from "elysia";
|
|
20713
|
+
var escapeHtml35 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
20589
20714
|
var getString12 = (value) => typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
20590
20715
|
var getNumber7 = (value) => {
|
|
20591
20716
|
if (typeof value === "number" && Number.isFinite(value)) {
|
|
@@ -20666,14 +20791,14 @@ var renderSinkResults2 = (delivery) => {
|
|
|
20666
20791
|
if (entries.length === 0) {
|
|
20667
20792
|
return "<p>No sink delivery attempts recorded yet.</p>";
|
|
20668
20793
|
}
|
|
20669
|
-
return `<ul>${entries.map(([sinkId, result]) => `<li><strong>${
|
|
20794
|
+
return `<ul>${entries.map(([sinkId, result]) => `<li><strong>${escapeHtml35(sinkId)}</strong>: ${escapeHtml35(result.status)}${result.deliveredTo ? ` to ${escapeHtml35(result.deliveredTo)}` : ""}${result.error ? ` (${escapeHtml35(result.error)})` : ""}</li>`).join("")}</ul>`;
|
|
20670
20795
|
};
|
|
20671
|
-
var renderEventList2 = (delivery) => delivery.events.length === 0 ? "<p>No trace events in this delivery.</p>" : `<ul>${delivery.events.map((event) => `<li>${
|
|
20796
|
+
var renderEventList2 = (delivery) => delivery.events.length === 0 ? "<p>No trace events in this delivery.</p>" : `<ul>${delivery.events.map((event) => `<li>${escapeHtml35(event.type)} <small>${escapeHtml35(event.id)}</small>${event.sessionId ? ` session=${escapeHtml35(event.sessionId)}` : ""}</li>`).join("")}</ul>`;
|
|
20672
20797
|
var renderVoiceTraceDeliveryHTML = (report, options = {}) => {
|
|
20673
20798
|
const title = options.title ?? "AbsoluteJS Voice Trace Deliveries";
|
|
20674
|
-
const drainAction = options.workerPath === false ? "" : `<form method="post" action="${
|
|
20675
|
-
const rows = report.deliveries.map((delivery) => `<article class="delivery ${
|
|
20676
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
20799
|
+
const drainAction = options.workerPath === false ? "" : `<form method="post" action="${escapeHtml35(options.workerPath ?? "/api/voice-trace-deliveries/drain")}"><button type="submit">Drain trace deliveries</button></form>`;
|
|
20800
|
+
const rows = report.deliveries.map((delivery) => `<article class="delivery ${escapeHtml35(delivery.deliveryStatus)}"><div class="head"><div><span>${escapeHtml35(delivery.deliveryStatus)}</span><h2>${escapeHtml35(delivery.id)}</h2><p>${escapeHtml35(new Date(delivery.createdAt).toLocaleString())}${delivery.deliveredAt ? ` \xB7 delivered ${escapeHtml35(new Date(delivery.deliveredAt).toLocaleString())}` : ""}</p></div><strong>${String(delivery.deliveryAttempts ?? 0)} attempt(s)</strong></div>${delivery.deliveryError ? `<p class="error">${escapeHtml35(delivery.deliveryError)}</p>` : ""}<h3>Sinks</h3>${renderSinkResults2(delivery)}<h3>Events</h3>${renderEventList2(delivery)}</article>`).join("");
|
|
20801
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml35(title)}</title><style>body{background:#0f1318;color:#f4efe1;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1120px;padding:32px}.hero{background:linear-gradient(135deg,rgba(34,197,94,.16),rgba(14,165,233,.14));border:1px solid #26313d;border-radius:28px;margin-bottom:18px;padding:28px}.eyebrow{color:#86efac;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.2rem,5vw,4.8rem);line-height:.92;margin:.2rem 0 1rem}.grid{display:grid;gap:12px;grid-template-columns:repeat(4,1fr);margin-bottom:16px}.grid article,.delivery{background:#151b22;border:1px solid #26313d;border-radius:22px;padding:18px}.grid span,.delivery span{color:#86efac;font-size:.78rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}.grid strong{display:block;font-size:2rem}.deliveries{display:grid;gap:14px}.delivery.failed{border-color:rgba(239,68,68,.75)}.delivery.pending{border-color:rgba(245,158,11,.7)}.delivery.delivered{border-color:rgba(34,197,94,.55)}.delivery.skipped{border-color:rgba(148,163,184,.6)}.head{align-items:start;display:flex;gap:14px;justify-content:space-between}.delivery h2{font-size:1.05rem;margin:.3rem 0;overflow-wrap:anywhere}.delivery h3{margin:1rem 0 .3rem}.delivery p,.delivery li{color:#c8d0d8}.error{color:#fecaca!important}button{background:#86efac;border:0;border-radius:999px;color:#07111f;cursor:pointer;font-weight:900;margin-top:12px;padding:10px 14px}@media(max-width:760px){main{padding:20px}.grid{grid-template-columns:1fr 1fr}.head{display:block}}</style></head><body><main><section class="hero"><p class="eyebrow">Trace export health</p><h1>${escapeHtml35(title)}</h1><p>Checked ${escapeHtml35(new Date(report.checkedAt).toLocaleString())}. Showing ${String(report.deliveries.length)} delivery item(s).</p>${drainAction}</section>${renderMetricGrid3(report)}<section class="deliveries">${rows || "<p>No trace deliveries match this filter.</p>"}</section></main></body></html>`;
|
|
20677
20802
|
};
|
|
20678
20803
|
var createVoiceTraceDeliveryJSONHandler = (options) => async ({ query }) => buildVoiceTraceDeliveryReport(options, resolveVoiceTraceDeliveryFilter(query, options.filter));
|
|
20679
20804
|
var createVoiceTraceDeliveryHTMLHandler = (options) => async ({ query }) => {
|
|
@@ -20693,7 +20818,7 @@ var createVoiceTraceDeliveryRoutes = (options) => {
|
|
|
20693
20818
|
const path = options.path ?? "/api/voice-trace-deliveries";
|
|
20694
20819
|
const htmlPath = options.htmlPath === undefined ? "/traces/deliveries" : options.htmlPath;
|
|
20695
20820
|
const workerPath = options.workerPath === undefined ? `${path}/drain` : options.workerPath;
|
|
20696
|
-
const routes = new
|
|
20821
|
+
const routes = new Elysia33({
|
|
20697
20822
|
name: options.name ?? "absolutejs-voice-trace-deliveries"
|
|
20698
20823
|
}).get(path, createVoiceTraceDeliveryJSONHandler(options));
|
|
20699
20824
|
if (htmlPath !== false) {
|
|
@@ -20711,8 +20836,8 @@ var createVoiceTraceDeliveryRoutes = (options) => {
|
|
|
20711
20836
|
return routes;
|
|
20712
20837
|
};
|
|
20713
20838
|
// src/traceTimeline.ts
|
|
20714
|
-
import { Elysia as
|
|
20715
|
-
var
|
|
20839
|
+
import { Elysia as Elysia34 } from "elysia";
|
|
20840
|
+
var escapeHtml36 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
20716
20841
|
var getString13 = (value) => typeof value === "string" && value.trim() ? value : undefined;
|
|
20717
20842
|
var getNumber8 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
20718
20843
|
var firstString3 = (payload, keys) => {
|
|
@@ -20880,20 +21005,20 @@ var summarizeVoiceTraceTimeline = (events, options = {}) => {
|
|
|
20880
21005
|
};
|
|
20881
21006
|
};
|
|
20882
21007
|
var formatMs3 = (value) => value === undefined ? "n/a" : `${String(value)}ms`;
|
|
20883
|
-
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>${
|
|
21008
|
+
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>${escapeHtml36(provider.provider)}</strong><dl><div><dt>Events</dt><dd>${String(provider.eventCount)}</dd></div><div><dt>Avg</dt><dd>${formatMs3(provider.averageElapsedMs)}</dd></div><div><dt>Max</dt><dd>${formatMs3(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>`;
|
|
20884
21009
|
var renderVoiceTraceTimelineSessionHTML = (session, options = {}) => {
|
|
20885
|
-
const events = session.events.map((event) => `<tr class="${
|
|
20886
|
-
const issues = session.evaluation.issues.length ? session.evaluation.issues.map((issue) => `<li class="${
|
|
20887
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
21010
|
+
const events = session.events.map((event) => `<tr class="${escapeHtml36(event.status ?? "")}"><td>+${String(event.offsetMs)}ms</td><td>${escapeHtml36(event.type)}</td><td>${escapeHtml36(event.label)}</td><td>${escapeHtml36(event.provider ?? "")}</td><td>${escapeHtml36(event.status ?? "")}</td><td>${formatMs3(event.elapsedMs)}</td></tr>`).join("");
|
|
21011
|
+
const issues = session.evaluation.issues.length ? session.evaluation.issues.map((issue) => `<li class="${escapeHtml36(issue.severity)}">${escapeHtml36(issue.code)}: ${escapeHtml36(issue.message)}</li>`).join("") : "<li>none</li>";
|
|
21012
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml36(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>${escapeHtml36(session.sessionId)}</h1><p class="status ${escapeHtml36(session.status)}">${escapeHtml36(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>${formatMs3(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>`;
|
|
20888
21013
|
};
|
|
20889
|
-
var renderSessionRows = (report) => report.sessions.length === 0 ? '<tr><td colspan="7">No trace events recorded yet.</td></tr>' : report.sessions.map((session) => `<tr class="${
|
|
21014
|
+
var renderSessionRows = (report) => report.sessions.length === 0 ? '<tr><td colspan="7">No trace events recorded yet.</td></tr>' : report.sessions.map((session) => `<tr class="${escapeHtml36(session.status)}"><td><a href="/traces/${encodeURIComponent(session.sessionId)}">${escapeHtml36(session.sessionId)}</a></td><td>${escapeHtml36(session.status)}</td><td>${String(session.summary.eventCount)}</td><td>${String(session.summary.turnCount)}</td><td>${String(session.summary.errorCount)}</td><td>${formatMs3(session.summary.callDurationMs)}</td><td>${session.providers.map((provider) => escapeHtml36(provider.provider)).join(", ")}</td></tr>`).join("");
|
|
20890
21015
|
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}}";
|
|
20891
|
-
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>${
|
|
21016
|
+
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>${escapeHtml36(options.title ?? "Voice Trace Timelines")}</title><style>${timelineCSS}</style></head><body><main><header><p class="eyebrow">Self-hosted voice debugging</p><h1>${escapeHtml36(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>`;
|
|
20892
21017
|
var createVoiceTraceTimelineRoutes = (options) => {
|
|
20893
21018
|
const path = options.path ?? "/api/voice-traces";
|
|
20894
21019
|
const htmlPath = options.htmlPath ?? "/traces";
|
|
20895
21020
|
const title = options.title ?? "AbsoluteJS Voice Trace Timelines";
|
|
20896
|
-
const routes = new
|
|
21021
|
+
const routes = new Elysia34({
|
|
20897
21022
|
name: options.name ?? "absolutejs-voice-trace-timelines"
|
|
20898
21023
|
});
|
|
20899
21024
|
const buildReport = async () => summarizeVoiceTraceTimeline(await options.store.list(), {
|
|
@@ -21546,7 +21671,7 @@ var createVoiceMemoryStore = () => {
|
|
|
21546
21671
|
return { get, getOrCreate, list, remove, set };
|
|
21547
21672
|
};
|
|
21548
21673
|
// src/opsWebhook.ts
|
|
21549
|
-
import { Elysia as
|
|
21674
|
+
import { Elysia as Elysia35 } from "elysia";
|
|
21550
21675
|
var toHex6 = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
21551
21676
|
var signVoiceOpsWebhookBody = async (input) => {
|
|
21552
21677
|
const encoder = new TextEncoder;
|
|
@@ -21676,7 +21801,7 @@ var verifyVoiceOpsWebhookSignature = async (input) => {
|
|
|
21676
21801
|
};
|
|
21677
21802
|
var createVoiceOpsWebhookReceiverRoutes = (options = {}) => {
|
|
21678
21803
|
const path = options.path ?? "/api/voice-ops/webhook";
|
|
21679
|
-
return new
|
|
21804
|
+
return new Elysia35().post(path, async ({ body, request, set }) => {
|
|
21680
21805
|
const bodyText = typeof body === "string" ? body : JSON.stringify(body);
|
|
21681
21806
|
if (options.signingSecret) {
|
|
21682
21807
|
const verification = await verifyVoiceOpsWebhookSignature({
|
|
@@ -22564,6 +22689,7 @@ export {
|
|
|
22564
22689
|
runVoiceSessionEvals,
|
|
22565
22690
|
runVoiceScenarioFixtureEvals,
|
|
22566
22691
|
runVoiceScenarioEvals,
|
|
22692
|
+
runVoiceReconnectContract,
|
|
22567
22693
|
runVoiceProviderRoutingContract,
|
|
22568
22694
|
runVoicePhoneAgentProductionSmokeContract,
|
|
22569
22695
|
runVoiceOutcomeContractSuite,
|
|
@@ -22603,6 +22729,7 @@ export {
|
|
|
22603
22729
|
renderVoiceScenarioFixtureEvalHTML,
|
|
22604
22730
|
renderVoiceScenarioEvalHTML,
|
|
22605
22731
|
renderVoiceResilienceHTML,
|
|
22732
|
+
renderVoiceReconnectContractHTML,
|
|
22606
22733
|
renderVoiceQualityHTML,
|
|
22607
22734
|
renderVoiceProviderHealthHTML,
|
|
22608
22735
|
renderVoiceProviderCapabilityHTML,
|
|
@@ -22743,6 +22870,7 @@ export {
|
|
|
22743
22870
|
createVoiceRedisTelephonyWebhookIdempotencyStore,
|
|
22744
22871
|
createVoiceRedisTaskLeaseCoordinator,
|
|
22745
22872
|
createVoiceRedisIdempotencyStore,
|
|
22873
|
+
createVoiceReconnectContractRoutes,
|
|
22746
22874
|
createVoiceQualityRoutes,
|
|
22747
22875
|
createVoiceProviderRouter,
|
|
22748
22876
|
createVoiceProviderHealthRoutes,
|