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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/appKit.d.ts CHANGED
@@ -6,11 +6,12 @@ import { type VoiceHandoffHealthRoutesOptions } from './handoffHealth';
6
6
  import { type VoiceOpsConsoleRoutesOptions } from './opsConsoleRoutes';
7
7
  import { type VoiceProviderHealthRoutesOptions } from './providerHealth';
8
8
  import { type VoiceProviderCapabilityRoutesOptions } from './providerCapabilities';
9
+ import { type VoiceProductionReadinessRoutesOptions } from './productionReadiness';
9
10
  import { type VoiceQualityRoutesOptions } from './qualityRoutes';
10
11
  import { type VoiceResilienceRoutesOptions } from './resilienceRoutes';
11
12
  import { type VoiceSessionListRoutesOptions, type VoiceSessionReplayRoutesOptions } from './sessionReplay';
12
13
  import { type VoiceTraceEventStore } from './trace';
13
- export type VoiceAppKitSurface = 'assistantHealth' | 'diagnostics' | 'evals' | 'handoffs' | 'opsConsole' | 'providerCapabilities' | 'providerHealth' | 'quality' | 'resilience' | 'sessionReplay' | 'sessions';
14
+ export type VoiceAppKitSurface = 'assistantHealth' | 'diagnostics' | 'evals' | 'handoffs' | 'opsConsole' | 'providerCapabilities' | 'providerHealth' | 'productionReadiness' | 'quality' | 'resilience' | 'sessionReplay' | 'sessions';
14
15
  export type VoiceAppKitLink = VoiceEvalLink & {
15
16
  description?: string;
16
17
  statusHref?: string;
@@ -28,6 +29,7 @@ export type VoiceAppKitRoutesOptions<TProvider extends string = string> = {
28
29
  opsConsole?: false | Partial<VoiceOpsConsoleRoutesOptions>;
29
30
  providerCapabilities?: false | Partial<VoiceProviderCapabilityRoutesOptions<TProvider>>;
30
31
  providerHealth?: false | Partial<VoiceProviderHealthRoutesOptions<TProvider>>;
32
+ productionReadiness?: false | Partial<VoiceProductionReadinessRoutesOptions>;
31
33
  quality?: false | Partial<VoiceQualityRoutesOptions>;
32
34
  resilience?: false | Partial<VoiceResilienceRoutesOptions>;
33
35
  sessionReplay?: false | Partial<VoiceSessionReplayRoutesOptions>;
@@ -29,7 +29,7 @@ export type VoiceOpsStatusWidgetOptions = VoiceAppKitStatusClientOptions & {
29
29
  includeLinks?: boolean;
30
30
  title?: string;
31
31
  };
32
- export declare const getVoiceOpsStatusLabel: (report?: VoiceAppKitStatusReport | null, error?: string | null) => "Passing" | "Needs attention" | "Unavailable" | "Checking";
32
+ export declare const getVoiceOpsStatusLabel: (report?: VoiceAppKitStatusReport | null, error?: string | null) => "Needs attention" | "Passing" | "Unavailable" | "Checking";
33
33
  export declare const createVoiceOpsStatusViewModel: (snapshot: VoiceAppKitStatusSnapshot, options?: VoiceOpsStatusWidgetOptions) => VoiceOpsStatusViewModel;
34
34
  export declare const renderVoiceOpsStatusHTML: (snapshot: VoiceAppKitStatusSnapshot, options?: VoiceOpsStatusWidgetOptions) => string;
35
35
  export declare const getVoiceOpsStatusCSS: () => string;
package/dist/index.d.ts CHANGED
@@ -18,6 +18,7 @@ export { createAnthropicVoiceAssistantModel, createGeminiVoiceAssistantModel, cr
18
18
  export { createOpenAIVoiceTTS } from './openaiTTS';
19
19
  export { createVoiceProviderHealthHTMLHandler, createVoiceProviderHealthJSONHandler, createVoiceProviderHealthRoutes, renderVoiceProviderHealthHTML, summarizeVoiceProviderHealth } from './providerHealth';
20
20
  export { createVoiceProviderCapabilityHTMLHandler, createVoiceProviderCapabilityJSONHandler, createVoiceProviderCapabilityRoutes, renderVoiceProviderCapabilityHTML, summarizeVoiceProviderCapabilities } from './providerCapabilities';
21
+ export { buildVoiceProductionReadinessReport, createVoiceProductionReadinessRoutes, renderVoiceProductionReadinessHTML } from './productionReadiness';
21
22
  export { buildVoiceOpsConsoleReport, createVoiceOpsConsoleRoutes, renderVoiceOpsConsoleHTML } from './opsConsoleRoutes';
22
23
  export { createVoiceQualityRoutes, evaluateVoiceQuality, renderVoiceQualityHTML } from './qualityRoutes';
23
24
  export { createVoiceResilienceRoutes, createVoiceRoutingDecisionSummary, listVoiceRoutingEvents, renderVoiceResilienceHTML, summarizeVoiceRoutingDecision, summarizeVoiceRoutingSessions } from './resilienceRoutes';
@@ -61,6 +62,7 @@ export type { VoiceTurnQualityHTMLHandlerOptions, VoiceTurnQualityItem, VoiceTur
61
62
  export type { VoiceOutcomeContractDefinition, VoiceOutcomeContractHTMLHandlerOptions, VoiceOutcomeContractIssue, VoiceOutcomeContractOptions, VoiceOutcomeContractReport, VoiceOutcomeContractRoutesOptions, VoiceOutcomeContractStatus, VoiceOutcomeContractSuiteReport } from './outcomeContract';
62
63
  export type { VoiceTelephonyOutcomeAction, VoiceTelephonyOutcomeDecision, VoiceTelephonyOutcomePolicy, VoiceTelephonyOutcomeProviderEvent, VoiceTelephonyOutcomeRouteResult, VoiceTelephonyOutcomeStatusDecision, VoiceTelephonyWebhookDecision, VoiceTelephonyWebhookHandlerOptions, VoiceTelephonyWebhookIdempotencyStore, VoiceTelephonyWebhookParseInput, VoiceTelephonyWebhookProvider, VoiceTelephonyWebhookRoutesOptions, VoiceTelephonyWebhookVerificationResult, StoredVoiceTelephonyWebhookDecision } from './telephonyOutcome';
63
64
  export type { VoiceOpsConsoleLink, VoiceOpsConsoleReport, VoiceOpsConsoleRoutesOptions } from './opsConsoleRoutes';
65
+ export type { VoiceProductionReadinessCheck, VoiceProductionReadinessReport, VoiceProductionReadinessRoutesOptions, VoiceProductionReadinessStatus } from './productionReadiness';
64
66
  export type { VoiceQualityLink, VoiceQualityMetric, VoiceQualityReport, VoiceQualityRoutesOptions, VoiceQualityStatus, VoiceQualityThresholds } from './qualityRoutes';
65
67
  export type { VoiceResilienceIOSimulator, VoiceResilienceLink, VoiceResiliencePageData, VoiceResilienceRoutesOptions, VoiceResilienceSimulationProvider, VoiceRoutingKindSummary, VoiceRoutingDecisionSummary, VoiceRoutingDecisionSummaryOptions, VoiceRoutingEvent, VoiceRoutingEventKind, VoiceRoutingSessionSummary, VoiceRoutingSessionSummaryOptions } from './resilienceRoutes';
66
68
  export type { VoiceIOProviderRouterEvent, VoiceIOProviderRouterOptions, VoiceIOProviderRouterPolicy, VoiceIOProviderRouterPolicyConfig, VoiceSTTProviderRouterOptions, VoiceTTSProviderRouterOptions } from './providerAdapters';
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";
@@ -9403,6 +9403,365 @@ var createVoiceProviderCapabilityRoutes = (options) => {
9403
9403
  return routes;
9404
9404
  };
9405
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
+
9406
9765
  // src/appKit.ts
9407
9766
  var DEFAULT_LINKS2 = [
9408
9767
  {
@@ -9427,6 +9786,12 @@ var DEFAULT_LINKS2 = [
9427
9786
  href: "/resilience",
9428
9787
  label: "Resilience"
9429
9788
  },
9789
+ {
9790
+ description: "One JSON/HTML production readiness rollup.",
9791
+ href: "/production-readiness",
9792
+ label: "Production Readiness",
9793
+ statusHref: "/api/production-readiness"
9794
+ },
9430
9795
  {
9431
9796
  description: "Recent sessions and replay links.",
9432
9797
  href: "/sessions",
@@ -9559,7 +9924,7 @@ var summarizeVoiceAppKitStatus = async (options) => {
9559
9924
  };
9560
9925
  };
9561
9926
  var createVoiceAppKitRoutes = (options) => {
9562
- const routes = new Elysia12({
9927
+ const routes = new Elysia14({
9563
9928
  name: options.name ?? "absolutejs-voice-app-kit"
9564
9929
  });
9565
9930
  const links = resolveLinks(options.links);
@@ -9666,6 +10031,23 @@ var createVoiceAppKitRoutes = (options) => {
9666
10031
  ...options.resilience
9667
10032
  }));
9668
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
+ }
9669
10051
  if (options.opsConsole !== false) {
9670
10052
  surfaces.push("opsConsole");
9671
10053
  routes.use(createVoiceOpsConsoleRoutes({
@@ -10177,7 +10559,7 @@ var createVoiceToolIdempotencyKey = (input) => {
10177
10559
  ].join(":");
10178
10560
  };
10179
10561
  // src/toolContract.ts
10180
- import { Elysia as Elysia13 } from "elysia";
10562
+ import { Elysia as Elysia15 } from "elysia";
10181
10563
  var createDefaultSession = (contractId, caseId) => createVoiceSessionRecord(`tool-contract-${contractId}-${caseId}`);
10182
10564
  var createDefaultTurn = (caseId) => ({
10183
10565
  committedAt: Date.now(),
@@ -10187,7 +10569,7 @@ var createDefaultTurn = (caseId) => ({
10187
10569
  });
10188
10570
  var defaultApi = {};
10189
10571
  var sameJSON = (left, right) => JSON.stringify(left) === JSON.stringify(right);
10190
- 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;");
10191
10573
  var evaluateExpectation = (input) => {
10192
10574
  const issues = [];
10193
10575
  const expect = input.expect;
@@ -10353,19 +10735,19 @@ var renderVoiceToolContractHTML = (report, options = {}) => {
10353
10735
  const title = options.title ?? "Voice Tool Contracts";
10354
10736
  const contracts = report.contracts.map((contract) => {
10355
10737
  const cases = contract.cases.map((testCase) => `<tr>
10356
- <td>${escapeHtml14(testCase.label ?? testCase.caseId)}</td>
10738
+ <td>${escapeHtml16(testCase.label ?? testCase.caseId)}</td>
10357
10739
  <td class="${testCase.pass ? "pass" : "fail"}">${testCase.pass ? "pass" : "fail"}</td>
10358
- <td>${escapeHtml14(testCase.status)}</td>
10740
+ <td>${escapeHtml16(testCase.status)}</td>
10359
10741
  <td>${String(testCase.attempts)}</td>
10360
10742
  <td>${String(testCase.elapsedMs)}ms</td>
10361
10743
  <td>${testCase.timedOut ? "yes" : "no"}</td>
10362
- <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>
10363
10745
  </tr>`).join("");
10364
10746
  return `<section class="contract ${contract.pass ? "pass" : "fail"}">
10365
10747
  <div class="contract-header">
10366
10748
  <div>
10367
- <p class="eyebrow">${escapeHtml14(contract.toolName)}</p>
10368
- <h2>${escapeHtml14(contract.label ?? contract.contractId)}</h2>
10749
+ <p class="eyebrow">${escapeHtml16(contract.toolName)}</p>
10750
+ <h2>${escapeHtml16(contract.label ?? contract.contractId)}</h2>
10369
10751
  </div>
10370
10752
  <strong class="${contract.pass ? "pass" : "fail"}">${contract.pass ? "Passing" : "Failing"}</strong>
10371
10753
  </div>
@@ -10375,7 +10757,7 @@ var renderVoiceToolContractHTML = (report, options = {}) => {
10375
10757
  </table>
10376
10758
  </section>`;
10377
10759
  }).join("");
10378
- 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>`;
10379
10761
  };
10380
10762
  var createVoiceToolContractJSONHandler = (options) => () => runVoiceToolContractSuite(options);
10381
10763
  var createVoiceToolContractHTMLHandler = (options) => async () => {
@@ -10392,7 +10774,7 @@ var createVoiceToolContractHTMLHandler = (options) => async () => {
10392
10774
  var createVoiceToolContractRoutes = (options) => {
10393
10775
  const path = options.path ?? "/api/tool-contracts";
10394
10776
  const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
10395
- const routes = new Elysia13({
10777
+ const routes = new Elysia15({
10396
10778
  name: options.name ?? "absolutejs-voice-tool-contracts"
10397
10779
  }).get(path, createVoiceToolContractJSONHandler(options));
10398
10780
  if (htmlPath) {
@@ -10401,9 +10783,9 @@ var createVoiceToolContractRoutes = (options) => {
10401
10783
  return routes;
10402
10784
  };
10403
10785
  // src/turnQuality.ts
10404
- import { Elysia as Elysia14 } from "elysia";
10786
+ import { Elysia as Elysia16 } from "elysia";
10405
10787
  var DEFAULT_CONFIDENCE_WARN_THRESHOLD = 0.72;
10406
- 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;");
10407
10789
  var getTurnLatencyMs = (turn) => {
10408
10790
  const firstTranscriptAt = turn.transcripts.map((transcript) => transcript.endedAtMs ?? transcript.startedAtMs).filter((value) => typeof value === "number").sort((left, right) => left - right)[0];
10409
10791
  if (firstTranscriptAt === undefined) {
@@ -10474,24 +10856,24 @@ var summarizeVoiceTurnQuality = async (options) => {
10474
10856
  };
10475
10857
  var renderVoiceTurnQualityHTML = (report, options = {}) => {
10476
10858
  const title = options.title ?? "Voice Turn Quality";
10477
- 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)}">
10478
10860
  <div class="turn-header">
10479
10861
  <div>
10480
- <p class="eyebrow">${escapeHtml15(turn.sessionId)} \xB7 ${escapeHtml15(turn.turnId)}</p>
10481
- <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>
10482
10864
  </div>
10483
- <strong>${escapeHtml15(turn.status)}</strong>
10865
+ <strong>${escapeHtml17(turn.status)}</strong>
10484
10866
  </div>
10485
10867
  <dl>
10486
- <div><dt>Source</dt><dd>${escapeHtml15(turn.source ?? "unknown")}</dd></div>
10868
+ <div><dt>Source</dt><dd>${escapeHtml17(turn.source ?? "unknown")}</dd></div>
10487
10869
  <div><dt>Confidence</dt><dd>${turn.averageConfidence === undefined ? "n/a" : `${Math.round(turn.averageConfidence * 100)}%`}</dd></div>
10488
- <div><dt>Fallback</dt><dd>${turn.fallbackUsed ? `yes (${escapeHtml15(turn.fallbackSelectionReason ?? "selected")})` : "no"}</dd></div>
10489
- <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>
10490
10872
  <div><dt>Transcripts</dt><dd>${String(turn.selectedTranscriptCount)} selected \xB7 ${String(turn.finalTranscriptCount)} final \xB7 ${String(turn.partialTranscriptCount)} partial</dd></div>
10491
10873
  <div><dt>Cost</dt><dd>${turn.costUnits === undefined ? "n/a" : String(turn.costUnits)}</dd></div>
10492
10874
  </dl>
10493
10875
  </article>`).join("");
10494
- 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>`;
10495
10877
  };
10496
10878
  var createVoiceTurnQualityJSONHandler = (options) => async () => summarizeVoiceTurnQuality(options);
10497
10879
  var createVoiceTurnQualityHTMLHandler = (options) => async () => {
@@ -10508,7 +10890,7 @@ var createVoiceTurnQualityHTMLHandler = (options) => async () => {
10508
10890
  var createVoiceTurnQualityRoutes = (options) => {
10509
10891
  const path = options.path ?? "/api/turn-quality";
10510
10892
  const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
10511
- const routes = new Elysia14({
10893
+ const routes = new Elysia16({
10512
10894
  name: options.name ?? "absolutejs-voice-turn-quality"
10513
10895
  }).get(path, createVoiceTurnQualityJSONHandler(options));
10514
10896
  if (htmlPath) {
@@ -10517,8 +10899,8 @@ var createVoiceTurnQualityRoutes = (options) => {
10517
10899
  return routes;
10518
10900
  };
10519
10901
  // src/outcomeContract.ts
10520
- import { Elysia as Elysia15 } from "elysia";
10521
- 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;");
10522
10904
  var getPayloadString = (event, key) => typeof event.payload[key] === "string" ? event.payload[key] : undefined;
10523
10905
  var toList = async (input) => Array.isArray(input) ? input : await input?.list() ?? [];
10524
10906
  var hydrateSessions = async (input) => {
@@ -10626,9 +11008,9 @@ var renderVoiceOutcomeContractHTML = (report, options = {}) => {
10626
11008
  const contracts = report.contracts.map((contract) => `<section class="contract ${contract.pass ? "pass" : "fail"}">
10627
11009
  <div class="contract-header">
10628
11010
  <div>
10629
- <p class="eyebrow">${escapeHtml16(contract.contractId)}</p>
10630
- <h2>${escapeHtml16(contract.label ?? contract.contractId)}</h2>
10631
- ${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>` : ""}
10632
11014
  </div>
10633
11015
  <strong>${contract.pass ? "pass" : "fail"}</strong>
10634
11016
  </div>
@@ -10639,9 +11021,9 @@ var renderVoiceOutcomeContractHTML = (report, options = {}) => {
10639
11021
  <span>handoffs ${String(contract.matched.handoffs)}</span>
10640
11022
  <span>events ${String(contract.matched.integrationEvents)}</span>
10641
11023
  </div>
10642
- ${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>` : ""}
10643
11025
  </section>`).join("");
10644
- 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>`;
10645
11027
  };
10646
11028
  var createVoiceOutcomeContractJSONHandler = (options) => async () => runVoiceOutcomeContractSuite(options);
10647
11029
  var createVoiceOutcomeContractHTMLHandler = (options) => async () => {
@@ -10657,7 +11039,7 @@ var createVoiceOutcomeContractHTMLHandler = (options) => async () => {
10657
11039
  var createVoiceOutcomeContractRoutes = (options) => {
10658
11040
  const path = options.path ?? "/api/outcome-contracts";
10659
11041
  const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
10660
- const routes = new Elysia15({
11042
+ const routes = new Elysia17({
10661
11043
  name: options.name ?? "absolutejs-voice-outcome-contracts"
10662
11044
  }).get(path, createVoiceOutcomeContractJSONHandler(options));
10663
11045
  if (htmlPath) {
@@ -10666,7 +11048,7 @@ var createVoiceOutcomeContractRoutes = (options) => {
10666
11048
  return routes;
10667
11049
  };
10668
11050
  // src/telephonyOutcome.ts
10669
- import { Elysia as Elysia16 } from "elysia";
11051
+ import { Elysia as Elysia18 } from "elysia";
10670
11052
  var DEFAULT_COMPLETED_STATUSES = [
10671
11053
  "answered",
10672
11054
  "completed",
@@ -11313,7 +11695,7 @@ var createVoiceTelephonyWebhookHandler = (options = {}) => async (input) => {
11313
11695
  var createVoiceTelephonyWebhookRoutes = (options = {}) => {
11314
11696
  const path = options.path ?? "/api/voice/telephony/webhook";
11315
11697
  const handler = createVoiceTelephonyWebhookHandler(options);
11316
- return new Elysia16({
11698
+ return new Elysia18({
11317
11699
  name: options.name ?? "absolutejs-voice-telephony-webhooks"
11318
11700
  }).post(path, async ({ query, request }) => {
11319
11701
  try {
@@ -13581,7 +13963,7 @@ var createVoiceMemoryStore = () => {
13581
13963
  return { get, getOrCreate, list, remove, set };
13582
13964
  };
13583
13965
  // src/opsWebhook.ts
13584
- import { Elysia as Elysia17 } from "elysia";
13966
+ import { Elysia as Elysia19 } from "elysia";
13585
13967
  var toHex5 = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
13586
13968
  var signVoiceOpsWebhookBody = async (input) => {
13587
13969
  const encoder = new TextEncoder;
@@ -13711,7 +14093,7 @@ var verifyVoiceOpsWebhookSignature = async (input) => {
13711
14093
  };
13712
14094
  var createVoiceOpsWebhookReceiverRoutes = (options = {}) => {
13713
14095
  const path = options.path ?? "/api/voice-ops/webhook";
13714
- return new Elysia17().post(path, async ({ body, request, set }) => {
14096
+ return new Elysia19().post(path, async ({ body, request, set }) => {
13715
14097
  const bodyText = typeof body === "string" ? body : JSON.stringify(body);
13716
14098
  if (options.signingSecret) {
13717
14099
  const verification = await verifyVoiceOpsWebhookSignature({
@@ -15440,7 +15822,7 @@ var createVoiceSTTRoutingCorrectionHandler = (mode = "generic") => {
15440
15822
  };
15441
15823
  // src/telephony/twilio.ts
15442
15824
  import { Buffer as Buffer3 } from "buffer";
15443
- import { Elysia as Elysia18 } from "elysia";
15825
+ import { Elysia as Elysia20 } from "elysia";
15444
15826
  var TWILIO_MULAW_SAMPLE_RATE = 8000;
15445
15827
  var VOICE_PCM_SAMPLE_RATE = 16000;
15446
15828
  var escapeXml2 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
@@ -15470,7 +15852,7 @@ var resolveTwilioStreamParameters = async (parameters, input) => {
15470
15852
  return parameters;
15471
15853
  };
15472
15854
  var joinUrlPath = (origin, path) => `${origin.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
15473
- 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;");
15474
15856
  var getWebhookVerificationUrl = (webhook, input) => {
15475
15857
  if (!webhook?.verificationUrl) {
15476
15858
  return;
@@ -15513,23 +15895,23 @@ var buildTwilioVoiceSetupStatus = async (options, input) => {
15513
15895
  };
15514
15896
  var renderTwilioVoiceSetupHTML = (status, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
15515
15897
  <p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Twilio setup</p>
15516
- <h1>${escapeHtml17(title)}</h1>
15898
+ <h1>${escapeHtml19(title)}</h1>
15517
15899
  <p><strong>Status:</strong> ${status.ready ? "Ready" : "Needs attention"}</p>
15518
15900
  <section>
15519
15901
  <h2>URLs</h2>
15520
15902
  <ul>
15521
- <li><strong>TwiML:</strong> <code>${escapeHtml17(status.urls.twiml)}</code></li>
15522
- <li><strong>Media stream:</strong> <code>${escapeHtml17(status.urls.stream)}</code></li>
15523
- <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>
15524
15906
  </ul>
15525
15907
  </section>
15526
15908
  <section>
15527
15909
  <h2>Signing</h2>
15528
15910
  <p>Mode: <code>${status.signing.mode}</code></p>
15529
- ${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>` : ""}
15530
15912
  </section>
15531
- ${status.missing.length ? `<section><h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml17(name)}</code></li>`).join("")}</ul></section>` : ""}
15532
- ${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>` : ""}
15533
15915
  </main>`;
15534
15916
  var extractTwilioStreamUrl = (twiml) => twiml.match(/<Stream\b[^>]*\surl="([^"]+)"/i)?.[1]?.replaceAll("&amp;", "&");
15535
15917
  var createSmokeCheck = (name, status, message, details) => ({
@@ -15540,20 +15922,20 @@ var createSmokeCheck = (name, status, message, details) => ({
15540
15922
  });
15541
15923
  var renderTwilioVoiceSmokeHTML = (report, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
15542
15924
  <p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Twilio smoke test</p>
15543
- <h1>${escapeHtml17(title)}</h1>
15925
+ <h1>${escapeHtml19(title)}</h1>
15544
15926
  <p><strong>Status:</strong> ${report.pass ? "Pass" : "Fail"}</p>
15545
15927
  <section>
15546
15928
  <h2>Checks</h2>
15547
15929
  <ul>
15548
- ${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("")}
15549
15931
  </ul>
15550
15932
  </section>
15551
15933
  <section>
15552
15934
  <h2>Observed URLs</h2>
15553
15935
  <ul>
15554
- <li><strong>TwiML:</strong> <code>${escapeHtml17(report.setup.urls.twiml)}</code></li>
15555
- <li><strong>Stream:</strong> <code>${escapeHtml17(report.twiml?.streamUrl ?? report.setup.urls.stream)}</code></li>
15556
- <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>
15557
15939
  </ul>
15558
15940
  </section>
15559
15941
  </main>`;
@@ -16013,7 +16395,7 @@ var createTwilioVoiceRoutes = (options) => {
16013
16395
  const smokePath = options.smoke?.path === false ? false : options.smoke?.path ?? "/api/voice/twilio/smoke";
16014
16396
  const bridges = new WeakMap;
16015
16397
  const webhookPolicy = options.webhook?.policy ?? options.outcomePolicy ?? createVoiceTelephonyOutcomePolicy();
16016
- const app = new Elysia18({
16398
+ const app = new Elysia20({
16017
16399
  name: options.name ?? "absolutejs-voice-twilio"
16018
16400
  }).get(twimlPath, async ({ query, request }) => {
16019
16401
  const streamUrl = await resolveTwilioStreamUrl(options, {
@@ -16147,90 +16529,11 @@ var createTwilioVoiceRoutes = (options) => {
16147
16529
  return report;
16148
16530
  });
16149
16531
  };
16150
- // src/telephony/contract.ts
16151
- var DEFAULT_REQUIREMENTS = [
16152
- "stream-url",
16153
- "wss-stream",
16154
- "webhook-url",
16155
- "signed-webhook",
16156
- "smoke-pass"
16157
- ];
16158
- var hasFailingSmokeCheck = (smoke) => smoke?.checks.some((check) => check.status === "fail") ?? false;
16159
- var evaluateVoiceTelephonyContract = (input) => {
16160
- const requirements = input.options?.requirements ?? DEFAULT_REQUIREMENTS;
16161
- const issues = [];
16162
- const hasRequirement = (requirement) => requirements.includes(requirement);
16163
- if (hasRequirement("stream-url") && !input.setup.urls.stream) {
16164
- issues.push({
16165
- message: "Missing media stream URL.",
16166
- requirement: "stream-url",
16167
- severity: "error"
16168
- });
16169
- }
16170
- if (hasRequirement("wss-stream") && !input.setup.urls.stream.startsWith("wss://")) {
16171
- issues.push({
16172
- message: "Media stream URL must use wss://.",
16173
- requirement: "wss-stream",
16174
- severity: "error"
16175
- });
16176
- }
16177
- if (hasRequirement("webhook-url") && !input.setup.urls.webhook) {
16178
- issues.push({
16179
- message: "Missing carrier webhook URL.",
16180
- requirement: "webhook-url",
16181
- severity: "error"
16182
- });
16183
- }
16184
- if (hasRequirement("signed-webhook") && !input.setup.signing.configured) {
16185
- issues.push({
16186
- message: "Carrier webhook signature verification is not configured.",
16187
- requirement: "signed-webhook",
16188
- severity: "error"
16189
- });
16190
- }
16191
- if (hasRequirement("smoke-pass")) {
16192
- if (!input.smoke) {
16193
- issues.push({
16194
- message: "Missing telephony smoke test report.",
16195
- requirement: "smoke-pass",
16196
- severity: "error"
16197
- });
16198
- } else if (!input.smoke.pass || hasFailingSmokeCheck(input.smoke)) {
16199
- issues.push({
16200
- message: "Telephony smoke test did not pass.",
16201
- requirement: "smoke-pass",
16202
- severity: "error"
16203
- });
16204
- }
16205
- }
16206
- for (const warning of input.setup.warnings) {
16207
- issues.push({
16208
- message: warning,
16209
- requirement: "stream-url",
16210
- severity: "warning"
16211
- });
16212
- }
16213
- for (const name of input.setup.missing) {
16214
- issues.push({
16215
- message: `${name} is missing.`,
16216
- requirement: "webhook-url",
16217
- severity: "error"
16218
- });
16219
- }
16220
- return {
16221
- issues,
16222
- pass: issues.every((issue) => issue.severity !== "error"),
16223
- provider: input.setup.provider,
16224
- requirements,
16225
- setup: input.setup,
16226
- smoke: input.smoke
16227
- };
16228
- };
16229
16532
  // src/telephony/telnyx.ts
16230
16533
  import { Buffer as Buffer4 } from "buffer";
16231
- import { Elysia as Elysia19 } from "elysia";
16534
+ import { Elysia as Elysia21 } from "elysia";
16232
16535
  var escapeXml3 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
16233
- 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;");
16234
16537
  var joinUrlPath2 = (origin, path) => `${origin.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
16235
16538
  var resolveRequestOrigin2 = (request) => {
16236
16539
  const url = new URL(request.url);
@@ -16431,21 +16734,21 @@ var buildTelnyxVoiceSetupStatus = async (options, input) => {
16431
16734
  };
16432
16735
  var renderTelnyxSetupHTML = (status, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
16433
16736
  <p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Telnyx setup</p>
16434
- <h1>${escapeHtml18(title)}</h1>
16737
+ <h1>${escapeHtml20(title)}</h1>
16435
16738
  <p><strong>Status:</strong> ${status.ready ? "Ready" : "Needs attention"}</p>
16436
16739
  <ul>
16437
- <li><strong>TeXML:</strong> <code>${escapeHtml18(status.urls.texml)}</code></li>
16438
- <li><strong>Media stream:</strong> <code>${escapeHtml18(status.urls.stream)}</code></li>
16439
- <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>
16440
16743
  </ul>
16441
- ${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml18(name)}</code></li>`).join("")}</ul>` : ""}
16442
- ${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>` : ""}
16443
16746
  </main>`;
16444
16747
  var renderTelnyxSmokeHTML = (report, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
16445
16748
  <p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Telnyx smoke test</p>
16446
- <h1>${escapeHtml18(title)}</h1>
16749
+ <h1>${escapeHtml20(title)}</h1>
16447
16750
  <p><strong>Status:</strong> ${report.pass ? "Pass" : "Fail"}</p>
16448
- <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>
16449
16752
  </main>`;
16450
16753
  var runTelnyxSmokeTest = async (input) => {
16451
16754
  const setup = await buildTelnyxVoiceSetupStatus(input.options, input);
@@ -16539,7 +16842,7 @@ var createTelnyxVoiceRoutes = (options = {}) => {
16539
16842
  publicKey: options.webhook?.publicKey,
16540
16843
  toleranceSeconds: options.webhook?.toleranceSeconds
16541
16844
  }) : undefined);
16542
- const app = new Elysia19({
16845
+ const app = new Elysia21({
16543
16846
  name: options.name ?? "absolutejs-voice-telnyx"
16544
16847
  }).get(texmlPath, async ({ query, request }) => {
16545
16848
  const streamUrl = await resolveTelnyxStreamUrl(options, {
@@ -16649,9 +16952,9 @@ var createTelnyxVoiceRoutes = (options = {}) => {
16649
16952
  };
16650
16953
  // src/telephony/plivo.ts
16651
16954
  import { Buffer as Buffer5 } from "buffer";
16652
- import { Elysia as Elysia20 } from "elysia";
16955
+ import { Elysia as Elysia22 } from "elysia";
16653
16956
  var escapeXml4 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
16654
- 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;");
16655
16958
  var joinUrlPath3 = (origin, path) => `${origin.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
16656
16959
  var resolveRequestOrigin3 = (request) => {
16657
16960
  const url = new URL(request.url);
@@ -16902,21 +17205,21 @@ var buildPlivoVoiceSetupStatus = async (options, input) => {
16902
17205
  };
16903
17206
  var renderPlivoSetupHTML = (status, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
16904
17207
  <p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Plivo setup</p>
16905
- <h1>${escapeHtml19(title)}</h1>
17208
+ <h1>${escapeHtml21(title)}</h1>
16906
17209
  <p><strong>Status:</strong> ${status.ready ? "Ready" : "Needs attention"}</p>
16907
17210
  <ul>
16908
- <li><strong>Answer XML:</strong> <code>${escapeHtml19(status.urls.answer)}</code></li>
16909
- <li><strong>Audio stream:</strong> <code>${escapeHtml19(status.urls.stream)}</code></li>
16910
- <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>
16911
17214
  </ul>
16912
- ${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml19(name)}</code></li>`).join("")}</ul>` : ""}
16913
- ${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>` : ""}
16914
17217
  </main>`;
16915
17218
  var renderPlivoSmokeHTML = (report, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
16916
17219
  <p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Plivo smoke test</p>
16917
- <h1>${escapeHtml19(title)}</h1>
17220
+ <h1>${escapeHtml21(title)}</h1>
16918
17221
  <p><strong>Status:</strong> ${report.pass ? "Pass" : "Fail"}</p>
16919
- <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>
16920
17223
  </main>`;
16921
17224
  var runPlivoSmokeTest = async (input) => {
16922
17225
  const setup = await buildPlivoVoiceSetupStatus(input.options, input);
@@ -17011,7 +17314,7 @@ var createPlivoVoiceRoutes = (options = {}) => {
17011
17314
  request: input.request
17012
17315
  }) : verificationUrl ?? input.request.url
17013
17316
  }) : undefined);
17014
- const app = new Elysia20({
17317
+ const app = new Elysia22({
17015
17318
  name: options.name ?? "absolutejs-voice-plivo"
17016
17319
  }).get(answerPath, async ({ query, request }) => {
17017
17320
  const streamUrl = await resolvePlivoStreamUrl(options, {
@@ -17119,111 +17422,6 @@ var createPlivoVoiceRoutes = (options = {}) => {
17119
17422
  return report;
17120
17423
  });
17121
17424
  };
17122
- // src/telephony/matrix.ts
17123
- import { Elysia as Elysia21 } from "elysia";
17124
- var escapeHtml20 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&#39;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
17125
- var labelForProvider = (provider) => provider.split("-").map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`).join(" ");
17126
- var resolveEntryStatus = (contract, setup, smoke) => {
17127
- if (!contract.pass || !setup.ready || smoke?.pass === false) {
17128
- return "fail";
17129
- }
17130
- if (contract.issues.some((issue) => issue.severity === "warning") || setup.warnings.length > 0 || smoke?.checks.some((check) => check.status === "warn")) {
17131
- return "warn";
17132
- }
17133
- return "pass";
17134
- };
17135
- var createVoiceTelephonyCarrierMatrix = (options) => {
17136
- const entries = options.providers.map((provider) => {
17137
- const contract = provider.contract ?? evaluateVoiceTelephonyContract({
17138
- options: options.contract,
17139
- setup: provider.setup,
17140
- smoke: provider.smoke
17141
- });
17142
- const failures = provider.smoke?.checks.filter((check) => check.status === "fail").length ?? 0;
17143
- const warnings = contract.issues.filter((issue) => issue.severity === "warning").length + (provider.smoke?.checks.filter((check) => check.status === "warn").length ?? 0);
17144
- const errors = contract.issues.filter((issue) => issue.severity === "error").length;
17145
- const status = resolveEntryStatus(contract, provider.setup, provider.smoke);
17146
- return {
17147
- contract,
17148
- issues: contract.issues,
17149
- name: provider.name ?? labelForProvider(provider.setup.provider),
17150
- provider: provider.setup.provider,
17151
- ready: provider.setup.ready,
17152
- setup: provider.setup,
17153
- smoke: provider.smoke,
17154
- status,
17155
- summary: {
17156
- errors,
17157
- failures,
17158
- missing: provider.setup.missing.length,
17159
- warnings
17160
- }
17161
- };
17162
- });
17163
- const summary = {
17164
- contractsPassing: entries.filter((entry) => entry.contract.pass).length,
17165
- failing: entries.filter((entry) => entry.status === "fail").length,
17166
- providers: entries.length,
17167
- ready: entries.filter((entry) => entry.ready).length,
17168
- smokePassing: entries.filter((entry) => entry.smoke?.pass).length,
17169
- warnings: entries.reduce((total, entry) => total + entry.summary.warnings, 0)
17170
- };
17171
- return {
17172
- entries,
17173
- generatedAt: options.generatedAt ?? Date.now(),
17174
- pass: entries.length > 0 && entries.every((entry) => entry.status !== "fail"),
17175
- summary
17176
- };
17177
- };
17178
- var badgeStyles = {
17179
- fail: "background:#fee2e2;color:#991b1b;border-color:#fecaca;",
17180
- pass: "background:#dcfce7;color:#166534;border-color:#bbf7d0;",
17181
- warn: "background:#fef3c7;color:#92400e;border-color:#fde68a;"
17182
- };
17183
- var renderVoiceTelephonyCarrierMatrixHTML = (matrix, options = {}) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 1040px; margin: 40px auto; padding: 0 20px; color: #172033;">
17184
- <p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Carrier matrix</p>
17185
- <h1 style="font-size: 34px; margin: 0 0 8px;">${escapeHtml20(options.title ?? "AbsoluteJS Voice Carrier Matrix")}</h1>
17186
- <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>
17187
- <section style="display:grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 16px;">
17188
- ${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);">
17189
- <div style="display:flex; justify-content:space-between; gap:12px; align-items:center;">
17190
- <h2 style="margin:0; font-size:20px;">${escapeHtml20(entry.name)}</h2>
17191
- <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>
17192
- </div>
17193
- <dl style="display:grid; grid-template-columns: 1fr 1fr; gap:8px 12px; margin:16px 0;">
17194
- <dt style="color:#64748b;">Setup</dt><dd style="margin:0; font-weight:700;">${entry.ready ? "Ready" : "Needs attention"}</dd>
17195
- <dt style="color:#64748b;">Signing</dt><dd style="margin:0; font-weight:700;">${entry.setup.signing.configured ? entry.setup.signing.mode : "missing"}</dd>
17196
- <dt style="color:#64748b;">Smoke</dt><dd style="margin:0; font-weight:700;">${entry.smoke ? entry.smoke.pass ? "Pass" : "Fail" : "Missing"}</dd>
17197
- <dt style="color:#64748b;">Contract</dt><dd style="margin:0; font-weight:700;">${entry.contract.pass ? "Pass" : "Fail"}</dd>
17198
- </dl>
17199
- <p style="margin:0 0 8px; color:#475569;"><strong>Stream:</strong> <code>${escapeHtml20(entry.setup.urls.stream || "missing")}</code></p>
17200
- <p style="margin:0 0 12px; color:#475569;"><strong>Webhook:</strong> <code>${escapeHtml20(entry.setup.urls.webhook || "missing")}</code></p>
17201
- ${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>'}
17202
- </article>`).join("")}
17203
- </section>
17204
- </main>`;
17205
- var createVoiceTelephonyCarrierMatrixRoutes = (options) => {
17206
- const path = options.path ?? "/api/voice/telephony/carriers";
17207
- return new Elysia21({
17208
- name: options.name ?? "absolutejs-voice-telephony-carrier-matrix"
17209
- }).get(path, async ({ query, request }) => {
17210
- const providers = await options.load({ query, request });
17211
- const matrix = createVoiceTelephonyCarrierMatrix({
17212
- contract: options.contract,
17213
- providers
17214
- });
17215
- if (query.format === "html") {
17216
- return new Response(renderVoiceTelephonyCarrierMatrixHTML(matrix, {
17217
- title: options.title
17218
- }), {
17219
- headers: {
17220
- "content-type": "text/html; charset=utf-8"
17221
- }
17222
- });
17223
- }
17224
- return matrix;
17225
- });
17226
- };
17227
17425
  // src/telephony/response.ts
17228
17426
  var normalizeWhitespace = (value) => value.replace(/\s+/g, " ").trim();
17229
17427
  var DEFAULT_MAX_WORDS = 12;
@@ -17345,6 +17543,7 @@ export {
17345
17543
  renderVoiceQualityHTML,
17346
17544
  renderVoiceProviderHealthHTML,
17347
17545
  renderVoiceProviderCapabilityHTML,
17546
+ renderVoiceProductionReadinessHTML,
17348
17547
  renderVoiceOutcomeContractHTML,
17349
17548
  renderVoiceOpsConsoleHTML,
17350
17549
  renderVoiceHandoffHealthHTML,
@@ -17453,6 +17652,7 @@ export {
17453
17652
  createVoiceProviderCapabilityRoutes,
17454
17653
  createVoiceProviderCapabilityJSONHandler,
17455
17654
  createVoiceProviderCapabilityHTMLHandler,
17655
+ createVoiceProductionReadinessRoutes,
17456
17656
  createVoicePostgresTraceSinkDeliveryStore,
17457
17657
  createVoicePostgresTraceEventStore,
17458
17658
  createVoicePostgresTelephonyWebhookIdempotencyStore,
@@ -17556,6 +17756,7 @@ export {
17556
17756
  compareVoiceEvalBaseline,
17557
17757
  claimVoiceOpsTask,
17558
17758
  buildVoiceTraceReplay,
17759
+ buildVoiceProductionReadinessReport,
17559
17760
  buildVoiceOpsTaskFromSLABreach,
17560
17761
  buildVoiceOpsTaskFromReview,
17561
17762
  buildVoiceOpsConsoleReport,
@@ -0,0 +1,103 @@
1
+ import { Elysia } from 'elysia';
2
+ import { type VoiceTelephonyCarrierMatrixInput } from './telephony/matrix';
3
+ import type { VoiceTraceEventStore } from './trace';
4
+ export type VoiceProductionReadinessStatus = 'fail' | 'pass' | 'warn';
5
+ export type VoiceProductionReadinessCheck = {
6
+ detail?: string;
7
+ href?: string;
8
+ label: string;
9
+ status: VoiceProductionReadinessStatus;
10
+ value?: number | string;
11
+ };
12
+ export type VoiceProductionReadinessReport = {
13
+ checkedAt: number;
14
+ checks: VoiceProductionReadinessCheck[];
15
+ links: {
16
+ carriers?: string;
17
+ handoffs?: string;
18
+ quality?: string;
19
+ resilience?: string;
20
+ sessions?: string;
21
+ };
22
+ status: VoiceProductionReadinessStatus;
23
+ summary: {
24
+ carriers?: {
25
+ failing: number;
26
+ providers: number;
27
+ ready: number;
28
+ status: VoiceProductionReadinessStatus;
29
+ warnings: number;
30
+ };
31
+ handoffs: {
32
+ failed: number;
33
+ total: number;
34
+ };
35
+ providers: {
36
+ degraded: number;
37
+ total: number;
38
+ };
39
+ quality: {
40
+ status: 'fail' | 'pass';
41
+ };
42
+ routing: {
43
+ events: number;
44
+ sessions: number;
45
+ };
46
+ sessions: {
47
+ failed: number;
48
+ total: number;
49
+ };
50
+ };
51
+ };
52
+ export type VoiceProductionReadinessRoutesOptions = {
53
+ carriers?: false | readonly VoiceTelephonyCarrierMatrixInput[] | ((input: {
54
+ query: Record<string, unknown>;
55
+ request: Request;
56
+ }) => Promise<readonly VoiceTelephonyCarrierMatrixInput[]> | readonly VoiceTelephonyCarrierMatrixInput[]);
57
+ headers?: HeadersInit;
58
+ htmlPath?: false | string;
59
+ links?: VoiceProductionReadinessReport['links'];
60
+ llmProviders?: readonly string[];
61
+ name?: string;
62
+ path?: string;
63
+ render?: (report: VoiceProductionReadinessReport) => string | Promise<string>;
64
+ store: VoiceTraceEventStore;
65
+ sttProviders?: readonly string[];
66
+ title?: string;
67
+ ttsProviders?: readonly string[];
68
+ };
69
+ export declare const buildVoiceProductionReadinessReport: (options: VoiceProductionReadinessRoutesOptions, input?: {
70
+ query?: Record<string, unknown>;
71
+ request?: Request;
72
+ }) => Promise<VoiceProductionReadinessReport>;
73
+ export declare const renderVoiceProductionReadinessHTML: (report: VoiceProductionReadinessReport, options?: {
74
+ title?: string;
75
+ }) => string;
76
+ export declare const createVoiceProductionReadinessRoutes: (options: VoiceProductionReadinessRoutesOptions) => Elysia<"", {
77
+ decorator: {};
78
+ store: {};
79
+ derive: {};
80
+ resolve: {};
81
+ }, {
82
+ typebox: {};
83
+ error: {};
84
+ }, {
85
+ schema: {};
86
+ standaloneSchema: {};
87
+ macro: {};
88
+ macroFn: {};
89
+ parser: {};
90
+ response: {};
91
+ }, {}, {
92
+ derive: {};
93
+ resolve: {};
94
+ schema: {};
95
+ standaloneSchema: {};
96
+ response: {};
97
+ }, {
98
+ derive: {};
99
+ resolve: {};
100
+ schema: {};
101
+ standaloneSchema: {};
102
+ response: {};
103
+ }>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.87",
3
+ "version": "0.0.22-beta.88",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",