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

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 { VoiceProductionReadinessAction, 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,424 @@ 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
+ actions: quality.status === "pass" ? [] : [
9646
+ {
9647
+ description: "Open the quality report to inspect failing gates.",
9648
+ href: options.links?.quality ?? "/quality",
9649
+ label: "Inspect quality gates"
9650
+ }
9651
+ ]
9652
+ },
9653
+ {
9654
+ detail: degradedProviders === 0 ? "No configured providers are currently degraded." : `${degradedProviders} provider(s) are degraded, suppressed, or rate-limited.`,
9655
+ href: options.links?.resilience ?? "/resilience",
9656
+ label: "Provider health",
9657
+ status: degradedProviders > 0 ? "fail" : "pass",
9658
+ value: degradedProviders,
9659
+ actions: degradedProviders > 0 ? [
9660
+ {
9661
+ description: "Open provider health, fallback state, and recovery controls.",
9662
+ href: options.links?.resilience ?? "/resilience",
9663
+ label: "Open provider recovery"
9664
+ }
9665
+ ] : []
9666
+ },
9667
+ {
9668
+ 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.`,
9669
+ href: options.links?.sessions ?? "/sessions",
9670
+ label: "Session health",
9671
+ status: failedSessions > 0 ? "fail" : sessions.length === 0 ? "warn" : "pass",
9672
+ value: `${sessions.length - failedSessions}/${sessions.length}`,
9673
+ actions: failedSessions > 0 ? [
9674
+ {
9675
+ description: "Open failed sessions and replay traces.",
9676
+ href: `${options.links?.sessions ?? "/sessions"}?status=failed`,
9677
+ label: "Replay failed sessions"
9678
+ }
9679
+ ] : sessions.length === 0 ? [
9680
+ {
9681
+ description: "Open sessions after running a smoke or live call.",
9682
+ href: options.links?.sessions ?? "/sessions",
9683
+ label: "Open sessions"
9684
+ }
9685
+ ] : []
9686
+ },
9687
+ {
9688
+ detail: handoffs.failed === 0 ? "No failed handoff deliveries are recorded." : `${handoffs.failed} handoff delivery failure(s) are recorded.`,
9689
+ href: options.links?.handoffs ?? "/handoffs",
9690
+ label: "Handoff delivery",
9691
+ status: handoffs.failed > 0 ? "fail" : "pass",
9692
+ value: `${handoffs.total - handoffs.failed}/${handoffs.total}`,
9693
+ actions: handoffs.failed > 0 ? [
9694
+ {
9695
+ description: "Retry queued or failed handoff deliveries.",
9696
+ href: options.links?.handoffRetry ?? "/api/voice-handoffs/retry",
9697
+ label: "Retry handoff deliveries",
9698
+ method: "POST"
9699
+ },
9700
+ {
9701
+ description: "Inspect handoff queue and delivery errors.",
9702
+ href: options.links?.handoffs ?? "/handoffs",
9703
+ label: "Open handoff queue"
9704
+ }
9705
+ ] : []
9706
+ },
9707
+ {
9708
+ detail: routingEvents.length > 0 ? `${routingSessions.length} session(s) have provider routing evidence.` : "No provider routing traces are recorded yet.",
9709
+ href: options.links?.resilience ?? "/resilience",
9710
+ label: "Routing evidence",
9711
+ status: routingEvents.length > 0 ? "pass" : "warn",
9712
+ value: routingEvents.length,
9713
+ actions: routingEvents.length > 0 ? [] : [
9714
+ {
9715
+ description: "Open provider routing and run a smoke or simulation to create evidence.",
9716
+ href: options.links?.resilience ?? "/resilience",
9717
+ label: "Open routing evidence"
9718
+ }
9719
+ ]
9720
+ }
9721
+ ];
9722
+ const carrierSummary = carriers ? {
9723
+ failing: carriers.summary.failing,
9724
+ providers: carriers.summary.providers,
9725
+ ready: carriers.summary.ready,
9726
+ status: carrierStatus(carriers),
9727
+ warnings: carriers.summary.warnings
9728
+ } : undefined;
9729
+ if (carriers && carrierSummary) {
9730
+ checks.push({
9731
+ detail: carrierSummary.status === "pass" ? "Configured carrier setup and contract checks are passing." : `${carrierSummary.failing} carrier(s) failing, ${carrierSummary.warnings} warning(s).`,
9732
+ href: options.links?.carriers ?? "/carriers",
9733
+ label: "Carrier readiness",
9734
+ status: carrierSummary.status,
9735
+ value: `${carrierSummary.ready}/${carrierSummary.providers}`,
9736
+ actions: carrierSummary.status === "pass" ? [] : [
9737
+ {
9738
+ description: "Open the carrier matrix for exact missing env, signing, and URL issues.",
9739
+ href: options.links?.carriers ?? "/carriers",
9740
+ label: "Open carrier matrix"
9741
+ }
9742
+ ]
9743
+ });
9744
+ }
9745
+ return {
9746
+ checkedAt: Date.now(),
9747
+ checks,
9748
+ links: {
9749
+ carriers: "/carriers",
9750
+ handoffs: "/handoffs",
9751
+ handoffRetry: "/api/voice-handoffs/retry",
9752
+ quality: "/quality",
9753
+ resilience: "/resilience",
9754
+ sessions: "/sessions",
9755
+ ...options.links
9756
+ },
9757
+ status: rollupStatus(checks),
9758
+ summary: {
9759
+ carriers: carrierSummary,
9760
+ handoffs: {
9761
+ failed: handoffs.failed,
9762
+ total: handoffs.total
9763
+ },
9764
+ providers: {
9765
+ degraded: degradedProviders,
9766
+ total: providers.length
9767
+ },
9768
+ quality: {
9769
+ status: quality.status
9770
+ },
9771
+ routing: {
9772
+ events: routingEvents.length,
9773
+ sessions: routingSessions.length
9774
+ },
9775
+ sessions: {
9776
+ failed: failedSessions,
9777
+ total: sessions.length
9778
+ }
9779
+ }
9780
+ };
9781
+ };
9782
+ var renderVoiceProductionReadinessHTML = (report, options = {}) => {
9783
+ const title = options.title ?? "AbsoluteJS Voice Production Readiness";
9784
+ const checks = report.checks.map((check, index) => {
9785
+ const actions = (check.actions ?? []).map((action) => action.method === "POST" ? `<button type="button" data-readiness-action="${index}" data-action-url="${escapeHtml15(action.href)}">${escapeHtml15(action.label)}</button>` : `<a href="${escapeHtml15(action.href)}">${escapeHtml15(action.label)}</a>`).join("");
9786
+ return `<article class="check ${escapeHtml15(check.status)}">
9787
+ <div>
9788
+ <span>${escapeHtml15(check.status.toUpperCase())}</span>
9789
+ <h2>${escapeHtml15(check.label)}</h2>
9790
+ ${check.detail ? `<p>${escapeHtml15(check.detail)}</p>` : ""}
9791
+ ${actions ? `<p class="actions">${actions}</p>` : ""}
9792
+ </div>
9793
+ <strong>${escapeHtml15(String(check.value ?? check.status))}</strong>
9794
+ ${check.href ? `<a href="${escapeHtml15(check.href)}">Open surface</a>` : ""}
9795
+ </article>`;
9796
+ }).join("");
9797
+ 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}.actions{display:flex;flex-wrap:wrap;gap:10px}.check a,a{color:#fbbf24}button{background:#fbbf24;border:0;border-radius:999px;color:#111827;cursor:pointer;font-weight:800;padding:9px 12px}button:disabled{cursor:wait;opacity:.65}@media(max-width:760px){main{padding:20px}.check{grid-template-columns:1fr}}</style></head><body><main><section class="hero"><p class="eyebrow">Self-hosted readiness</p><h1>${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><script>document.querySelectorAll("[data-readiness-action]").forEach((button)=>{button.addEventListener("click",async()=>{const url=button.getAttribute("data-action-url");if(!url)return;button.disabled=true;const original=button.textContent;button.textContent="Running...";try{const response=await fetch(url,{method:"POST"});button.textContent=response.ok?"Done. Reloading...":"Failed";if(response.ok)setTimeout(()=>location.reload(),500)}catch{button.textContent="Failed"}finally{setTimeout(()=>{button.disabled=false;button.textContent=original},1500)}})});</script></body></html>`;
9798
+ };
9799
+ var createVoiceProductionReadinessRoutes = (options) => {
9800
+ const path = options.path ?? "/api/production-readiness";
9801
+ const htmlPath = options.htmlPath ?? "/production-readiness";
9802
+ const routes = new Elysia13({
9803
+ name: options.name ?? "absolutejs-voice-production-readiness"
9804
+ });
9805
+ routes.get(path, async ({ query, request }) => buildVoiceProductionReadinessReport(options, { query, request }));
9806
+ if (htmlPath !== false) {
9807
+ routes.get(htmlPath, async ({ query, request }) => {
9808
+ const report = await buildVoiceProductionReadinessReport(options, {
9809
+ query,
9810
+ request
9811
+ });
9812
+ const body = await (options.render ?? renderVoiceProductionReadinessHTML)(report);
9813
+ return new Response(body, {
9814
+ headers: {
9815
+ "Content-Type": "text/html; charset=utf-8",
9816
+ ...options.headers
9817
+ }
9818
+ });
9819
+ });
9820
+ }
9821
+ return routes;
9822
+ };
9823
+
9406
9824
  // src/appKit.ts
9407
9825
  var DEFAULT_LINKS2 = [
9408
9826
  {
@@ -9427,6 +9845,12 @@ var DEFAULT_LINKS2 = [
9427
9845
  href: "/resilience",
9428
9846
  label: "Resilience"
9429
9847
  },
9848
+ {
9849
+ description: "One JSON/HTML production readiness rollup.",
9850
+ href: "/production-readiness",
9851
+ label: "Production Readiness",
9852
+ statusHref: "/api/production-readiness"
9853
+ },
9430
9854
  {
9431
9855
  description: "Recent sessions and replay links.",
9432
9856
  href: "/sessions",
@@ -9559,7 +9983,7 @@ var summarizeVoiceAppKitStatus = async (options) => {
9559
9983
  };
9560
9984
  };
9561
9985
  var createVoiceAppKitRoutes = (options) => {
9562
- const routes = new Elysia12({
9986
+ const routes = new Elysia14({
9563
9987
  name: options.name ?? "absolutejs-voice-app-kit"
9564
9988
  });
9565
9989
  const links = resolveLinks(options.links);
@@ -9666,6 +10090,23 @@ var createVoiceAppKitRoutes = (options) => {
9666
10090
  ...options.resilience
9667
10091
  }));
9668
10092
  }
10093
+ if (options.productionReadiness !== false) {
10094
+ surfaces.push("productionReadiness");
10095
+ routes.use(createVoiceProductionReadinessRoutes({
10096
+ ...common,
10097
+ links: {
10098
+ handoffs: "/handoffs",
10099
+ quality: "/quality",
10100
+ resilience: "/resilience",
10101
+ sessions: "/sessions"
10102
+ },
10103
+ llmProviders: options.llmProviders,
10104
+ sttProviders: options.sttProviders,
10105
+ title: options.title ? `${options.title} Production Readiness` : undefined,
10106
+ ttsProviders: options.ttsProviders,
10107
+ ...options.productionReadiness
10108
+ }));
10109
+ }
9669
10110
  if (options.opsConsole !== false) {
9670
10111
  surfaces.push("opsConsole");
9671
10112
  routes.use(createVoiceOpsConsoleRoutes({
@@ -10177,7 +10618,7 @@ var createVoiceToolIdempotencyKey = (input) => {
10177
10618
  ].join(":");
10178
10619
  };
10179
10620
  // src/toolContract.ts
10180
- import { Elysia as Elysia13 } from "elysia";
10621
+ import { Elysia as Elysia15 } from "elysia";
10181
10622
  var createDefaultSession = (contractId, caseId) => createVoiceSessionRecord(`tool-contract-${contractId}-${caseId}`);
10182
10623
  var createDefaultTurn = (caseId) => ({
10183
10624
  committedAt: Date.now(),
@@ -10187,7 +10628,7 @@ var createDefaultTurn = (caseId) => ({
10187
10628
  });
10188
10629
  var defaultApi = {};
10189
10630
  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;");
10631
+ var escapeHtml16 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
10191
10632
  var evaluateExpectation = (input) => {
10192
10633
  const issues = [];
10193
10634
  const expect = input.expect;
@@ -10353,19 +10794,19 @@ var renderVoiceToolContractHTML = (report, options = {}) => {
10353
10794
  const title = options.title ?? "Voice Tool Contracts";
10354
10795
  const contracts = report.contracts.map((contract) => {
10355
10796
  const cases = contract.cases.map((testCase) => `<tr>
10356
- <td>${escapeHtml14(testCase.label ?? testCase.caseId)}</td>
10797
+ <td>${escapeHtml16(testCase.label ?? testCase.caseId)}</td>
10357
10798
  <td class="${testCase.pass ? "pass" : "fail"}">${testCase.pass ? "pass" : "fail"}</td>
10358
- <td>${escapeHtml14(testCase.status)}</td>
10799
+ <td>${escapeHtml16(testCase.status)}</td>
10359
10800
  <td>${String(testCase.attempts)}</td>
10360
10801
  <td>${String(testCase.elapsedMs)}ms</td>
10361
10802
  <td>${testCase.timedOut ? "yes" : "no"}</td>
10362
- <td>${escapeHtml14(testCase.issues.map((issue) => issue.message).join(" ") || testCase.error || "")}</td>
10803
+ <td>${escapeHtml16(testCase.issues.map((issue) => issue.message).join(" ") || testCase.error || "")}</td>
10363
10804
  </tr>`).join("");
10364
10805
  return `<section class="contract ${contract.pass ? "pass" : "fail"}">
10365
10806
  <div class="contract-header">
10366
10807
  <div>
10367
- <p class="eyebrow">${escapeHtml14(contract.toolName)}</p>
10368
- <h2>${escapeHtml14(contract.label ?? contract.contractId)}</h2>
10808
+ <p class="eyebrow">${escapeHtml16(contract.toolName)}</p>
10809
+ <h2>${escapeHtml16(contract.label ?? contract.contractId)}</h2>
10369
10810
  </div>
10370
10811
  <strong class="${contract.pass ? "pass" : "fail"}">${contract.pass ? "Passing" : "Failing"}</strong>
10371
10812
  </div>
@@ -10375,7 +10816,7 @@ var renderVoiceToolContractHTML = (report, options = {}) => {
10375
10816
  </table>
10376
10817
  </section>`;
10377
10818
  }).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>`;
10819
+ 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
10820
  };
10380
10821
  var createVoiceToolContractJSONHandler = (options) => () => runVoiceToolContractSuite(options);
10381
10822
  var createVoiceToolContractHTMLHandler = (options) => async () => {
@@ -10392,7 +10833,7 @@ var createVoiceToolContractHTMLHandler = (options) => async () => {
10392
10833
  var createVoiceToolContractRoutes = (options) => {
10393
10834
  const path = options.path ?? "/api/tool-contracts";
10394
10835
  const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
10395
- const routes = new Elysia13({
10836
+ const routes = new Elysia15({
10396
10837
  name: options.name ?? "absolutejs-voice-tool-contracts"
10397
10838
  }).get(path, createVoiceToolContractJSONHandler(options));
10398
10839
  if (htmlPath) {
@@ -10401,9 +10842,9 @@ var createVoiceToolContractRoutes = (options) => {
10401
10842
  return routes;
10402
10843
  };
10403
10844
  // src/turnQuality.ts
10404
- import { Elysia as Elysia14 } from "elysia";
10845
+ import { Elysia as Elysia16 } from "elysia";
10405
10846
  var DEFAULT_CONFIDENCE_WARN_THRESHOLD = 0.72;
10406
- var escapeHtml15 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
10847
+ var escapeHtml17 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
10407
10848
  var getTurnLatencyMs = (turn) => {
10408
10849
  const firstTranscriptAt = turn.transcripts.map((transcript) => transcript.endedAtMs ?? transcript.startedAtMs).filter((value) => typeof value === "number").sort((left, right) => left - right)[0];
10409
10850
  if (firstTranscriptAt === undefined) {
@@ -10474,24 +10915,24 @@ var summarizeVoiceTurnQuality = async (options) => {
10474
10915
  };
10475
10916
  var renderVoiceTurnQualityHTML = (report, options = {}) => {
10476
10917
  const title = options.title ?? "Voice Turn Quality";
10477
- const turns = report.turns.map((turn) => `<article class="turn ${escapeHtml15(turn.status)}">
10918
+ const turns = report.turns.map((turn) => `<article class="turn ${escapeHtml17(turn.status)}">
10478
10919
  <div class="turn-header">
10479
10920
  <div>
10480
- <p class="eyebrow">${escapeHtml15(turn.sessionId)} \xB7 ${escapeHtml15(turn.turnId)}</p>
10481
- <h2>${escapeHtml15(turn.text || "Empty turn")}</h2>
10921
+ <p class="eyebrow">${escapeHtml17(turn.sessionId)} \xB7 ${escapeHtml17(turn.turnId)}</p>
10922
+ <h2>${escapeHtml17(turn.text || "Empty turn")}</h2>
10482
10923
  </div>
10483
- <strong>${escapeHtml15(turn.status)}</strong>
10924
+ <strong>${escapeHtml17(turn.status)}</strong>
10484
10925
  </div>
10485
10926
  <dl>
10486
- <div><dt>Source</dt><dd>${escapeHtml15(turn.source ?? "unknown")}</dd></div>
10927
+ <div><dt>Source</dt><dd>${escapeHtml17(turn.source ?? "unknown")}</dd></div>
10487
10928
  <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>
10929
+ <div><dt>Fallback</dt><dd>${turn.fallbackUsed ? `yes (${escapeHtml17(turn.fallbackSelectionReason ?? "selected")})` : "no"}</dd></div>
10930
+ <div><dt>Correction</dt><dd>${turn.correctionChanged ? `changed${turn.correctionProvider ? ` by ${escapeHtml17(turn.correctionProvider)}` : ""}` : "none"}</dd></div>
10490
10931
  <div><dt>Transcripts</dt><dd>${String(turn.selectedTranscriptCount)} selected \xB7 ${String(turn.finalTranscriptCount)} final \xB7 ${String(turn.partialTranscriptCount)} partial</dd></div>
10491
10932
  <div><dt>Cost</dt><dd>${turn.costUnits === undefined ? "n/a" : String(turn.costUnits)}</dd></div>
10492
10933
  </dl>
10493
10934
  </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>`;
10935
+ 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
10936
  };
10496
10937
  var createVoiceTurnQualityJSONHandler = (options) => async () => summarizeVoiceTurnQuality(options);
10497
10938
  var createVoiceTurnQualityHTMLHandler = (options) => async () => {
@@ -10508,7 +10949,7 @@ var createVoiceTurnQualityHTMLHandler = (options) => async () => {
10508
10949
  var createVoiceTurnQualityRoutes = (options) => {
10509
10950
  const path = options.path ?? "/api/turn-quality";
10510
10951
  const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
10511
- const routes = new Elysia14({
10952
+ const routes = new Elysia16({
10512
10953
  name: options.name ?? "absolutejs-voice-turn-quality"
10513
10954
  }).get(path, createVoiceTurnQualityJSONHandler(options));
10514
10955
  if (htmlPath) {
@@ -10517,8 +10958,8 @@ var createVoiceTurnQualityRoutes = (options) => {
10517
10958
  return routes;
10518
10959
  };
10519
10960
  // 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;");
10961
+ import { Elysia as Elysia17 } from "elysia";
10962
+ var escapeHtml18 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
10522
10963
  var getPayloadString = (event, key) => typeof event.payload[key] === "string" ? event.payload[key] : undefined;
10523
10964
  var toList = async (input) => Array.isArray(input) ? input : await input?.list() ?? [];
10524
10965
  var hydrateSessions = async (input) => {
@@ -10626,9 +11067,9 @@ var renderVoiceOutcomeContractHTML = (report, options = {}) => {
10626
11067
  const contracts = report.contracts.map((contract) => `<section class="contract ${contract.pass ? "pass" : "fail"}">
10627
11068
  <div class="contract-header">
10628
11069
  <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>` : ""}
11070
+ <p class="eyebrow">${escapeHtml18(contract.contractId)}</p>
11071
+ <h2>${escapeHtml18(contract.label ?? contract.contractId)}</h2>
11072
+ ${contract.description ? `<p>${escapeHtml18(contract.description)}</p>` : ""}
10632
11073
  </div>
10633
11074
  <strong>${contract.pass ? "pass" : "fail"}</strong>
10634
11075
  </div>
@@ -10639,9 +11080,9 @@ var renderVoiceOutcomeContractHTML = (report, options = {}) => {
10639
11080
  <span>handoffs ${String(contract.matched.handoffs)}</span>
10640
11081
  <span>events ${String(contract.matched.integrationEvents)}</span>
10641
11082
  </div>
10642
- ${contract.issues.length ? `<ul>${contract.issues.map((issue) => `<li>${escapeHtml16(issue.message)}</li>`).join("")}</ul>` : ""}
11083
+ ${contract.issues.length ? `<ul>${contract.issues.map((issue) => `<li>${escapeHtml18(issue.message)}</li>`).join("")}</ul>` : ""}
10643
11084
  </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>`;
11085
+ 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
11086
  };
10646
11087
  var createVoiceOutcomeContractJSONHandler = (options) => async () => runVoiceOutcomeContractSuite(options);
10647
11088
  var createVoiceOutcomeContractHTMLHandler = (options) => async () => {
@@ -10657,7 +11098,7 @@ var createVoiceOutcomeContractHTMLHandler = (options) => async () => {
10657
11098
  var createVoiceOutcomeContractRoutes = (options) => {
10658
11099
  const path = options.path ?? "/api/outcome-contracts";
10659
11100
  const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
10660
- const routes = new Elysia15({
11101
+ const routes = new Elysia17({
10661
11102
  name: options.name ?? "absolutejs-voice-outcome-contracts"
10662
11103
  }).get(path, createVoiceOutcomeContractJSONHandler(options));
10663
11104
  if (htmlPath) {
@@ -10666,7 +11107,7 @@ var createVoiceOutcomeContractRoutes = (options) => {
10666
11107
  return routes;
10667
11108
  };
10668
11109
  // src/telephonyOutcome.ts
10669
- import { Elysia as Elysia16 } from "elysia";
11110
+ import { Elysia as Elysia18 } from "elysia";
10670
11111
  var DEFAULT_COMPLETED_STATUSES = [
10671
11112
  "answered",
10672
11113
  "completed",
@@ -11313,7 +11754,7 @@ var createVoiceTelephonyWebhookHandler = (options = {}) => async (input) => {
11313
11754
  var createVoiceTelephonyWebhookRoutes = (options = {}) => {
11314
11755
  const path = options.path ?? "/api/voice/telephony/webhook";
11315
11756
  const handler = createVoiceTelephonyWebhookHandler(options);
11316
- return new Elysia16({
11757
+ return new Elysia18({
11317
11758
  name: options.name ?? "absolutejs-voice-telephony-webhooks"
11318
11759
  }).post(path, async ({ query, request }) => {
11319
11760
  try {
@@ -13581,7 +14022,7 @@ var createVoiceMemoryStore = () => {
13581
14022
  return { get, getOrCreate, list, remove, set };
13582
14023
  };
13583
14024
  // src/opsWebhook.ts
13584
- import { Elysia as Elysia17 } from "elysia";
14025
+ import { Elysia as Elysia19 } from "elysia";
13585
14026
  var toHex5 = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
13586
14027
  var signVoiceOpsWebhookBody = async (input) => {
13587
14028
  const encoder = new TextEncoder;
@@ -13711,7 +14152,7 @@ var verifyVoiceOpsWebhookSignature = async (input) => {
13711
14152
  };
13712
14153
  var createVoiceOpsWebhookReceiverRoutes = (options = {}) => {
13713
14154
  const path = options.path ?? "/api/voice-ops/webhook";
13714
- return new Elysia17().post(path, async ({ body, request, set }) => {
14155
+ return new Elysia19().post(path, async ({ body, request, set }) => {
13715
14156
  const bodyText = typeof body === "string" ? body : JSON.stringify(body);
13716
14157
  if (options.signingSecret) {
13717
14158
  const verification = await verifyVoiceOpsWebhookSignature({
@@ -15440,7 +15881,7 @@ var createVoiceSTTRoutingCorrectionHandler = (mode = "generic") => {
15440
15881
  };
15441
15882
  // src/telephony/twilio.ts
15442
15883
  import { Buffer as Buffer3 } from "buffer";
15443
- import { Elysia as Elysia18 } from "elysia";
15884
+ import { Elysia as Elysia20 } from "elysia";
15444
15885
  var TWILIO_MULAW_SAMPLE_RATE = 8000;
15445
15886
  var VOICE_PCM_SAMPLE_RATE = 16000;
15446
15887
  var escapeXml2 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
@@ -15470,7 +15911,7 @@ var resolveTwilioStreamParameters = async (parameters, input) => {
15470
15911
  return parameters;
15471
15912
  };
15472
15913
  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;");
15914
+ var escapeHtml19 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&#39;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
15474
15915
  var getWebhookVerificationUrl = (webhook, input) => {
15475
15916
  if (!webhook?.verificationUrl) {
15476
15917
  return;
@@ -15513,23 +15954,23 @@ var buildTwilioVoiceSetupStatus = async (options, input) => {
15513
15954
  };
15514
15955
  var renderTwilioVoiceSetupHTML = (status, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
15515
15956
  <p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Twilio setup</p>
15516
- <h1>${escapeHtml17(title)}</h1>
15957
+ <h1>${escapeHtml19(title)}</h1>
15517
15958
  <p><strong>Status:</strong> ${status.ready ? "Ready" : "Needs attention"}</p>
15518
15959
  <section>
15519
15960
  <h2>URLs</h2>
15520
15961
  <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>
15962
+ <li><strong>TwiML:</strong> <code>${escapeHtml19(status.urls.twiml)}</code></li>
15963
+ <li><strong>Media stream:</strong> <code>${escapeHtml19(status.urls.stream)}</code></li>
15964
+ <li><strong>Status webhook:</strong> <code>${escapeHtml19(status.urls.webhook)}</code></li>
15524
15965
  </ul>
15525
15966
  </section>
15526
15967
  <section>
15527
15968
  <h2>Signing</h2>
15528
15969
  <p>Mode: <code>${status.signing.mode}</code></p>
15529
- ${status.signing.verificationUrl ? `<p>Verification URL: <code>${escapeHtml17(status.signing.verificationUrl)}</code></p>` : ""}
15970
+ ${status.signing.verificationUrl ? `<p>Verification URL: <code>${escapeHtml19(status.signing.verificationUrl)}</code></p>` : ""}
15530
15971
  </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>` : ""}
15972
+ ${status.missing.length ? `<section><h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml19(name)}</code></li>`).join("")}</ul></section>` : ""}
15973
+ ${status.warnings.length ? `<section><h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml19(warning)}</li>`).join("")}</ul></section>` : ""}
15533
15974
  </main>`;
15534
15975
  var extractTwilioStreamUrl = (twiml) => twiml.match(/<Stream\b[^>]*\surl="([^"]+)"/i)?.[1]?.replaceAll("&amp;", "&");
15535
15976
  var createSmokeCheck = (name, status, message, details) => ({
@@ -15540,20 +15981,20 @@ var createSmokeCheck = (name, status, message, details) => ({
15540
15981
  });
15541
15982
  var renderTwilioVoiceSmokeHTML = (report, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
15542
15983
  <p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Twilio smoke test</p>
15543
- <h1>${escapeHtml17(title)}</h1>
15984
+ <h1>${escapeHtml19(title)}</h1>
15544
15985
  <p><strong>Status:</strong> ${report.pass ? "Pass" : "Fail"}</p>
15545
15986
  <section>
15546
15987
  <h2>Checks</h2>
15547
15988
  <ul>
15548
- ${report.checks.map((check) => `<li><strong>${escapeHtml17(check.name)}</strong>: ${escapeHtml17(check.status)}${check.message ? ` - ${escapeHtml17(check.message)}` : ""}</li>`).join("")}
15989
+ ${report.checks.map((check) => `<li><strong>${escapeHtml19(check.name)}</strong>: ${escapeHtml19(check.status)}${check.message ? ` - ${escapeHtml19(check.message)}` : ""}</li>`).join("")}
15549
15990
  </ul>
15550
15991
  </section>
15551
15992
  <section>
15552
15993
  <h2>Observed URLs</h2>
15553
15994
  <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>
15995
+ <li><strong>TwiML:</strong> <code>${escapeHtml19(report.setup.urls.twiml)}</code></li>
15996
+ <li><strong>Stream:</strong> <code>${escapeHtml19(report.twiml?.streamUrl ?? report.setup.urls.stream)}</code></li>
15997
+ <li><strong>Webhook:</strong> <code>${escapeHtml19(report.setup.urls.webhook)}</code></li>
15557
15998
  </ul>
15558
15999
  </section>
15559
16000
  </main>`;
@@ -16013,7 +16454,7 @@ var createTwilioVoiceRoutes = (options) => {
16013
16454
  const smokePath = options.smoke?.path === false ? false : options.smoke?.path ?? "/api/voice/twilio/smoke";
16014
16455
  const bridges = new WeakMap;
16015
16456
  const webhookPolicy = options.webhook?.policy ?? options.outcomePolicy ?? createVoiceTelephonyOutcomePolicy();
16016
- const app = new Elysia18({
16457
+ const app = new Elysia20({
16017
16458
  name: options.name ?? "absolutejs-voice-twilio"
16018
16459
  }).get(twimlPath, async ({ query, request }) => {
16019
16460
  const streamUrl = await resolveTwilioStreamUrl(options, {
@@ -16147,90 +16588,11 @@ var createTwilioVoiceRoutes = (options) => {
16147
16588
  return report;
16148
16589
  });
16149
16590
  };
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
16591
  // src/telephony/telnyx.ts
16230
16592
  import { Buffer as Buffer4 } from "buffer";
16231
- import { Elysia as Elysia19 } from "elysia";
16593
+ import { Elysia as Elysia21 } from "elysia";
16232
16594
  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;");
16595
+ var escapeHtml20 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&#39;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
16234
16596
  var joinUrlPath2 = (origin, path) => `${origin.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
16235
16597
  var resolveRequestOrigin2 = (request) => {
16236
16598
  const url = new URL(request.url);
@@ -16431,21 +16793,21 @@ var buildTelnyxVoiceSetupStatus = async (options, input) => {
16431
16793
  };
16432
16794
  var renderTelnyxSetupHTML = (status, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
16433
16795
  <p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Telnyx setup</p>
16434
- <h1>${escapeHtml18(title)}</h1>
16796
+ <h1>${escapeHtml20(title)}</h1>
16435
16797
  <p><strong>Status:</strong> ${status.ready ? "Ready" : "Needs attention"}</p>
16436
16798
  <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>
16799
+ <li><strong>TeXML:</strong> <code>${escapeHtml20(status.urls.texml)}</code></li>
16800
+ <li><strong>Media stream:</strong> <code>${escapeHtml20(status.urls.stream)}</code></li>
16801
+ <li><strong>Status webhook:</strong> <code>${escapeHtml20(status.urls.webhook)}</code></li>
16440
16802
  </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>` : ""}
16803
+ ${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml20(name)}</code></li>`).join("")}</ul>` : ""}
16804
+ ${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml20(warning)}</li>`).join("")}</ul>` : ""}
16443
16805
  </main>`;
16444
16806
  var renderTelnyxSmokeHTML = (report, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
16445
16807
  <p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Telnyx smoke test</p>
16446
- <h1>${escapeHtml18(title)}</h1>
16808
+ <h1>${escapeHtml20(title)}</h1>
16447
16809
  <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>
16810
+ <ul>${report.checks.map((check) => `<li><strong>${escapeHtml20(check.name)}</strong>: ${escapeHtml20(check.status)}${check.message ? ` - ${escapeHtml20(check.message)}` : ""}</li>`).join("")}</ul>
16449
16811
  </main>`;
16450
16812
  var runTelnyxSmokeTest = async (input) => {
16451
16813
  const setup = await buildTelnyxVoiceSetupStatus(input.options, input);
@@ -16539,7 +16901,7 @@ var createTelnyxVoiceRoutes = (options = {}) => {
16539
16901
  publicKey: options.webhook?.publicKey,
16540
16902
  toleranceSeconds: options.webhook?.toleranceSeconds
16541
16903
  }) : undefined);
16542
- const app = new Elysia19({
16904
+ const app = new Elysia21({
16543
16905
  name: options.name ?? "absolutejs-voice-telnyx"
16544
16906
  }).get(texmlPath, async ({ query, request }) => {
16545
16907
  const streamUrl = await resolveTelnyxStreamUrl(options, {
@@ -16649,9 +17011,9 @@ var createTelnyxVoiceRoutes = (options = {}) => {
16649
17011
  };
16650
17012
  // src/telephony/plivo.ts
16651
17013
  import { Buffer as Buffer5 } from "buffer";
16652
- import { Elysia as Elysia20 } from "elysia";
17014
+ import { Elysia as Elysia22 } from "elysia";
16653
17015
  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;");
17016
+ var escapeHtml21 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&#39;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
16655
17017
  var joinUrlPath3 = (origin, path) => `${origin.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
16656
17018
  var resolveRequestOrigin3 = (request) => {
16657
17019
  const url = new URL(request.url);
@@ -16902,21 +17264,21 @@ var buildPlivoVoiceSetupStatus = async (options, input) => {
16902
17264
  };
16903
17265
  var renderPlivoSetupHTML = (status, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
16904
17266
  <p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Plivo setup</p>
16905
- <h1>${escapeHtml19(title)}</h1>
17267
+ <h1>${escapeHtml21(title)}</h1>
16906
17268
  <p><strong>Status:</strong> ${status.ready ? "Ready" : "Needs attention"}</p>
16907
17269
  <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>
17270
+ <li><strong>Answer XML:</strong> <code>${escapeHtml21(status.urls.answer)}</code></li>
17271
+ <li><strong>Audio stream:</strong> <code>${escapeHtml21(status.urls.stream)}</code></li>
17272
+ <li><strong>Status webhook:</strong> <code>${escapeHtml21(status.urls.webhook)}</code></li>
16911
17273
  </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>` : ""}
17274
+ ${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml21(name)}</code></li>`).join("")}</ul>` : ""}
17275
+ ${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml21(warning)}</li>`).join("")}</ul>` : ""}
16914
17276
  </main>`;
16915
17277
  var renderPlivoSmokeHTML = (report, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
16916
17278
  <p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Plivo smoke test</p>
16917
- <h1>${escapeHtml19(title)}</h1>
17279
+ <h1>${escapeHtml21(title)}</h1>
16918
17280
  <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>
17281
+ <ul>${report.checks.map((check) => `<li><strong>${escapeHtml21(check.name)}</strong>: ${escapeHtml21(check.status)}${check.message ? ` - ${escapeHtml21(check.message)}` : ""}</li>`).join("")}</ul>
16920
17282
  </main>`;
16921
17283
  var runPlivoSmokeTest = async (input) => {
16922
17284
  const setup = await buildPlivoVoiceSetupStatus(input.options, input);
@@ -17011,7 +17373,7 @@ var createPlivoVoiceRoutes = (options = {}) => {
17011
17373
  request: input.request
17012
17374
  }) : verificationUrl ?? input.request.url
17013
17375
  }) : undefined);
17014
- const app = new Elysia20({
17376
+ const app = new Elysia22({
17015
17377
  name: options.name ?? "absolutejs-voice-plivo"
17016
17378
  }).get(answerPath, async ({ query, request }) => {
17017
17379
  const streamUrl = await resolvePlivoStreamUrl(options, {
@@ -17119,111 +17481,6 @@ var createPlivoVoiceRoutes = (options = {}) => {
17119
17481
  return report;
17120
17482
  });
17121
17483
  };
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
17484
  // src/telephony/response.ts
17228
17485
  var normalizeWhitespace = (value) => value.replace(/\s+/g, " ").trim();
17229
17486
  var DEFAULT_MAX_WORDS = 12;
@@ -17345,6 +17602,7 @@ export {
17345
17602
  renderVoiceQualityHTML,
17346
17603
  renderVoiceProviderHealthHTML,
17347
17604
  renderVoiceProviderCapabilityHTML,
17605
+ renderVoiceProductionReadinessHTML,
17348
17606
  renderVoiceOutcomeContractHTML,
17349
17607
  renderVoiceOpsConsoleHTML,
17350
17608
  renderVoiceHandoffHealthHTML,
@@ -17453,6 +17711,7 @@ export {
17453
17711
  createVoiceProviderCapabilityRoutes,
17454
17712
  createVoiceProviderCapabilityJSONHandler,
17455
17713
  createVoiceProviderCapabilityHTMLHandler,
17714
+ createVoiceProductionReadinessRoutes,
17456
17715
  createVoicePostgresTraceSinkDeliveryStore,
17457
17716
  createVoicePostgresTraceEventStore,
17458
17717
  createVoicePostgresTelephonyWebhookIdempotencyStore,
@@ -17556,6 +17815,7 @@ export {
17556
17815
  compareVoiceEvalBaseline,
17557
17816
  claimVoiceOpsTask,
17558
17817
  buildVoiceTraceReplay,
17818
+ buildVoiceProductionReadinessReport,
17559
17819
  buildVoiceOpsTaskFromSLABreach,
17560
17820
  buildVoiceOpsTaskFromReview,
17561
17821
  buildVoiceOpsConsoleReport,
@@ -0,0 +1,111 @@
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 VoiceProductionReadinessAction = {
6
+ description?: string;
7
+ href: string;
8
+ label: string;
9
+ method?: 'GET' | 'POST';
10
+ };
11
+ export type VoiceProductionReadinessCheck = {
12
+ actions?: VoiceProductionReadinessAction[];
13
+ detail?: string;
14
+ href?: string;
15
+ label: string;
16
+ status: VoiceProductionReadinessStatus;
17
+ value?: number | string;
18
+ };
19
+ export type VoiceProductionReadinessReport = {
20
+ checkedAt: number;
21
+ checks: VoiceProductionReadinessCheck[];
22
+ links: {
23
+ carriers?: string;
24
+ handoffs?: string;
25
+ handoffRetry?: string;
26
+ quality?: string;
27
+ resilience?: string;
28
+ sessions?: string;
29
+ };
30
+ status: VoiceProductionReadinessStatus;
31
+ summary: {
32
+ carriers?: {
33
+ failing: number;
34
+ providers: number;
35
+ ready: number;
36
+ status: VoiceProductionReadinessStatus;
37
+ warnings: number;
38
+ };
39
+ handoffs: {
40
+ failed: number;
41
+ total: number;
42
+ };
43
+ providers: {
44
+ degraded: number;
45
+ total: number;
46
+ };
47
+ quality: {
48
+ status: 'fail' | 'pass';
49
+ };
50
+ routing: {
51
+ events: number;
52
+ sessions: number;
53
+ };
54
+ sessions: {
55
+ failed: number;
56
+ total: number;
57
+ };
58
+ };
59
+ };
60
+ export type VoiceProductionReadinessRoutesOptions = {
61
+ carriers?: false | readonly VoiceTelephonyCarrierMatrixInput[] | ((input: {
62
+ query: Record<string, unknown>;
63
+ request: Request;
64
+ }) => Promise<readonly VoiceTelephonyCarrierMatrixInput[]> | readonly VoiceTelephonyCarrierMatrixInput[]);
65
+ headers?: HeadersInit;
66
+ htmlPath?: false | string;
67
+ links?: VoiceProductionReadinessReport['links'];
68
+ llmProviders?: readonly string[];
69
+ name?: string;
70
+ path?: string;
71
+ render?: (report: VoiceProductionReadinessReport) => string | Promise<string>;
72
+ store: VoiceTraceEventStore;
73
+ sttProviders?: readonly string[];
74
+ title?: string;
75
+ ttsProviders?: readonly string[];
76
+ };
77
+ export declare const buildVoiceProductionReadinessReport: (options: VoiceProductionReadinessRoutesOptions, input?: {
78
+ query?: Record<string, unknown>;
79
+ request?: Request;
80
+ }) => Promise<VoiceProductionReadinessReport>;
81
+ export declare const renderVoiceProductionReadinessHTML: (report: VoiceProductionReadinessReport, options?: {
82
+ title?: string;
83
+ }) => string;
84
+ export declare const createVoiceProductionReadinessRoutes: (options: VoiceProductionReadinessRoutesOptions) => Elysia<"", {
85
+ decorator: {};
86
+ store: {};
87
+ derive: {};
88
+ resolve: {};
89
+ }, {
90
+ typebox: {};
91
+ error: {};
92
+ }, {
93
+ schema: {};
94
+ standaloneSchema: {};
95
+ macro: {};
96
+ macroFn: {};
97
+ parser: {};
98
+ response: {};
99
+ }, {}, {
100
+ derive: {};
101
+ resolve: {};
102
+ schema: {};
103
+ standaloneSchema: {};
104
+ response: {};
105
+ }, {
106
+ derive: {};
107
+ resolve: {};
108
+ schema: {};
109
+ standaloneSchema: {};
110
+ response: {};
111
+ }>;
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.89",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",