@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 +3 -1
- package/dist/client/opsStatusWidget.d.ts +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +515 -255
- package/dist/productionReadiness.d.ts +111 -0
- package/package.json +1 -1
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) => "
|
|
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
|
|
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("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
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("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
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
|
|
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
|
|
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
|
|
10631
|
+
var escapeHtml16 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
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>${
|
|
10797
|
+
<td>${escapeHtml16(testCase.label ?? testCase.caseId)}</td>
|
|
10357
10798
|
<td class="${testCase.pass ? "pass" : "fail"}">${testCase.pass ? "pass" : "fail"}</td>
|
|
10358
|
-
<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>${
|
|
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">${
|
|
10368
|
-
<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>${
|
|
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
|
|
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
|
|
10845
|
+
import { Elysia as Elysia16 } from "elysia";
|
|
10405
10846
|
var DEFAULT_CONFIDENCE_WARN_THRESHOLD = 0.72;
|
|
10406
|
-
var
|
|
10847
|
+
var escapeHtml17 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
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 ${
|
|
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">${
|
|
10481
|
-
<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>${
|
|
10924
|
+
<strong>${escapeHtml17(turn.status)}</strong>
|
|
10484
10925
|
</div>
|
|
10485
10926
|
<dl>
|
|
10486
|
-
<div><dt>Source</dt><dd>${
|
|
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 (${
|
|
10489
|
-
<div><dt>Correction</dt><dd>${turn.correctionChanged ? `changed${turn.correctionProvider ? ` by ${
|
|
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>${
|
|
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
|
|
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
|
|
10521
|
-
var
|
|
10961
|
+
import { Elysia as Elysia17 } from "elysia";
|
|
10962
|
+
var escapeHtml18 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
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">${
|
|
10630
|
-
<h2>${
|
|
10631
|
-
${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>${
|
|
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>${
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
@@ -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
|
|
15914
|
+
var escapeHtml19 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
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>${
|
|
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>${
|
|
15522
|
-
<li><strong>Media stream:</strong> <code>${
|
|
15523
|
-
<li><strong>Status webhook:</strong> <code>${
|
|
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>${
|
|
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>${
|
|
15532
|
-
${status.warnings.length ? `<section><h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${
|
|
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("&", "&");
|
|
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>${
|
|
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>${
|
|
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>${
|
|
15555
|
-
<li><strong>Stream:</strong> <code>${
|
|
15556
|
-
<li><strong>Webhook:</strong> <code>${
|
|
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
|
|
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
|
|
16593
|
+
import { Elysia as Elysia21 } from "elysia";
|
|
16232
16594
|
var escapeXml3 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
16233
|
-
var
|
|
16595
|
+
var escapeHtml20 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
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>${
|
|
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>${
|
|
16438
|
-
<li><strong>Media stream:</strong> <code>${
|
|
16439
|
-
<li><strong>Status webhook:</strong> <code>${
|
|
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>${
|
|
16442
|
-
${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${
|
|
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>${
|
|
16808
|
+
<h1>${escapeHtml20(title)}</h1>
|
|
16447
16809
|
<p><strong>Status:</strong> ${report.pass ? "Pass" : "Fail"}</p>
|
|
16448
|
-
<ul>${report.checks.map((check) => `<li><strong>${
|
|
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
|
|
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
|
|
17014
|
+
import { Elysia as Elysia22 } from "elysia";
|
|
16653
17015
|
var escapeXml4 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
16654
|
-
var
|
|
17016
|
+
var escapeHtml21 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
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>${
|
|
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>${
|
|
16909
|
-
<li><strong>Audio stream:</strong> <code>${
|
|
16910
|
-
<li><strong>Status webhook:</strong> <code>${
|
|
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>${
|
|
16913
|
-
${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${
|
|
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>${
|
|
17279
|
+
<h1>${escapeHtml21(title)}</h1>
|
|
16918
17280
|
<p><strong>Status:</strong> ${report.pass ? "Pass" : "Fail"}</p>
|
|
16919
|
-
<ul>${report.checks.map((check) => `<li><strong>${
|
|
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
|
|
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("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
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
|
+
}>;
|