@absolutejs/voice 0.0.22-beta.87 → 0.0.22-beta.88
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/appKit.d.ts +3 -1
- package/dist/client/opsStatusWidget.d.ts +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +456 -255
- package/dist/productionReadiness.d.ts +103 -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 { 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,365 @@ var createVoiceProviderCapabilityRoutes = (options) => {
|
|
|
9403
9403
|
return routes;
|
|
9404
9404
|
};
|
|
9405
9405
|
|
|
9406
|
+
// src/productionReadiness.ts
|
|
9407
|
+
import { Elysia as Elysia13 } from "elysia";
|
|
9408
|
+
|
|
9409
|
+
// src/telephony/matrix.ts
|
|
9410
|
+
import { Elysia as Elysia12 } from "elysia";
|
|
9411
|
+
|
|
9412
|
+
// src/telephony/contract.ts
|
|
9413
|
+
var DEFAULT_REQUIREMENTS = [
|
|
9414
|
+
"stream-url",
|
|
9415
|
+
"wss-stream",
|
|
9416
|
+
"webhook-url",
|
|
9417
|
+
"signed-webhook",
|
|
9418
|
+
"smoke-pass"
|
|
9419
|
+
];
|
|
9420
|
+
var hasFailingSmokeCheck = (smoke) => smoke?.checks.some((check) => check.status === "fail") ?? false;
|
|
9421
|
+
var evaluateVoiceTelephonyContract = (input) => {
|
|
9422
|
+
const requirements = input.options?.requirements ?? DEFAULT_REQUIREMENTS;
|
|
9423
|
+
const issues = [];
|
|
9424
|
+
const hasRequirement = (requirement) => requirements.includes(requirement);
|
|
9425
|
+
if (hasRequirement("stream-url") && !input.setup.urls.stream) {
|
|
9426
|
+
issues.push({
|
|
9427
|
+
message: "Missing media stream URL.",
|
|
9428
|
+
requirement: "stream-url",
|
|
9429
|
+
severity: "error"
|
|
9430
|
+
});
|
|
9431
|
+
}
|
|
9432
|
+
if (hasRequirement("wss-stream") && !input.setup.urls.stream.startsWith("wss://")) {
|
|
9433
|
+
issues.push({
|
|
9434
|
+
message: "Media stream URL must use wss://.",
|
|
9435
|
+
requirement: "wss-stream",
|
|
9436
|
+
severity: "error"
|
|
9437
|
+
});
|
|
9438
|
+
}
|
|
9439
|
+
if (hasRequirement("webhook-url") && !input.setup.urls.webhook) {
|
|
9440
|
+
issues.push({
|
|
9441
|
+
message: "Missing carrier webhook URL.",
|
|
9442
|
+
requirement: "webhook-url",
|
|
9443
|
+
severity: "error"
|
|
9444
|
+
});
|
|
9445
|
+
}
|
|
9446
|
+
if (hasRequirement("signed-webhook") && !input.setup.signing.configured) {
|
|
9447
|
+
issues.push({
|
|
9448
|
+
message: "Carrier webhook signature verification is not configured.",
|
|
9449
|
+
requirement: "signed-webhook",
|
|
9450
|
+
severity: "error"
|
|
9451
|
+
});
|
|
9452
|
+
}
|
|
9453
|
+
if (hasRequirement("smoke-pass")) {
|
|
9454
|
+
if (!input.smoke) {
|
|
9455
|
+
issues.push({
|
|
9456
|
+
message: "Missing telephony smoke test report.",
|
|
9457
|
+
requirement: "smoke-pass",
|
|
9458
|
+
severity: "error"
|
|
9459
|
+
});
|
|
9460
|
+
} else if (!input.smoke.pass || hasFailingSmokeCheck(input.smoke)) {
|
|
9461
|
+
issues.push({
|
|
9462
|
+
message: "Telephony smoke test did not pass.",
|
|
9463
|
+
requirement: "smoke-pass",
|
|
9464
|
+
severity: "error"
|
|
9465
|
+
});
|
|
9466
|
+
}
|
|
9467
|
+
}
|
|
9468
|
+
for (const warning of input.setup.warnings) {
|
|
9469
|
+
issues.push({
|
|
9470
|
+
message: warning,
|
|
9471
|
+
requirement: "stream-url",
|
|
9472
|
+
severity: "warning"
|
|
9473
|
+
});
|
|
9474
|
+
}
|
|
9475
|
+
for (const name of input.setup.missing) {
|
|
9476
|
+
issues.push({
|
|
9477
|
+
message: `${name} is missing.`,
|
|
9478
|
+
requirement: "webhook-url",
|
|
9479
|
+
severity: "error"
|
|
9480
|
+
});
|
|
9481
|
+
}
|
|
9482
|
+
return {
|
|
9483
|
+
issues,
|
|
9484
|
+
pass: issues.every((issue) => issue.severity !== "error"),
|
|
9485
|
+
provider: input.setup.provider,
|
|
9486
|
+
requirements,
|
|
9487
|
+
setup: input.setup,
|
|
9488
|
+
smoke: input.smoke
|
|
9489
|
+
};
|
|
9490
|
+
};
|
|
9491
|
+
|
|
9492
|
+
// src/telephony/matrix.ts
|
|
9493
|
+
var escapeHtml14 = (value) => value.replaceAll("&", "&").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
|
+
},
|
|
9646
|
+
{
|
|
9647
|
+
detail: degradedProviders === 0 ? "No configured providers are currently degraded." : `${degradedProviders} provider(s) are degraded, suppressed, or rate-limited.`,
|
|
9648
|
+
href: options.links?.resilience ?? "/resilience",
|
|
9649
|
+
label: "Provider health",
|
|
9650
|
+
status: degradedProviders > 0 ? "fail" : "pass",
|
|
9651
|
+
value: degradedProviders
|
|
9652
|
+
},
|
|
9653
|
+
{
|
|
9654
|
+
detail: failedSessions === 0 ? sessions.length > 0 ? "Recent sessions have no recorded provider/session failures." : "No sessions have been recorded yet; run a smoke or live session for proof." : `${failedSessions} recent session(s) have failures.`,
|
|
9655
|
+
href: options.links?.sessions ?? "/sessions",
|
|
9656
|
+
label: "Session health",
|
|
9657
|
+
status: failedSessions > 0 ? "fail" : sessions.length === 0 ? "warn" : "pass",
|
|
9658
|
+
value: `${sessions.length - failedSessions}/${sessions.length}`
|
|
9659
|
+
},
|
|
9660
|
+
{
|
|
9661
|
+
detail: handoffs.failed === 0 ? "No failed handoff deliveries are recorded." : `${handoffs.failed} handoff delivery failure(s) are recorded.`,
|
|
9662
|
+
href: options.links?.handoffs ?? "/handoffs",
|
|
9663
|
+
label: "Handoff delivery",
|
|
9664
|
+
status: handoffs.failed > 0 ? "fail" : "pass",
|
|
9665
|
+
value: `${handoffs.total - handoffs.failed}/${handoffs.total}`
|
|
9666
|
+
},
|
|
9667
|
+
{
|
|
9668
|
+
detail: routingEvents.length > 0 ? `${routingSessions.length} session(s) have provider routing evidence.` : "No provider routing traces are recorded yet.",
|
|
9669
|
+
href: options.links?.resilience ?? "/resilience",
|
|
9670
|
+
label: "Routing evidence",
|
|
9671
|
+
status: routingEvents.length > 0 ? "pass" : "warn",
|
|
9672
|
+
value: routingEvents.length
|
|
9673
|
+
}
|
|
9674
|
+
];
|
|
9675
|
+
const carrierSummary = carriers ? {
|
|
9676
|
+
failing: carriers.summary.failing,
|
|
9677
|
+
providers: carriers.summary.providers,
|
|
9678
|
+
ready: carriers.summary.ready,
|
|
9679
|
+
status: carrierStatus(carriers),
|
|
9680
|
+
warnings: carriers.summary.warnings
|
|
9681
|
+
} : undefined;
|
|
9682
|
+
if (carriers && carrierSummary) {
|
|
9683
|
+
checks.push({
|
|
9684
|
+
detail: carrierSummary.status === "pass" ? "Configured carrier setup and contract checks are passing." : `${carrierSummary.failing} carrier(s) failing, ${carrierSummary.warnings} warning(s).`,
|
|
9685
|
+
href: options.links?.carriers ?? "/carriers",
|
|
9686
|
+
label: "Carrier readiness",
|
|
9687
|
+
status: carrierSummary.status,
|
|
9688
|
+
value: `${carrierSummary.ready}/${carrierSummary.providers}`
|
|
9689
|
+
});
|
|
9690
|
+
}
|
|
9691
|
+
return {
|
|
9692
|
+
checkedAt: Date.now(),
|
|
9693
|
+
checks,
|
|
9694
|
+
links: {
|
|
9695
|
+
carriers: "/carriers",
|
|
9696
|
+
handoffs: "/handoffs",
|
|
9697
|
+
quality: "/quality",
|
|
9698
|
+
resilience: "/resilience",
|
|
9699
|
+
sessions: "/sessions",
|
|
9700
|
+
...options.links
|
|
9701
|
+
},
|
|
9702
|
+
status: rollupStatus(checks),
|
|
9703
|
+
summary: {
|
|
9704
|
+
carriers: carrierSummary,
|
|
9705
|
+
handoffs: {
|
|
9706
|
+
failed: handoffs.failed,
|
|
9707
|
+
total: handoffs.total
|
|
9708
|
+
},
|
|
9709
|
+
providers: {
|
|
9710
|
+
degraded: degradedProviders,
|
|
9711
|
+
total: providers.length
|
|
9712
|
+
},
|
|
9713
|
+
quality: {
|
|
9714
|
+
status: quality.status
|
|
9715
|
+
},
|
|
9716
|
+
routing: {
|
|
9717
|
+
events: routingEvents.length,
|
|
9718
|
+
sessions: routingSessions.length
|
|
9719
|
+
},
|
|
9720
|
+
sessions: {
|
|
9721
|
+
failed: failedSessions,
|
|
9722
|
+
total: sessions.length
|
|
9723
|
+
}
|
|
9724
|
+
}
|
|
9725
|
+
};
|
|
9726
|
+
};
|
|
9727
|
+
var renderVoiceProductionReadinessHTML = (report, options = {}) => {
|
|
9728
|
+
const title = options.title ?? "AbsoluteJS Voice Production Readiness";
|
|
9729
|
+
const checks = report.checks.map((check) => `<article class="check ${escapeHtml15(check.status)}">
|
|
9730
|
+
<div>
|
|
9731
|
+
<span>${escapeHtml15(check.status.toUpperCase())}</span>
|
|
9732
|
+
<h2>${escapeHtml15(check.label)}</h2>
|
|
9733
|
+
${check.detail ? `<p>${escapeHtml15(check.detail)}</p>` : ""}
|
|
9734
|
+
</div>
|
|
9735
|
+
<strong>${escapeHtml15(String(check.value ?? check.status))}</strong>
|
|
9736
|
+
${check.href ? `<a href="${escapeHtml15(check.href)}">Open surface</a>` : ""}
|
|
9737
|
+
</article>`).join("");
|
|
9738
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml15(title)}</title><style>body{background:#0c0f14;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1060px;padding:32px}.hero{background:linear-gradient(135deg,rgba(20,184,166,.18),rgba(245,158,11,.12));border:1px solid #26313d;border-radius:28px;margin-bottom:18px;padding:28px}.eyebrow{color:#fbbf24;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,5rem);line-height:.9;margin:.2rem 0 1rem}.status{display:inline-flex;border:1px solid #3f3f46;border-radius:999px;padding:8px 12px}.status.pass,.check.pass{border-color:rgba(34,197,94,.55)}.status.warn,.check.warn{border-color:rgba(245,158,11,.65)}.status.fail,.check.fail{border-color:rgba(239,68,68,.75)}.checks{display:grid;gap:14px}.check{align-items:center;background:#141922;border:1px solid #26313d;border-radius:22px;display:grid;gap:16px;grid-template-columns:1fr auto auto;padding:18px}.check span{color:#a8b0b8;font-size:.78rem;font-weight:900;letter-spacing:.08em}.check h2{margin:.2rem 0}.check p{color:#b9c0c8;margin:.2rem 0 0}.check strong{font-size:1.5rem}.check a,a{color:#fbbf24}@media(max-width:760px){main{padding:20px}.check{grid-template-columns:1fr}}</style></head><body><main><section class="hero"><p class="eyebrow">Self-hosted readiness</p><h1>${escapeHtml15(title)}</h1><p>One deployable pass/fail report for quality gates, provider failover, session health, handoffs, routing evidence, and optional carrier readiness.</p><p class="status ${escapeHtml15(report.status)}">Overall: ${escapeHtml15(report.status.toUpperCase())}</p><p>Checked ${escapeHtml15(new Date(report.checkedAt).toLocaleString())}</p></section><section class="checks">${checks}</section></main></body></html>`;
|
|
9739
|
+
};
|
|
9740
|
+
var createVoiceProductionReadinessRoutes = (options) => {
|
|
9741
|
+
const path = options.path ?? "/api/production-readiness";
|
|
9742
|
+
const htmlPath = options.htmlPath ?? "/production-readiness";
|
|
9743
|
+
const routes = new Elysia13({
|
|
9744
|
+
name: options.name ?? "absolutejs-voice-production-readiness"
|
|
9745
|
+
});
|
|
9746
|
+
routes.get(path, async ({ query, request }) => buildVoiceProductionReadinessReport(options, { query, request }));
|
|
9747
|
+
if (htmlPath !== false) {
|
|
9748
|
+
routes.get(htmlPath, async ({ query, request }) => {
|
|
9749
|
+
const report = await buildVoiceProductionReadinessReport(options, {
|
|
9750
|
+
query,
|
|
9751
|
+
request
|
|
9752
|
+
});
|
|
9753
|
+
const body = await (options.render ?? renderVoiceProductionReadinessHTML)(report);
|
|
9754
|
+
return new Response(body, {
|
|
9755
|
+
headers: {
|
|
9756
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
9757
|
+
...options.headers
|
|
9758
|
+
}
|
|
9759
|
+
});
|
|
9760
|
+
});
|
|
9761
|
+
}
|
|
9762
|
+
return routes;
|
|
9763
|
+
};
|
|
9764
|
+
|
|
9406
9765
|
// src/appKit.ts
|
|
9407
9766
|
var DEFAULT_LINKS2 = [
|
|
9408
9767
|
{
|
|
@@ -9427,6 +9786,12 @@ var DEFAULT_LINKS2 = [
|
|
|
9427
9786
|
href: "/resilience",
|
|
9428
9787
|
label: "Resilience"
|
|
9429
9788
|
},
|
|
9789
|
+
{
|
|
9790
|
+
description: "One JSON/HTML production readiness rollup.",
|
|
9791
|
+
href: "/production-readiness",
|
|
9792
|
+
label: "Production Readiness",
|
|
9793
|
+
statusHref: "/api/production-readiness"
|
|
9794
|
+
},
|
|
9430
9795
|
{
|
|
9431
9796
|
description: "Recent sessions and replay links.",
|
|
9432
9797
|
href: "/sessions",
|
|
@@ -9559,7 +9924,7 @@ var summarizeVoiceAppKitStatus = async (options) => {
|
|
|
9559
9924
|
};
|
|
9560
9925
|
};
|
|
9561
9926
|
var createVoiceAppKitRoutes = (options) => {
|
|
9562
|
-
const routes = new
|
|
9927
|
+
const routes = new Elysia14({
|
|
9563
9928
|
name: options.name ?? "absolutejs-voice-app-kit"
|
|
9564
9929
|
});
|
|
9565
9930
|
const links = resolveLinks(options.links);
|
|
@@ -9666,6 +10031,23 @@ var createVoiceAppKitRoutes = (options) => {
|
|
|
9666
10031
|
...options.resilience
|
|
9667
10032
|
}));
|
|
9668
10033
|
}
|
|
10034
|
+
if (options.productionReadiness !== false) {
|
|
10035
|
+
surfaces.push("productionReadiness");
|
|
10036
|
+
routes.use(createVoiceProductionReadinessRoutes({
|
|
10037
|
+
...common,
|
|
10038
|
+
links: {
|
|
10039
|
+
handoffs: "/handoffs",
|
|
10040
|
+
quality: "/quality",
|
|
10041
|
+
resilience: "/resilience",
|
|
10042
|
+
sessions: "/sessions"
|
|
10043
|
+
},
|
|
10044
|
+
llmProviders: options.llmProviders,
|
|
10045
|
+
sttProviders: options.sttProviders,
|
|
10046
|
+
title: options.title ? `${options.title} Production Readiness` : undefined,
|
|
10047
|
+
ttsProviders: options.ttsProviders,
|
|
10048
|
+
...options.productionReadiness
|
|
10049
|
+
}));
|
|
10050
|
+
}
|
|
9669
10051
|
if (options.opsConsole !== false) {
|
|
9670
10052
|
surfaces.push("opsConsole");
|
|
9671
10053
|
routes.use(createVoiceOpsConsoleRoutes({
|
|
@@ -10177,7 +10559,7 @@ var createVoiceToolIdempotencyKey = (input) => {
|
|
|
10177
10559
|
].join(":");
|
|
10178
10560
|
};
|
|
10179
10561
|
// src/toolContract.ts
|
|
10180
|
-
import { Elysia as
|
|
10562
|
+
import { Elysia as Elysia15 } from "elysia";
|
|
10181
10563
|
var createDefaultSession = (contractId, caseId) => createVoiceSessionRecord(`tool-contract-${contractId}-${caseId}`);
|
|
10182
10564
|
var createDefaultTurn = (caseId) => ({
|
|
10183
10565
|
committedAt: Date.now(),
|
|
@@ -10187,7 +10569,7 @@ var createDefaultTurn = (caseId) => ({
|
|
|
10187
10569
|
});
|
|
10188
10570
|
var defaultApi = {};
|
|
10189
10571
|
var sameJSON = (left, right) => JSON.stringify(left) === JSON.stringify(right);
|
|
10190
|
-
var
|
|
10572
|
+
var escapeHtml16 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
10191
10573
|
var evaluateExpectation = (input) => {
|
|
10192
10574
|
const issues = [];
|
|
10193
10575
|
const expect = input.expect;
|
|
@@ -10353,19 +10735,19 @@ var renderVoiceToolContractHTML = (report, options = {}) => {
|
|
|
10353
10735
|
const title = options.title ?? "Voice Tool Contracts";
|
|
10354
10736
|
const contracts = report.contracts.map((contract) => {
|
|
10355
10737
|
const cases = contract.cases.map((testCase) => `<tr>
|
|
10356
|
-
<td>${
|
|
10738
|
+
<td>${escapeHtml16(testCase.label ?? testCase.caseId)}</td>
|
|
10357
10739
|
<td class="${testCase.pass ? "pass" : "fail"}">${testCase.pass ? "pass" : "fail"}</td>
|
|
10358
|
-
<td>${
|
|
10740
|
+
<td>${escapeHtml16(testCase.status)}</td>
|
|
10359
10741
|
<td>${String(testCase.attempts)}</td>
|
|
10360
10742
|
<td>${String(testCase.elapsedMs)}ms</td>
|
|
10361
10743
|
<td>${testCase.timedOut ? "yes" : "no"}</td>
|
|
10362
|
-
<td>${
|
|
10744
|
+
<td>${escapeHtml16(testCase.issues.map((issue) => issue.message).join(" ") || testCase.error || "")}</td>
|
|
10363
10745
|
</tr>`).join("");
|
|
10364
10746
|
return `<section class="contract ${contract.pass ? "pass" : "fail"}">
|
|
10365
10747
|
<div class="contract-header">
|
|
10366
10748
|
<div>
|
|
10367
|
-
<p class="eyebrow">${
|
|
10368
|
-
<h2>${
|
|
10749
|
+
<p class="eyebrow">${escapeHtml16(contract.toolName)}</p>
|
|
10750
|
+
<h2>${escapeHtml16(contract.label ?? contract.contractId)}</h2>
|
|
10369
10751
|
</div>
|
|
10370
10752
|
<strong class="${contract.pass ? "pass" : "fail"}">${contract.pass ? "Passing" : "Failing"}</strong>
|
|
10371
10753
|
</div>
|
|
@@ -10375,7 +10757,7 @@ var renderVoiceToolContractHTML = (report, options = {}) => {
|
|
|
10375
10757
|
</table>
|
|
10376
10758
|
</section>`;
|
|
10377
10759
|
}).join("");
|
|
10378
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
10760
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml16(title)}</title><style>body{background:#101316;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}.hero,.contract{background:#181d22;border:1px solid #2a323a;border-radius:20px;margin-bottom:16px;padding:20px}.hero{background:linear-gradient(135deg,rgba(34,197,94,.14),rgba(245,158,11,.12))}.eyebrow{color:#fbbf24;font-size:.78rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}h1{font-size:clamp(2.3rem,6vw,5rem);letter-spacing:-.06em;line-height:.9;margin:.2rem 0 1rem}.summary{display:flex;flex-wrap:wrap;gap:10px}.pill{background:#0f1217;border:1px solid #3f3f46;border-radius:999px;padding:7px 10px}.contract-header{align-items:flex-start;display:flex;gap:16px;justify-content:space-between}h2{margin:.2rem 0 1rem}.pass{color:#86efac}.fail{color:#fca5a5}.contract.fail{border-color:rgba(248,113,113,.45)}table{border-collapse:collapse;width:100%}td,th{border-bottom:1px solid #2a323a;padding:12px;text-align:left;vertical-align:top}th{color:#a8b0b8;font-size:.82rem}@media(max-width:800px){main{padding:18px}table{display:block;overflow:auto}.contract-header{display:block}}</style></head><body><main><section class="hero"><p class="eyebrow">Tool Reliability</p><h1>${escapeHtml16(title)}</h1><div class="summary"><span class="pill ${report.status === "pass" ? "pass" : "fail"}">${escapeHtml16(report.status)}</span><span class="pill">${String(report.passed)} passing</span><span class="pill">${String(report.failed)} failing</span><span class="pill">${String(report.total)} contracts</span></div></section>${contracts || '<section class="contract"><p>No tool contracts configured.</p></section>'}</main></body></html>`;
|
|
10379
10761
|
};
|
|
10380
10762
|
var createVoiceToolContractJSONHandler = (options) => () => runVoiceToolContractSuite(options);
|
|
10381
10763
|
var createVoiceToolContractHTMLHandler = (options) => async () => {
|
|
@@ -10392,7 +10774,7 @@ var createVoiceToolContractHTMLHandler = (options) => async () => {
|
|
|
10392
10774
|
var createVoiceToolContractRoutes = (options) => {
|
|
10393
10775
|
const path = options.path ?? "/api/tool-contracts";
|
|
10394
10776
|
const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
|
|
10395
|
-
const routes = new
|
|
10777
|
+
const routes = new Elysia15({
|
|
10396
10778
|
name: options.name ?? "absolutejs-voice-tool-contracts"
|
|
10397
10779
|
}).get(path, createVoiceToolContractJSONHandler(options));
|
|
10398
10780
|
if (htmlPath) {
|
|
@@ -10401,9 +10783,9 @@ var createVoiceToolContractRoutes = (options) => {
|
|
|
10401
10783
|
return routes;
|
|
10402
10784
|
};
|
|
10403
10785
|
// src/turnQuality.ts
|
|
10404
|
-
import { Elysia as
|
|
10786
|
+
import { Elysia as Elysia16 } from "elysia";
|
|
10405
10787
|
var DEFAULT_CONFIDENCE_WARN_THRESHOLD = 0.72;
|
|
10406
|
-
var
|
|
10788
|
+
var escapeHtml17 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
10407
10789
|
var getTurnLatencyMs = (turn) => {
|
|
10408
10790
|
const firstTranscriptAt = turn.transcripts.map((transcript) => transcript.endedAtMs ?? transcript.startedAtMs).filter((value) => typeof value === "number").sort((left, right) => left - right)[0];
|
|
10409
10791
|
if (firstTranscriptAt === undefined) {
|
|
@@ -10474,24 +10856,24 @@ var summarizeVoiceTurnQuality = async (options) => {
|
|
|
10474
10856
|
};
|
|
10475
10857
|
var renderVoiceTurnQualityHTML = (report, options = {}) => {
|
|
10476
10858
|
const title = options.title ?? "Voice Turn Quality";
|
|
10477
|
-
const turns = report.turns.map((turn) => `<article class="turn ${
|
|
10859
|
+
const turns = report.turns.map((turn) => `<article class="turn ${escapeHtml17(turn.status)}">
|
|
10478
10860
|
<div class="turn-header">
|
|
10479
10861
|
<div>
|
|
10480
|
-
<p class="eyebrow">${
|
|
10481
|
-
<h2>${
|
|
10862
|
+
<p class="eyebrow">${escapeHtml17(turn.sessionId)} \xB7 ${escapeHtml17(turn.turnId)}</p>
|
|
10863
|
+
<h2>${escapeHtml17(turn.text || "Empty turn")}</h2>
|
|
10482
10864
|
</div>
|
|
10483
|
-
<strong>${
|
|
10865
|
+
<strong>${escapeHtml17(turn.status)}</strong>
|
|
10484
10866
|
</div>
|
|
10485
10867
|
<dl>
|
|
10486
|
-
<div><dt>Source</dt><dd>${
|
|
10868
|
+
<div><dt>Source</dt><dd>${escapeHtml17(turn.source ?? "unknown")}</dd></div>
|
|
10487
10869
|
<div><dt>Confidence</dt><dd>${turn.averageConfidence === undefined ? "n/a" : `${Math.round(turn.averageConfidence * 100)}%`}</dd></div>
|
|
10488
|
-
<div><dt>Fallback</dt><dd>${turn.fallbackUsed ? `yes (${
|
|
10489
|
-
<div><dt>Correction</dt><dd>${turn.correctionChanged ? `changed${turn.correctionProvider ? ` by ${
|
|
10870
|
+
<div><dt>Fallback</dt><dd>${turn.fallbackUsed ? `yes (${escapeHtml17(turn.fallbackSelectionReason ?? "selected")})` : "no"}</dd></div>
|
|
10871
|
+
<div><dt>Correction</dt><dd>${turn.correctionChanged ? `changed${turn.correctionProvider ? ` by ${escapeHtml17(turn.correctionProvider)}` : ""}` : "none"}</dd></div>
|
|
10490
10872
|
<div><dt>Transcripts</dt><dd>${String(turn.selectedTranscriptCount)} selected \xB7 ${String(turn.finalTranscriptCount)} final \xB7 ${String(turn.partialTranscriptCount)} partial</dd></div>
|
|
10491
10873
|
<div><dt>Cost</dt><dd>${turn.costUnits === undefined ? "n/a" : String(turn.costUnits)}</dd></div>
|
|
10492
10874
|
</dl>
|
|
10493
10875
|
</article>`).join("");
|
|
10494
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
10876
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml17(title)}</title><style>body{background:#101316;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}.hero,.turn{background:#181d22;border:1px solid #2a323a;border-radius:20px;margin-bottom:16px;padding:20px}.hero{background:linear-gradient(135deg,rgba(251,191,36,.16),rgba(34,197,94,.1))}.eyebrow{color:#fbbf24;font-size:.78rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}h1{font-size:clamp(2.3rem,6vw,5rem);letter-spacing:-.06em;line-height:.9;margin:.2rem 0 1rem}h2{margin:.2rem 0 1rem}.summary{display:flex;flex-wrap:wrap;gap:10px}.pill{background:#0f1217;border:1px solid #3f3f46;border-radius:999px;padding:7px 10px}.turn-header{align-items:flex-start;display:flex;gap:16px;justify-content:space-between}.pass{color:#86efac}.warn,.unknown{color:#fde68a}.fail{color:#fca5a5}.turn.fail{border-color:rgba(248,113,113,.45)}dl{display:grid;gap:8px;grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}dt{color:#a8b0b8;font-size:.8rem}dd{margin:0}@media(max-width:800px){main{padding:18px}.turn-header{display:block}}</style></head><body><main><section class="hero"><p class="eyebrow">Realtime STT Debugging</p><h1>${escapeHtml17(title)}</h1><div class="summary"><span class="pill ${escapeHtml17(report.status)}">${escapeHtml17(report.status)}</span><span class="pill">${String(report.total)} turns</span><span class="pill">${String(report.warnings)} warnings</span><span class="pill">${String(report.failed)} failed</span><span class="pill">${String(report.sessions)} sessions</span></div></section>${turns || '<section class="turn"><p>No committed turns found.</p></section>'}</main></body></html>`;
|
|
10495
10877
|
};
|
|
10496
10878
|
var createVoiceTurnQualityJSONHandler = (options) => async () => summarizeVoiceTurnQuality(options);
|
|
10497
10879
|
var createVoiceTurnQualityHTMLHandler = (options) => async () => {
|
|
@@ -10508,7 +10890,7 @@ var createVoiceTurnQualityHTMLHandler = (options) => async () => {
|
|
|
10508
10890
|
var createVoiceTurnQualityRoutes = (options) => {
|
|
10509
10891
|
const path = options.path ?? "/api/turn-quality";
|
|
10510
10892
|
const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
|
|
10511
|
-
const routes = new
|
|
10893
|
+
const routes = new Elysia16({
|
|
10512
10894
|
name: options.name ?? "absolutejs-voice-turn-quality"
|
|
10513
10895
|
}).get(path, createVoiceTurnQualityJSONHandler(options));
|
|
10514
10896
|
if (htmlPath) {
|
|
@@ -10517,8 +10899,8 @@ var createVoiceTurnQualityRoutes = (options) => {
|
|
|
10517
10899
|
return routes;
|
|
10518
10900
|
};
|
|
10519
10901
|
// src/outcomeContract.ts
|
|
10520
|
-
import { Elysia as
|
|
10521
|
-
var
|
|
10902
|
+
import { Elysia as Elysia17 } from "elysia";
|
|
10903
|
+
var escapeHtml18 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
10522
10904
|
var getPayloadString = (event, key) => typeof event.payload[key] === "string" ? event.payload[key] : undefined;
|
|
10523
10905
|
var toList = async (input) => Array.isArray(input) ? input : await input?.list() ?? [];
|
|
10524
10906
|
var hydrateSessions = async (input) => {
|
|
@@ -10626,9 +11008,9 @@ var renderVoiceOutcomeContractHTML = (report, options = {}) => {
|
|
|
10626
11008
|
const contracts = report.contracts.map((contract) => `<section class="contract ${contract.pass ? "pass" : "fail"}">
|
|
10627
11009
|
<div class="contract-header">
|
|
10628
11010
|
<div>
|
|
10629
|
-
<p class="eyebrow">${
|
|
10630
|
-
<h2>${
|
|
10631
|
-
${contract.description ? `<p>${
|
|
11011
|
+
<p class="eyebrow">${escapeHtml18(contract.contractId)}</p>
|
|
11012
|
+
<h2>${escapeHtml18(contract.label ?? contract.contractId)}</h2>
|
|
11013
|
+
${contract.description ? `<p>${escapeHtml18(contract.description)}</p>` : ""}
|
|
10632
11014
|
</div>
|
|
10633
11015
|
<strong>${contract.pass ? "pass" : "fail"}</strong>
|
|
10634
11016
|
</div>
|
|
@@ -10639,9 +11021,9 @@ var renderVoiceOutcomeContractHTML = (report, options = {}) => {
|
|
|
10639
11021
|
<span>handoffs ${String(contract.matched.handoffs)}</span>
|
|
10640
11022
|
<span>events ${String(contract.matched.integrationEvents)}</span>
|
|
10641
11023
|
</div>
|
|
10642
|
-
${contract.issues.length ? `<ul>${contract.issues.map((issue) => `<li>${
|
|
11024
|
+
${contract.issues.length ? `<ul>${contract.issues.map((issue) => `<li>${escapeHtml18(issue.message)}</li>`).join("")}</ul>` : ""}
|
|
10643
11025
|
</section>`).join("");
|
|
10644
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
11026
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml18(title)}</title><style>body{background:#101316;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}.hero,.contract{background:#181d22;border:1px solid #2a323a;border-radius:20px;margin-bottom:16px;padding:20px}.hero{background:linear-gradient(135deg,rgba(34,197,94,.14),rgba(14,165,233,.12))}.eyebrow{color:#7dd3fc;font-size:.78rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}h1{font-size:clamp(2.3rem,6vw,5rem);letter-spacing:-.06em;line-height:.9;margin:.2rem 0 1rem}h2{margin:.2rem 0}.summary,.grid{display:flex;flex-wrap:wrap;gap:10px}.pill,.grid span{background:#0f1217;border:1px solid #3f3f46;border-radius:999px;padding:7px 10px}.contract-header{display:flex;gap:16px;justify-content:space-between}.pass{color:#86efac}.fail{color:#fca5a5}.contract.fail{border-color:rgba(248,113,113,.45)}li{margin:8px 0}@media(max-width:800px){main{padding:18px}.contract-header{display:block}}</style></head><body><main><section class="hero"><p class="eyebrow">Business Outcome Verification</p><h1>${escapeHtml18(title)}</h1><div class="summary"><span class="pill ${report.status}">${report.status}</span><span class="pill">${String(report.passed)} passing</span><span class="pill">${String(report.failed)} failing</span><span class="pill">${String(report.total)} contracts</span></div></section>${contracts || '<section class="contract"><p>No outcome contracts configured.</p></section>'}</main></body></html>`;
|
|
10645
11027
|
};
|
|
10646
11028
|
var createVoiceOutcomeContractJSONHandler = (options) => async () => runVoiceOutcomeContractSuite(options);
|
|
10647
11029
|
var createVoiceOutcomeContractHTMLHandler = (options) => async () => {
|
|
@@ -10657,7 +11039,7 @@ var createVoiceOutcomeContractHTMLHandler = (options) => async () => {
|
|
|
10657
11039
|
var createVoiceOutcomeContractRoutes = (options) => {
|
|
10658
11040
|
const path = options.path ?? "/api/outcome-contracts";
|
|
10659
11041
|
const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
|
|
10660
|
-
const routes = new
|
|
11042
|
+
const routes = new Elysia17({
|
|
10661
11043
|
name: options.name ?? "absolutejs-voice-outcome-contracts"
|
|
10662
11044
|
}).get(path, createVoiceOutcomeContractJSONHandler(options));
|
|
10663
11045
|
if (htmlPath) {
|
|
@@ -10666,7 +11048,7 @@ var createVoiceOutcomeContractRoutes = (options) => {
|
|
|
10666
11048
|
return routes;
|
|
10667
11049
|
};
|
|
10668
11050
|
// src/telephonyOutcome.ts
|
|
10669
|
-
import { Elysia as
|
|
11051
|
+
import { Elysia as Elysia18 } from "elysia";
|
|
10670
11052
|
var DEFAULT_COMPLETED_STATUSES = [
|
|
10671
11053
|
"answered",
|
|
10672
11054
|
"completed",
|
|
@@ -11313,7 +11695,7 @@ var createVoiceTelephonyWebhookHandler = (options = {}) => async (input) => {
|
|
|
11313
11695
|
var createVoiceTelephonyWebhookRoutes = (options = {}) => {
|
|
11314
11696
|
const path = options.path ?? "/api/voice/telephony/webhook";
|
|
11315
11697
|
const handler = createVoiceTelephonyWebhookHandler(options);
|
|
11316
|
-
return new
|
|
11698
|
+
return new Elysia18({
|
|
11317
11699
|
name: options.name ?? "absolutejs-voice-telephony-webhooks"
|
|
11318
11700
|
}).post(path, async ({ query, request }) => {
|
|
11319
11701
|
try {
|
|
@@ -13581,7 +13963,7 @@ var createVoiceMemoryStore = () => {
|
|
|
13581
13963
|
return { get, getOrCreate, list, remove, set };
|
|
13582
13964
|
};
|
|
13583
13965
|
// src/opsWebhook.ts
|
|
13584
|
-
import { Elysia as
|
|
13966
|
+
import { Elysia as Elysia19 } from "elysia";
|
|
13585
13967
|
var toHex5 = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
13586
13968
|
var signVoiceOpsWebhookBody = async (input) => {
|
|
13587
13969
|
const encoder = new TextEncoder;
|
|
@@ -13711,7 +14093,7 @@ var verifyVoiceOpsWebhookSignature = async (input) => {
|
|
|
13711
14093
|
};
|
|
13712
14094
|
var createVoiceOpsWebhookReceiverRoutes = (options = {}) => {
|
|
13713
14095
|
const path = options.path ?? "/api/voice-ops/webhook";
|
|
13714
|
-
return new
|
|
14096
|
+
return new Elysia19().post(path, async ({ body, request, set }) => {
|
|
13715
14097
|
const bodyText = typeof body === "string" ? body : JSON.stringify(body);
|
|
13716
14098
|
if (options.signingSecret) {
|
|
13717
14099
|
const verification = await verifyVoiceOpsWebhookSignature({
|
|
@@ -15440,7 +15822,7 @@ var createVoiceSTTRoutingCorrectionHandler = (mode = "generic") => {
|
|
|
15440
15822
|
};
|
|
15441
15823
|
// src/telephony/twilio.ts
|
|
15442
15824
|
import { Buffer as Buffer3 } from "buffer";
|
|
15443
|
-
import { Elysia as
|
|
15825
|
+
import { Elysia as Elysia20 } from "elysia";
|
|
15444
15826
|
var TWILIO_MULAW_SAMPLE_RATE = 8000;
|
|
15445
15827
|
var VOICE_PCM_SAMPLE_RATE = 16000;
|
|
15446
15828
|
var escapeXml2 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
@@ -15470,7 +15852,7 @@ var resolveTwilioStreamParameters = async (parameters, input) => {
|
|
|
15470
15852
|
return parameters;
|
|
15471
15853
|
};
|
|
15472
15854
|
var joinUrlPath = (origin, path) => `${origin.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
|
|
15473
|
-
var
|
|
15855
|
+
var escapeHtml19 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
15474
15856
|
var getWebhookVerificationUrl = (webhook, input) => {
|
|
15475
15857
|
if (!webhook?.verificationUrl) {
|
|
15476
15858
|
return;
|
|
@@ -15513,23 +15895,23 @@ var buildTwilioVoiceSetupStatus = async (options, input) => {
|
|
|
15513
15895
|
};
|
|
15514
15896
|
var renderTwilioVoiceSetupHTML = (status, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
|
|
15515
15897
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Twilio setup</p>
|
|
15516
|
-
<h1>${
|
|
15898
|
+
<h1>${escapeHtml19(title)}</h1>
|
|
15517
15899
|
<p><strong>Status:</strong> ${status.ready ? "Ready" : "Needs attention"}</p>
|
|
15518
15900
|
<section>
|
|
15519
15901
|
<h2>URLs</h2>
|
|
15520
15902
|
<ul>
|
|
15521
|
-
<li><strong>TwiML:</strong> <code>${
|
|
15522
|
-
<li><strong>Media stream:</strong> <code>${
|
|
15523
|
-
<li><strong>Status webhook:</strong> <code>${
|
|
15903
|
+
<li><strong>TwiML:</strong> <code>${escapeHtml19(status.urls.twiml)}</code></li>
|
|
15904
|
+
<li><strong>Media stream:</strong> <code>${escapeHtml19(status.urls.stream)}</code></li>
|
|
15905
|
+
<li><strong>Status webhook:</strong> <code>${escapeHtml19(status.urls.webhook)}</code></li>
|
|
15524
15906
|
</ul>
|
|
15525
15907
|
</section>
|
|
15526
15908
|
<section>
|
|
15527
15909
|
<h2>Signing</h2>
|
|
15528
15910
|
<p>Mode: <code>${status.signing.mode}</code></p>
|
|
15529
|
-
${status.signing.verificationUrl ? `<p>Verification URL: <code>${
|
|
15911
|
+
${status.signing.verificationUrl ? `<p>Verification URL: <code>${escapeHtml19(status.signing.verificationUrl)}</code></p>` : ""}
|
|
15530
15912
|
</section>
|
|
15531
|
-
${status.missing.length ? `<section><h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${
|
|
15532
|
-
${status.warnings.length ? `<section><h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${
|
|
15913
|
+
${status.missing.length ? `<section><h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml19(name)}</code></li>`).join("")}</ul></section>` : ""}
|
|
15914
|
+
${status.warnings.length ? `<section><h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml19(warning)}</li>`).join("")}</ul></section>` : ""}
|
|
15533
15915
|
</main>`;
|
|
15534
15916
|
var extractTwilioStreamUrl = (twiml) => twiml.match(/<Stream\b[^>]*\surl="([^"]+)"/i)?.[1]?.replaceAll("&", "&");
|
|
15535
15917
|
var createSmokeCheck = (name, status, message, details) => ({
|
|
@@ -15540,20 +15922,20 @@ var createSmokeCheck = (name, status, message, details) => ({
|
|
|
15540
15922
|
});
|
|
15541
15923
|
var renderTwilioVoiceSmokeHTML = (report, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
|
|
15542
15924
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Twilio smoke test</p>
|
|
15543
|
-
<h1>${
|
|
15925
|
+
<h1>${escapeHtml19(title)}</h1>
|
|
15544
15926
|
<p><strong>Status:</strong> ${report.pass ? "Pass" : "Fail"}</p>
|
|
15545
15927
|
<section>
|
|
15546
15928
|
<h2>Checks</h2>
|
|
15547
15929
|
<ul>
|
|
15548
|
-
${report.checks.map((check) => `<li><strong>${
|
|
15930
|
+
${report.checks.map((check) => `<li><strong>${escapeHtml19(check.name)}</strong>: ${escapeHtml19(check.status)}${check.message ? ` - ${escapeHtml19(check.message)}` : ""}</li>`).join("")}
|
|
15549
15931
|
</ul>
|
|
15550
15932
|
</section>
|
|
15551
15933
|
<section>
|
|
15552
15934
|
<h2>Observed URLs</h2>
|
|
15553
15935
|
<ul>
|
|
15554
|
-
<li><strong>TwiML:</strong> <code>${
|
|
15555
|
-
<li><strong>Stream:</strong> <code>${
|
|
15556
|
-
<li><strong>Webhook:</strong> <code>${
|
|
15936
|
+
<li><strong>TwiML:</strong> <code>${escapeHtml19(report.setup.urls.twiml)}</code></li>
|
|
15937
|
+
<li><strong>Stream:</strong> <code>${escapeHtml19(report.twiml?.streamUrl ?? report.setup.urls.stream)}</code></li>
|
|
15938
|
+
<li><strong>Webhook:</strong> <code>${escapeHtml19(report.setup.urls.webhook)}</code></li>
|
|
15557
15939
|
</ul>
|
|
15558
15940
|
</section>
|
|
15559
15941
|
</main>`;
|
|
@@ -16013,7 +16395,7 @@ var createTwilioVoiceRoutes = (options) => {
|
|
|
16013
16395
|
const smokePath = options.smoke?.path === false ? false : options.smoke?.path ?? "/api/voice/twilio/smoke";
|
|
16014
16396
|
const bridges = new WeakMap;
|
|
16015
16397
|
const webhookPolicy = options.webhook?.policy ?? options.outcomePolicy ?? createVoiceTelephonyOutcomePolicy();
|
|
16016
|
-
const app = new
|
|
16398
|
+
const app = new Elysia20({
|
|
16017
16399
|
name: options.name ?? "absolutejs-voice-twilio"
|
|
16018
16400
|
}).get(twimlPath, async ({ query, request }) => {
|
|
16019
16401
|
const streamUrl = await resolveTwilioStreamUrl(options, {
|
|
@@ -16147,90 +16529,11 @@ var createTwilioVoiceRoutes = (options) => {
|
|
|
16147
16529
|
return report;
|
|
16148
16530
|
});
|
|
16149
16531
|
};
|
|
16150
|
-
// src/telephony/contract.ts
|
|
16151
|
-
var DEFAULT_REQUIREMENTS = [
|
|
16152
|
-
"stream-url",
|
|
16153
|
-
"wss-stream",
|
|
16154
|
-
"webhook-url",
|
|
16155
|
-
"signed-webhook",
|
|
16156
|
-
"smoke-pass"
|
|
16157
|
-
];
|
|
16158
|
-
var hasFailingSmokeCheck = (smoke) => smoke?.checks.some((check) => check.status === "fail") ?? false;
|
|
16159
|
-
var evaluateVoiceTelephonyContract = (input) => {
|
|
16160
|
-
const requirements = input.options?.requirements ?? DEFAULT_REQUIREMENTS;
|
|
16161
|
-
const issues = [];
|
|
16162
|
-
const hasRequirement = (requirement) => requirements.includes(requirement);
|
|
16163
|
-
if (hasRequirement("stream-url") && !input.setup.urls.stream) {
|
|
16164
|
-
issues.push({
|
|
16165
|
-
message: "Missing media stream URL.",
|
|
16166
|
-
requirement: "stream-url",
|
|
16167
|
-
severity: "error"
|
|
16168
|
-
});
|
|
16169
|
-
}
|
|
16170
|
-
if (hasRequirement("wss-stream") && !input.setup.urls.stream.startsWith("wss://")) {
|
|
16171
|
-
issues.push({
|
|
16172
|
-
message: "Media stream URL must use wss://.",
|
|
16173
|
-
requirement: "wss-stream",
|
|
16174
|
-
severity: "error"
|
|
16175
|
-
});
|
|
16176
|
-
}
|
|
16177
|
-
if (hasRequirement("webhook-url") && !input.setup.urls.webhook) {
|
|
16178
|
-
issues.push({
|
|
16179
|
-
message: "Missing carrier webhook URL.",
|
|
16180
|
-
requirement: "webhook-url",
|
|
16181
|
-
severity: "error"
|
|
16182
|
-
});
|
|
16183
|
-
}
|
|
16184
|
-
if (hasRequirement("signed-webhook") && !input.setup.signing.configured) {
|
|
16185
|
-
issues.push({
|
|
16186
|
-
message: "Carrier webhook signature verification is not configured.",
|
|
16187
|
-
requirement: "signed-webhook",
|
|
16188
|
-
severity: "error"
|
|
16189
|
-
});
|
|
16190
|
-
}
|
|
16191
|
-
if (hasRequirement("smoke-pass")) {
|
|
16192
|
-
if (!input.smoke) {
|
|
16193
|
-
issues.push({
|
|
16194
|
-
message: "Missing telephony smoke test report.",
|
|
16195
|
-
requirement: "smoke-pass",
|
|
16196
|
-
severity: "error"
|
|
16197
|
-
});
|
|
16198
|
-
} else if (!input.smoke.pass || hasFailingSmokeCheck(input.smoke)) {
|
|
16199
|
-
issues.push({
|
|
16200
|
-
message: "Telephony smoke test did not pass.",
|
|
16201
|
-
requirement: "smoke-pass",
|
|
16202
|
-
severity: "error"
|
|
16203
|
-
});
|
|
16204
|
-
}
|
|
16205
|
-
}
|
|
16206
|
-
for (const warning of input.setup.warnings) {
|
|
16207
|
-
issues.push({
|
|
16208
|
-
message: warning,
|
|
16209
|
-
requirement: "stream-url",
|
|
16210
|
-
severity: "warning"
|
|
16211
|
-
});
|
|
16212
|
-
}
|
|
16213
|
-
for (const name of input.setup.missing) {
|
|
16214
|
-
issues.push({
|
|
16215
|
-
message: `${name} is missing.`,
|
|
16216
|
-
requirement: "webhook-url",
|
|
16217
|
-
severity: "error"
|
|
16218
|
-
});
|
|
16219
|
-
}
|
|
16220
|
-
return {
|
|
16221
|
-
issues,
|
|
16222
|
-
pass: issues.every((issue) => issue.severity !== "error"),
|
|
16223
|
-
provider: input.setup.provider,
|
|
16224
|
-
requirements,
|
|
16225
|
-
setup: input.setup,
|
|
16226
|
-
smoke: input.smoke
|
|
16227
|
-
};
|
|
16228
|
-
};
|
|
16229
16532
|
// src/telephony/telnyx.ts
|
|
16230
16533
|
import { Buffer as Buffer4 } from "buffer";
|
|
16231
|
-
import { Elysia as
|
|
16534
|
+
import { Elysia as Elysia21 } from "elysia";
|
|
16232
16535
|
var escapeXml3 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
16233
|
-
var
|
|
16536
|
+
var escapeHtml20 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
16234
16537
|
var joinUrlPath2 = (origin, path) => `${origin.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
|
|
16235
16538
|
var resolveRequestOrigin2 = (request) => {
|
|
16236
16539
|
const url = new URL(request.url);
|
|
@@ -16431,21 +16734,21 @@ var buildTelnyxVoiceSetupStatus = async (options, input) => {
|
|
|
16431
16734
|
};
|
|
16432
16735
|
var renderTelnyxSetupHTML = (status, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
|
|
16433
16736
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Telnyx setup</p>
|
|
16434
|
-
<h1>${
|
|
16737
|
+
<h1>${escapeHtml20(title)}</h1>
|
|
16435
16738
|
<p><strong>Status:</strong> ${status.ready ? "Ready" : "Needs attention"}</p>
|
|
16436
16739
|
<ul>
|
|
16437
|
-
<li><strong>TeXML:</strong> <code>${
|
|
16438
|
-
<li><strong>Media stream:</strong> <code>${
|
|
16439
|
-
<li><strong>Status webhook:</strong> <code>${
|
|
16740
|
+
<li><strong>TeXML:</strong> <code>${escapeHtml20(status.urls.texml)}</code></li>
|
|
16741
|
+
<li><strong>Media stream:</strong> <code>${escapeHtml20(status.urls.stream)}</code></li>
|
|
16742
|
+
<li><strong>Status webhook:</strong> <code>${escapeHtml20(status.urls.webhook)}</code></li>
|
|
16440
16743
|
</ul>
|
|
16441
|
-
${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${
|
|
16442
|
-
${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${
|
|
16744
|
+
${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml20(name)}</code></li>`).join("")}</ul>` : ""}
|
|
16745
|
+
${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml20(warning)}</li>`).join("")}</ul>` : ""}
|
|
16443
16746
|
</main>`;
|
|
16444
16747
|
var renderTelnyxSmokeHTML = (report, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
|
|
16445
16748
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Telnyx smoke test</p>
|
|
16446
|
-
<h1>${
|
|
16749
|
+
<h1>${escapeHtml20(title)}</h1>
|
|
16447
16750
|
<p><strong>Status:</strong> ${report.pass ? "Pass" : "Fail"}</p>
|
|
16448
|
-
<ul>${report.checks.map((check) => `<li><strong>${
|
|
16751
|
+
<ul>${report.checks.map((check) => `<li><strong>${escapeHtml20(check.name)}</strong>: ${escapeHtml20(check.status)}${check.message ? ` - ${escapeHtml20(check.message)}` : ""}</li>`).join("")}</ul>
|
|
16449
16752
|
</main>`;
|
|
16450
16753
|
var runTelnyxSmokeTest = async (input) => {
|
|
16451
16754
|
const setup = await buildTelnyxVoiceSetupStatus(input.options, input);
|
|
@@ -16539,7 +16842,7 @@ var createTelnyxVoiceRoutes = (options = {}) => {
|
|
|
16539
16842
|
publicKey: options.webhook?.publicKey,
|
|
16540
16843
|
toleranceSeconds: options.webhook?.toleranceSeconds
|
|
16541
16844
|
}) : undefined);
|
|
16542
|
-
const app = new
|
|
16845
|
+
const app = new Elysia21({
|
|
16543
16846
|
name: options.name ?? "absolutejs-voice-telnyx"
|
|
16544
16847
|
}).get(texmlPath, async ({ query, request }) => {
|
|
16545
16848
|
const streamUrl = await resolveTelnyxStreamUrl(options, {
|
|
@@ -16649,9 +16952,9 @@ var createTelnyxVoiceRoutes = (options = {}) => {
|
|
|
16649
16952
|
};
|
|
16650
16953
|
// src/telephony/plivo.ts
|
|
16651
16954
|
import { Buffer as Buffer5 } from "buffer";
|
|
16652
|
-
import { Elysia as
|
|
16955
|
+
import { Elysia as Elysia22 } from "elysia";
|
|
16653
16956
|
var escapeXml4 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
16654
|
-
var
|
|
16957
|
+
var escapeHtml21 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
16655
16958
|
var joinUrlPath3 = (origin, path) => `${origin.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
|
|
16656
16959
|
var resolveRequestOrigin3 = (request) => {
|
|
16657
16960
|
const url = new URL(request.url);
|
|
@@ -16902,21 +17205,21 @@ var buildPlivoVoiceSetupStatus = async (options, input) => {
|
|
|
16902
17205
|
};
|
|
16903
17206
|
var renderPlivoSetupHTML = (status, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
|
|
16904
17207
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Plivo setup</p>
|
|
16905
|
-
<h1>${
|
|
17208
|
+
<h1>${escapeHtml21(title)}</h1>
|
|
16906
17209
|
<p><strong>Status:</strong> ${status.ready ? "Ready" : "Needs attention"}</p>
|
|
16907
17210
|
<ul>
|
|
16908
|
-
<li><strong>Answer XML:</strong> <code>${
|
|
16909
|
-
<li><strong>Audio stream:</strong> <code>${
|
|
16910
|
-
<li><strong>Status webhook:</strong> <code>${
|
|
17211
|
+
<li><strong>Answer XML:</strong> <code>${escapeHtml21(status.urls.answer)}</code></li>
|
|
17212
|
+
<li><strong>Audio stream:</strong> <code>${escapeHtml21(status.urls.stream)}</code></li>
|
|
17213
|
+
<li><strong>Status webhook:</strong> <code>${escapeHtml21(status.urls.webhook)}</code></li>
|
|
16911
17214
|
</ul>
|
|
16912
|
-
${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${
|
|
16913
|
-
${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${
|
|
17215
|
+
${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml21(name)}</code></li>`).join("")}</ul>` : ""}
|
|
17216
|
+
${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml21(warning)}</li>`).join("")}</ul>` : ""}
|
|
16914
17217
|
</main>`;
|
|
16915
17218
|
var renderPlivoSmokeHTML = (report, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
|
|
16916
17219
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Plivo smoke test</p>
|
|
16917
|
-
<h1>${
|
|
17220
|
+
<h1>${escapeHtml21(title)}</h1>
|
|
16918
17221
|
<p><strong>Status:</strong> ${report.pass ? "Pass" : "Fail"}</p>
|
|
16919
|
-
<ul>${report.checks.map((check) => `<li><strong>${
|
|
17222
|
+
<ul>${report.checks.map((check) => `<li><strong>${escapeHtml21(check.name)}</strong>: ${escapeHtml21(check.status)}${check.message ? ` - ${escapeHtml21(check.message)}` : ""}</li>`).join("")}</ul>
|
|
16920
17223
|
</main>`;
|
|
16921
17224
|
var runPlivoSmokeTest = async (input) => {
|
|
16922
17225
|
const setup = await buildPlivoVoiceSetupStatus(input.options, input);
|
|
@@ -17011,7 +17314,7 @@ var createPlivoVoiceRoutes = (options = {}) => {
|
|
|
17011
17314
|
request: input.request
|
|
17012
17315
|
}) : verificationUrl ?? input.request.url
|
|
17013
17316
|
}) : undefined);
|
|
17014
|
-
const app = new
|
|
17317
|
+
const app = new Elysia22({
|
|
17015
17318
|
name: options.name ?? "absolutejs-voice-plivo"
|
|
17016
17319
|
}).get(answerPath, async ({ query, request }) => {
|
|
17017
17320
|
const streamUrl = await resolvePlivoStreamUrl(options, {
|
|
@@ -17119,111 +17422,6 @@ var createPlivoVoiceRoutes = (options = {}) => {
|
|
|
17119
17422
|
return report;
|
|
17120
17423
|
});
|
|
17121
17424
|
};
|
|
17122
|
-
// src/telephony/matrix.ts
|
|
17123
|
-
import { Elysia as Elysia21 } from "elysia";
|
|
17124
|
-
var escapeHtml20 = (value) => value.replaceAll("&", "&").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
17425
|
// src/telephony/response.ts
|
|
17228
17426
|
var normalizeWhitespace = (value) => value.replace(/\s+/g, " ").trim();
|
|
17229
17427
|
var DEFAULT_MAX_WORDS = 12;
|
|
@@ -17345,6 +17543,7 @@ export {
|
|
|
17345
17543
|
renderVoiceQualityHTML,
|
|
17346
17544
|
renderVoiceProviderHealthHTML,
|
|
17347
17545
|
renderVoiceProviderCapabilityHTML,
|
|
17546
|
+
renderVoiceProductionReadinessHTML,
|
|
17348
17547
|
renderVoiceOutcomeContractHTML,
|
|
17349
17548
|
renderVoiceOpsConsoleHTML,
|
|
17350
17549
|
renderVoiceHandoffHealthHTML,
|
|
@@ -17453,6 +17652,7 @@ export {
|
|
|
17453
17652
|
createVoiceProviderCapabilityRoutes,
|
|
17454
17653
|
createVoiceProviderCapabilityJSONHandler,
|
|
17455
17654
|
createVoiceProviderCapabilityHTMLHandler,
|
|
17655
|
+
createVoiceProductionReadinessRoutes,
|
|
17456
17656
|
createVoicePostgresTraceSinkDeliveryStore,
|
|
17457
17657
|
createVoicePostgresTraceEventStore,
|
|
17458
17658
|
createVoicePostgresTelephonyWebhookIdempotencyStore,
|
|
@@ -17556,6 +17756,7 @@ export {
|
|
|
17556
17756
|
compareVoiceEvalBaseline,
|
|
17557
17757
|
claimVoiceOpsTask,
|
|
17558
17758
|
buildVoiceTraceReplay,
|
|
17759
|
+
buildVoiceProductionReadinessReport,
|
|
17559
17760
|
buildVoiceOpsTaskFromSLABreach,
|
|
17560
17761
|
buildVoiceOpsTaskFromReview,
|
|
17561
17762
|
buildVoiceOpsConsoleReport,
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { Elysia } from 'elysia';
|
|
2
|
+
import { type VoiceTelephonyCarrierMatrixInput } from './telephony/matrix';
|
|
3
|
+
import type { VoiceTraceEventStore } from './trace';
|
|
4
|
+
export type VoiceProductionReadinessStatus = 'fail' | 'pass' | 'warn';
|
|
5
|
+
export type VoiceProductionReadinessCheck = {
|
|
6
|
+
detail?: string;
|
|
7
|
+
href?: string;
|
|
8
|
+
label: string;
|
|
9
|
+
status: VoiceProductionReadinessStatus;
|
|
10
|
+
value?: number | string;
|
|
11
|
+
};
|
|
12
|
+
export type VoiceProductionReadinessReport = {
|
|
13
|
+
checkedAt: number;
|
|
14
|
+
checks: VoiceProductionReadinessCheck[];
|
|
15
|
+
links: {
|
|
16
|
+
carriers?: string;
|
|
17
|
+
handoffs?: string;
|
|
18
|
+
quality?: string;
|
|
19
|
+
resilience?: string;
|
|
20
|
+
sessions?: string;
|
|
21
|
+
};
|
|
22
|
+
status: VoiceProductionReadinessStatus;
|
|
23
|
+
summary: {
|
|
24
|
+
carriers?: {
|
|
25
|
+
failing: number;
|
|
26
|
+
providers: number;
|
|
27
|
+
ready: number;
|
|
28
|
+
status: VoiceProductionReadinessStatus;
|
|
29
|
+
warnings: number;
|
|
30
|
+
};
|
|
31
|
+
handoffs: {
|
|
32
|
+
failed: number;
|
|
33
|
+
total: number;
|
|
34
|
+
};
|
|
35
|
+
providers: {
|
|
36
|
+
degraded: number;
|
|
37
|
+
total: number;
|
|
38
|
+
};
|
|
39
|
+
quality: {
|
|
40
|
+
status: 'fail' | 'pass';
|
|
41
|
+
};
|
|
42
|
+
routing: {
|
|
43
|
+
events: number;
|
|
44
|
+
sessions: number;
|
|
45
|
+
};
|
|
46
|
+
sessions: {
|
|
47
|
+
failed: number;
|
|
48
|
+
total: number;
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
export type VoiceProductionReadinessRoutesOptions = {
|
|
53
|
+
carriers?: false | readonly VoiceTelephonyCarrierMatrixInput[] | ((input: {
|
|
54
|
+
query: Record<string, unknown>;
|
|
55
|
+
request: Request;
|
|
56
|
+
}) => Promise<readonly VoiceTelephonyCarrierMatrixInput[]> | readonly VoiceTelephonyCarrierMatrixInput[]);
|
|
57
|
+
headers?: HeadersInit;
|
|
58
|
+
htmlPath?: false | string;
|
|
59
|
+
links?: VoiceProductionReadinessReport['links'];
|
|
60
|
+
llmProviders?: readonly string[];
|
|
61
|
+
name?: string;
|
|
62
|
+
path?: string;
|
|
63
|
+
render?: (report: VoiceProductionReadinessReport) => string | Promise<string>;
|
|
64
|
+
store: VoiceTraceEventStore;
|
|
65
|
+
sttProviders?: readonly string[];
|
|
66
|
+
title?: string;
|
|
67
|
+
ttsProviders?: readonly string[];
|
|
68
|
+
};
|
|
69
|
+
export declare const buildVoiceProductionReadinessReport: (options: VoiceProductionReadinessRoutesOptions, input?: {
|
|
70
|
+
query?: Record<string, unknown>;
|
|
71
|
+
request?: Request;
|
|
72
|
+
}) => Promise<VoiceProductionReadinessReport>;
|
|
73
|
+
export declare const renderVoiceProductionReadinessHTML: (report: VoiceProductionReadinessReport, options?: {
|
|
74
|
+
title?: string;
|
|
75
|
+
}) => string;
|
|
76
|
+
export declare const createVoiceProductionReadinessRoutes: (options: VoiceProductionReadinessRoutesOptions) => Elysia<"", {
|
|
77
|
+
decorator: {};
|
|
78
|
+
store: {};
|
|
79
|
+
derive: {};
|
|
80
|
+
resolve: {};
|
|
81
|
+
}, {
|
|
82
|
+
typebox: {};
|
|
83
|
+
error: {};
|
|
84
|
+
}, {
|
|
85
|
+
schema: {};
|
|
86
|
+
standaloneSchema: {};
|
|
87
|
+
macro: {};
|
|
88
|
+
macroFn: {};
|
|
89
|
+
parser: {};
|
|
90
|
+
response: {};
|
|
91
|
+
}, {}, {
|
|
92
|
+
derive: {};
|
|
93
|
+
resolve: {};
|
|
94
|
+
schema: {};
|
|
95
|
+
standaloneSchema: {};
|
|
96
|
+
response: {};
|
|
97
|
+
}, {
|
|
98
|
+
derive: {};
|
|
99
|
+
resolve: {};
|
|
100
|
+
schema: {};
|
|
101
|
+
standaloneSchema: {};
|
|
102
|
+
response: {};
|
|
103
|
+
}>;
|