@absolutejs/voice 0.0.22-beta.86 → 0.0.22-beta.88

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