@absolutejs/voice 0.0.22-beta.385 → 0.0.22-beta.387
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/client/index.js +4362 -3271
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2720 -2621
- package/dist/proofTrends.d.ts +37 -1
- package/dist/react/index.js +2020 -172
- package/dist/vue/index.js +1969 -121
- package/package.json +1 -1
package/dist/vue/index.js
CHANGED
|
@@ -1415,7 +1415,1760 @@ var VoicePlatformCoverage = defineComponent4({
|
|
|
1415
1415
|
import { defineComponent as defineComponent5, h as h5 } from "vue";
|
|
1416
1416
|
|
|
1417
1417
|
// src/proofTrends.ts
|
|
1418
|
+
import { Elysia as Elysia4 } from "elysia";
|
|
1419
|
+
|
|
1420
|
+
// src/providerDecisionTraces.ts
|
|
1421
|
+
import { Elysia as Elysia3 } from "elysia";
|
|
1422
|
+
|
|
1423
|
+
// src/resilienceRoutes.ts
|
|
1424
|
+
import { Elysia as Elysia2 } from "elysia";
|
|
1425
|
+
|
|
1426
|
+
// src/providerHealth.ts
|
|
1418
1427
|
import { Elysia } from "elysia";
|
|
1428
|
+
var getString = (value) => typeof value === "string" ? value : undefined;
|
|
1429
|
+
var getNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
1430
|
+
var isProviderStatus = (value) => value === "success" || value === "fallback" || value === "error";
|
|
1431
|
+
var summarizeVoiceProviderHealth = async (input) => {
|
|
1432
|
+
const options = Array.isArray(input) ? { events: input } : input;
|
|
1433
|
+
const events = options.events ?? await options.store?.list() ?? [];
|
|
1434
|
+
const providers = options.providers ?? [];
|
|
1435
|
+
const providerSet = new Set(providers);
|
|
1436
|
+
const now = options.now ?? Date.now();
|
|
1437
|
+
const entries = new Map;
|
|
1438
|
+
const isAllowedProvider = (value) => typeof value === "string" && (providerSet.size === 0 || providerSet.has(value));
|
|
1439
|
+
const getEntry = (provider) => {
|
|
1440
|
+
const existing = entries.get(provider);
|
|
1441
|
+
if (existing) {
|
|
1442
|
+
return existing;
|
|
1443
|
+
}
|
|
1444
|
+
const entry = {
|
|
1445
|
+
elapsedCount: 0,
|
|
1446
|
+
elapsedTotal: 0,
|
|
1447
|
+
errorCount: 0,
|
|
1448
|
+
fallbackCount: 0,
|
|
1449
|
+
provider,
|
|
1450
|
+
rateLimited: false,
|
|
1451
|
+
recommended: false,
|
|
1452
|
+
runCount: 0,
|
|
1453
|
+
status: "idle",
|
|
1454
|
+
timeoutCount: 0
|
|
1455
|
+
};
|
|
1456
|
+
entries.set(provider, entry);
|
|
1457
|
+
return entry;
|
|
1458
|
+
};
|
|
1459
|
+
for (const provider of providers) {
|
|
1460
|
+
getEntry(provider);
|
|
1461
|
+
}
|
|
1462
|
+
const hasProviderRouterEvents = events.some((event) => event.type === "session.error" && isAllowedProvider(event.payload.provider) && isProviderStatus(event.payload.providerStatus));
|
|
1463
|
+
for (const event of events) {
|
|
1464
|
+
if (event.type === "assistant.run") {
|
|
1465
|
+
if (hasProviderRouterEvents) {
|
|
1466
|
+
continue;
|
|
1467
|
+
}
|
|
1468
|
+
const provider2 = event.payload.variantId;
|
|
1469
|
+
if (!isAllowedProvider(provider2)) {
|
|
1470
|
+
continue;
|
|
1471
|
+
}
|
|
1472
|
+
const entry2 = getEntry(provider2);
|
|
1473
|
+
entry2.runCount += 1;
|
|
1474
|
+
const elapsedMs = getNumber(event.payload.elapsedMs);
|
|
1475
|
+
if (elapsedMs !== undefined) {
|
|
1476
|
+
entry2.elapsedCount += 1;
|
|
1477
|
+
entry2.elapsedTotal += elapsedMs;
|
|
1478
|
+
}
|
|
1479
|
+
continue;
|
|
1480
|
+
}
|
|
1481
|
+
if (event.type !== "session.error") {
|
|
1482
|
+
continue;
|
|
1483
|
+
}
|
|
1484
|
+
const provider = event.payload.provider;
|
|
1485
|
+
if (!isAllowedProvider(provider)) {
|
|
1486
|
+
continue;
|
|
1487
|
+
}
|
|
1488
|
+
const providerStatus = isProviderStatus(event.payload.providerStatus) ? event.payload.providerStatus : undefined;
|
|
1489
|
+
const applyProviderHealth = () => {
|
|
1490
|
+
const entry2 = getEntry(provider);
|
|
1491
|
+
const providerHealth = event.payload.providerHealth;
|
|
1492
|
+
if (providerHealth && typeof providerHealth === "object") {
|
|
1493
|
+
const suppressedUntil2 = getNumber(providerHealth.suppressedUntil);
|
|
1494
|
+
if (suppressedUntil2 !== undefined) {
|
|
1495
|
+
entry2.suppressedUntil = suppressedUntil2;
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
const suppressedUntil = getNumber(event.payload.suppressedUntil);
|
|
1499
|
+
if (suppressedUntil !== undefined) {
|
|
1500
|
+
entry2.suppressedUntil = suppressedUntil;
|
|
1501
|
+
}
|
|
1502
|
+
const suppressionRemainingMs = getNumber(event.payload.suppressionRemainingMs);
|
|
1503
|
+
if (suppressionRemainingMs !== undefined) {
|
|
1504
|
+
entry2.suppressionRemainingMs = suppressionRemainingMs;
|
|
1505
|
+
}
|
|
1506
|
+
return entry2;
|
|
1507
|
+
};
|
|
1508
|
+
if (providerStatus === "success" || providerStatus === "fallback") {
|
|
1509
|
+
const entry2 = applyProviderHealth();
|
|
1510
|
+
entry2.runCount += 1;
|
|
1511
|
+
entry2.lastSuccessAt = event.at;
|
|
1512
|
+
if (providerStatus === "success") {
|
|
1513
|
+
entry2.lastError = undefined;
|
|
1514
|
+
entry2.rateLimited = false;
|
|
1515
|
+
entry2.suppressedUntil = undefined;
|
|
1516
|
+
entry2.suppressionRemainingMs = undefined;
|
|
1517
|
+
}
|
|
1518
|
+
const elapsedMs = getNumber(event.payload.elapsedMs);
|
|
1519
|
+
if (elapsedMs !== undefined) {
|
|
1520
|
+
entry2.elapsedCount += 1;
|
|
1521
|
+
entry2.elapsedTotal += elapsedMs;
|
|
1522
|
+
}
|
|
1523
|
+
const selectedProvider = event.payload.selectedProvider;
|
|
1524
|
+
if (providerStatus === "fallback" && isAllowedProvider(selectedProvider) && selectedProvider !== provider) {
|
|
1525
|
+
getEntry(selectedProvider).fallbackCount += 1;
|
|
1526
|
+
}
|
|
1527
|
+
continue;
|
|
1528
|
+
}
|
|
1529
|
+
const entry = applyProviderHealth();
|
|
1530
|
+
entry.errorCount += 1;
|
|
1531
|
+
if (event.payload.timedOut === true) {
|
|
1532
|
+
entry.timeoutCount += 1;
|
|
1533
|
+
}
|
|
1534
|
+
entry.lastError = getString(event.payload.error);
|
|
1535
|
+
entry.lastErrorAt = event.at;
|
|
1536
|
+
entry.rateLimited ||= event.payload.rateLimited === true;
|
|
1537
|
+
}
|
|
1538
|
+
const summaries = [...entries.values()].map((entry) => {
|
|
1539
|
+
const hadSuppression = typeof entry.suppressedUntil === "number" || typeof entry.suppressionRemainingMs === "number";
|
|
1540
|
+
const suppressionRemainingMs = typeof entry.suppressedUntil === "number" ? Math.max(0, entry.suppressedUntil - now) : entry.suppressionRemainingMs;
|
|
1541
|
+
const activeSuppression = typeof suppressionRemainingMs === "number" && suppressionRemainingMs > 0;
|
|
1542
|
+
const recoverable = hadSuppression && !activeSuppression;
|
|
1543
|
+
const averageElapsedMs = entry.elapsedCount > 0 ? Math.round(entry.elapsedTotal / entry.elapsedCount) : undefined;
|
|
1544
|
+
const status = activeSuppression ? "suppressed" : recoverable ? "recoverable" : entry.rateLimited ? "rate-limited" : entry.errorCount > 0 && (!entry.lastSuccessAt || !entry.lastErrorAt || entry.lastErrorAt > entry.lastSuccessAt) ? "degraded" : entry.runCount > 0 ? "healthy" : "idle";
|
|
1545
|
+
return {
|
|
1546
|
+
averageElapsedMs,
|
|
1547
|
+
errorCount: entry.errorCount,
|
|
1548
|
+
fallbackCount: entry.fallbackCount,
|
|
1549
|
+
lastError: entry.lastError,
|
|
1550
|
+
lastErrorAt: entry.lastErrorAt,
|
|
1551
|
+
lastSuccessAt: entry.lastSuccessAt,
|
|
1552
|
+
provider: entry.provider,
|
|
1553
|
+
rateLimited: entry.rateLimited,
|
|
1554
|
+
recommended: false,
|
|
1555
|
+
runCount: entry.runCount,
|
|
1556
|
+
status,
|
|
1557
|
+
suppressionRemainingMs: activeSuppression ? suppressionRemainingMs : undefined,
|
|
1558
|
+
suppressedUntil: entry.suppressedUntil,
|
|
1559
|
+
timeoutCount: entry.timeoutCount
|
|
1560
|
+
};
|
|
1561
|
+
});
|
|
1562
|
+
const recommended = summaries.filter((entry) => entry.status === "healthy").sort((left, right) => (left.averageElapsedMs ?? Number.MAX_SAFE_INTEGER) - (right.averageElapsedMs ?? Number.MAX_SAFE_INTEGER))[0];
|
|
1563
|
+
if (recommended) {
|
|
1564
|
+
recommended.recommended = true;
|
|
1565
|
+
}
|
|
1566
|
+
return summaries;
|
|
1567
|
+
};
|
|
1568
|
+
var escapeHtml5 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
1569
|
+
var renderVoiceProviderHealthHTML = (providers) => providers.length === 0 ? '<p class="voice-provider-empty">No provider status yet.</p>' : [
|
|
1570
|
+
'<div class="voice-provider-health">',
|
|
1571
|
+
...providers.map((provider) => {
|
|
1572
|
+
const suppressionSeconds = typeof provider.suppressionRemainingMs === "number" ? Math.ceil(provider.suppressionRemainingMs / 1000) : undefined;
|
|
1573
|
+
return [
|
|
1574
|
+
`<article class="voice-provider-card ${escapeHtml5(provider.status)}">`,
|
|
1575
|
+
'<div class="voice-provider-card-header">',
|
|
1576
|
+
`<strong>${escapeHtml5(provider.provider)}</strong>`,
|
|
1577
|
+
`<span>${escapeHtml5(provider.status)}${provider.recommended ? " \xB7 recommended" : ""}</span>`,
|
|
1578
|
+
"</div>",
|
|
1579
|
+
"<dl>",
|
|
1580
|
+
`<div><dt>Runs</dt><dd>${String(provider.runCount)}</dd></div>`,
|
|
1581
|
+
`<div><dt>Avg latency</dt><dd>${String(provider.averageElapsedMs ?? 0)}ms</dd></div>`,
|
|
1582
|
+
`<div><dt>Errors</dt><dd>${String(provider.errorCount)}</dd></div>`,
|
|
1583
|
+
`<div><dt>Timeouts</dt><dd>${String(provider.timeoutCount)}</dd></div>`,
|
|
1584
|
+
`<div><dt>Fallbacks</dt><dd>${String(provider.fallbackCount)}</dd></div>`,
|
|
1585
|
+
"</dl>",
|
|
1586
|
+
suppressionSeconds ? `<p>Temporarily suppressed for ${String(suppressionSeconds)}s.</p>` : "",
|
|
1587
|
+
provider.lastError ? `<p>${escapeHtml5(provider.lastError)}</p>` : "",
|
|
1588
|
+
"</article>"
|
|
1589
|
+
].join("");
|
|
1590
|
+
}),
|
|
1591
|
+
"</div>"
|
|
1592
|
+
].join("");
|
|
1593
|
+
var createVoiceProviderHealthJSONHandler = (options) => async () => summarizeVoiceProviderHealth(options);
|
|
1594
|
+
var createVoiceProviderHealthHTMLHandler = (options) => async () => {
|
|
1595
|
+
const providers = await summarizeVoiceProviderHealth(options);
|
|
1596
|
+
const render = options.render ?? renderVoiceProviderHealthHTML;
|
|
1597
|
+
const body = await render(providers);
|
|
1598
|
+
return new Response(body, {
|
|
1599
|
+
headers: {
|
|
1600
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
1601
|
+
...options.headers
|
|
1602
|
+
}
|
|
1603
|
+
});
|
|
1604
|
+
};
|
|
1605
|
+
var createVoiceProviderHealthRoutes = (options) => {
|
|
1606
|
+
const path = options.path ?? "/api/provider-status";
|
|
1607
|
+
const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
|
|
1608
|
+
const routes = new Elysia({
|
|
1609
|
+
name: options.name ?? "absolutejs-voice-provider-health"
|
|
1610
|
+
}).get(path, createVoiceProviderHealthJSONHandler(options));
|
|
1611
|
+
if (htmlPath) {
|
|
1612
|
+
routes.get(htmlPath, createVoiceProviderHealthHTMLHandler(options));
|
|
1613
|
+
}
|
|
1614
|
+
return routes;
|
|
1615
|
+
};
|
|
1616
|
+
|
|
1617
|
+
// src/resilienceRoutes.ts
|
|
1618
|
+
var escapeHtml6 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
1619
|
+
var getString2 = (value) => typeof value === "string" ? value : undefined;
|
|
1620
|
+
var getNumber2 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
1621
|
+
var getBoolean = (value) => value === true;
|
|
1622
|
+
var isProviderStatus2 = (value) => value === "error" || value === "fallback" || value === "success";
|
|
1623
|
+
var listVoiceRoutingEvents = (events) => {
|
|
1624
|
+
const routingEvents = [];
|
|
1625
|
+
for (const event of events) {
|
|
1626
|
+
if (event.type !== "session.error") {
|
|
1627
|
+
continue;
|
|
1628
|
+
}
|
|
1629
|
+
const provider = getString2(event.payload.provider);
|
|
1630
|
+
const providerStatus = isProviderStatus2(event.payload.providerStatus) ? event.payload.providerStatus : undefined;
|
|
1631
|
+
if (!provider || !providerStatus) {
|
|
1632
|
+
continue;
|
|
1633
|
+
}
|
|
1634
|
+
const kind = getString2(event.payload.kind);
|
|
1635
|
+
routingEvents.push({
|
|
1636
|
+
at: event.at,
|
|
1637
|
+
attempt: getNumber2(event.payload.attempt),
|
|
1638
|
+
elapsedMs: getNumber2(event.payload.elapsedMs),
|
|
1639
|
+
error: getString2(event.payload.error),
|
|
1640
|
+
fallbackProvider: getString2(event.payload.fallbackProvider),
|
|
1641
|
+
kind: kind === "stt" || kind === "tts" ? kind : "llm",
|
|
1642
|
+
latencyBudgetMs: getNumber2(event.payload.latencyBudgetMs),
|
|
1643
|
+
operation: getString2(event.payload.operation),
|
|
1644
|
+
provider,
|
|
1645
|
+
routing: getString2(event.payload.routing),
|
|
1646
|
+
scenarioId: event.scenarioId,
|
|
1647
|
+
selectedProvider: getString2(event.payload.selectedProvider),
|
|
1648
|
+
sessionId: event.sessionId,
|
|
1649
|
+
status: providerStatus,
|
|
1650
|
+
suppressionRemainingMs: getNumber2(event.payload.suppressionRemainingMs),
|
|
1651
|
+
timedOut: getBoolean(event.payload.timedOut),
|
|
1652
|
+
turnId: event.turnId
|
|
1653
|
+
});
|
|
1654
|
+
}
|
|
1655
|
+
return routingEvents.sort((left, right) => right.at - left.at);
|
|
1656
|
+
};
|
|
1657
|
+
var summarizeVoiceRoutingDecision = (events, options = {}) => {
|
|
1658
|
+
const routingEvents = listVoiceRoutingEvents(events).filter((event) => {
|
|
1659
|
+
if (options.kind && event.kind !== options.kind) {
|
|
1660
|
+
return false;
|
|
1661
|
+
}
|
|
1662
|
+
if (options.sessionId && event.sessionId !== options.sessionId) {
|
|
1663
|
+
return false;
|
|
1664
|
+
}
|
|
1665
|
+
return true;
|
|
1666
|
+
});
|
|
1667
|
+
const limited = typeof options.limit === "number" && options.limit >= 0 ? routingEvents.slice(0, options.limit) : routingEvents;
|
|
1668
|
+
return limited[0] ?? null;
|
|
1669
|
+
};
|
|
1670
|
+
var createEmptyKindSummary = () => ({
|
|
1671
|
+
errorCount: 0,
|
|
1672
|
+
fallbackCount: 0,
|
|
1673
|
+
providers: [],
|
|
1674
|
+
runCount: 0,
|
|
1675
|
+
timeoutCount: 0
|
|
1676
|
+
});
|
|
1677
|
+
var summarizeVoiceRoutingSessions = (events, options = {}) => {
|
|
1678
|
+
const routingEvents = (events.some((event) => ("payload" in event)) ? listVoiceRoutingEvents(events) : [...events]).filter((event) => !options.sessionId || event.sessionId === options.sessionId);
|
|
1679
|
+
const sessions = new Map;
|
|
1680
|
+
for (const event of routingEvents) {
|
|
1681
|
+
const existing = sessions.get(event.sessionId);
|
|
1682
|
+
const summary = existing ?? {
|
|
1683
|
+
errorCount: 0,
|
|
1684
|
+
eventCount: 0,
|
|
1685
|
+
fallbackCount: 0,
|
|
1686
|
+
kinds: {
|
|
1687
|
+
llm: createEmptyKindSummary(),
|
|
1688
|
+
stt: createEmptyKindSummary(),
|
|
1689
|
+
tts: createEmptyKindSummary()
|
|
1690
|
+
},
|
|
1691
|
+
lastEventAt: event.at,
|
|
1692
|
+
sessionId: event.sessionId,
|
|
1693
|
+
startedAt: event.at,
|
|
1694
|
+
status: "healthy",
|
|
1695
|
+
timeoutCount: 0
|
|
1696
|
+
};
|
|
1697
|
+
summary.eventCount += 1;
|
|
1698
|
+
summary.startedAt = Math.min(summary.startedAt, event.at);
|
|
1699
|
+
summary.lastEventAt = Math.max(summary.lastEventAt, event.at);
|
|
1700
|
+
if (event.status === "error") {
|
|
1701
|
+
summary.errorCount += 1;
|
|
1702
|
+
}
|
|
1703
|
+
if (event.status === "fallback") {
|
|
1704
|
+
summary.fallbackCount += 1;
|
|
1705
|
+
}
|
|
1706
|
+
if (event.timedOut) {
|
|
1707
|
+
summary.timeoutCount += 1;
|
|
1708
|
+
}
|
|
1709
|
+
const kind = summary.kinds[event.kind];
|
|
1710
|
+
kind.runCount += 1;
|
|
1711
|
+
if (event.status === "error") {
|
|
1712
|
+
kind.errorCount += 1;
|
|
1713
|
+
}
|
|
1714
|
+
if (event.status === "fallback") {
|
|
1715
|
+
kind.fallbackCount += 1;
|
|
1716
|
+
}
|
|
1717
|
+
if (event.timedOut) {
|
|
1718
|
+
kind.timeoutCount += 1;
|
|
1719
|
+
}
|
|
1720
|
+
if (event.provider && !kind.providers.includes(event.provider)) {
|
|
1721
|
+
kind.providers.push(event.provider);
|
|
1722
|
+
}
|
|
1723
|
+
if (!kind.latest || event.at > kind.latest.at) {
|
|
1724
|
+
kind.latest = event;
|
|
1725
|
+
}
|
|
1726
|
+
summary.status = summary.errorCount > 0 || summary.timeoutCount > 0 ? "degraded" : summary.fallbackCount > 0 ? "fallback" : "healthy";
|
|
1727
|
+
sessions.set(event.sessionId, summary);
|
|
1728
|
+
}
|
|
1729
|
+
const sorted = [...sessions.values()].sort((left, right) => right.lastEventAt - left.lastEventAt);
|
|
1730
|
+
return typeof options.limit === "number" && options.limit >= 0 ? sorted.slice(0, options.limit) : sorted;
|
|
1731
|
+
};
|
|
1732
|
+
var createVoiceRoutingDecisionSummary = async (options) => {
|
|
1733
|
+
const events = await options.store.list({
|
|
1734
|
+
sessionId: options.sessionId,
|
|
1735
|
+
type: "session.error"
|
|
1736
|
+
});
|
|
1737
|
+
return summarizeVoiceRoutingDecision(events, options);
|
|
1738
|
+
};
|
|
1739
|
+
var summarizeRoutingEvents = (events) => {
|
|
1740
|
+
const byKind = new Map;
|
|
1741
|
+
let errors = 0;
|
|
1742
|
+
let fallbacks = 0;
|
|
1743
|
+
let timeouts = 0;
|
|
1744
|
+
for (const event of events) {
|
|
1745
|
+
byKind.set(event.kind, (byKind.get(event.kind) ?? 0) + 1);
|
|
1746
|
+
if (event.status === "error") {
|
|
1747
|
+
errors += 1;
|
|
1748
|
+
}
|
|
1749
|
+
if (event.status === "fallback") {
|
|
1750
|
+
fallbacks += 1;
|
|
1751
|
+
}
|
|
1752
|
+
if (event.timedOut) {
|
|
1753
|
+
timeouts += 1;
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
return {
|
|
1757
|
+
byKind,
|
|
1758
|
+
errors,
|
|
1759
|
+
fallbacks,
|
|
1760
|
+
timeouts,
|
|
1761
|
+
total: events.length
|
|
1762
|
+
};
|
|
1763
|
+
};
|
|
1764
|
+
var renderProviderCards = (title, providers) => {
|
|
1765
|
+
if (providers.length === 0) {
|
|
1766
|
+
return `<p class="muted">No ${escapeHtml6(title)} provider health yet.</p>`;
|
|
1767
|
+
}
|
|
1768
|
+
return `<div class="provider-grid">${providers.map((provider) => `
|
|
1769
|
+
<article class="card provider ${escapeHtml6(provider.status)}">
|
|
1770
|
+
<div class="card-header">
|
|
1771
|
+
<strong>${escapeHtml6(provider.provider)}</strong>
|
|
1772
|
+
<span>${escapeHtml6(provider.status)}${provider.recommended ? " \xB7 recommended" : ""}</span>
|
|
1773
|
+
</div>
|
|
1774
|
+
<dl>
|
|
1775
|
+
<div><dt>Runs</dt><dd>${provider.runCount}</dd></div>
|
|
1776
|
+
<div><dt>Avg latency</dt><dd>${provider.averageElapsedMs ?? 0}ms</dd></div>
|
|
1777
|
+
<div><dt>Errors</dt><dd>${provider.errorCount}</dd></div>
|
|
1778
|
+
<div><dt>Timeouts</dt><dd>${provider.timeoutCount}</dd></div>
|
|
1779
|
+
<div><dt>Fallbacks</dt><dd>${provider.fallbackCount}</dd></div>
|
|
1780
|
+
</dl>
|
|
1781
|
+
${provider.lastError ? `<p class="muted">${escapeHtml6(provider.lastError)}</p>` : ""}
|
|
1782
|
+
</article>
|
|
1783
|
+
`).join("")}</div>`;
|
|
1784
|
+
};
|
|
1785
|
+
var renderTimeline = (events) => {
|
|
1786
|
+
if (events.length === 0) {
|
|
1787
|
+
return '<p class="muted">No provider routing events yet. Run the app or simulate provider failover.</p>';
|
|
1788
|
+
}
|
|
1789
|
+
return `<div class="timeline">${events.slice(0, 40).map((event) => `
|
|
1790
|
+
<article class="card event ${escapeHtml6(event.status ?? "unknown")}">
|
|
1791
|
+
<div class="card-header">
|
|
1792
|
+
<strong>${escapeHtml6(event.kind.toUpperCase())} ${escapeHtml6(event.operation ?? "generate")}</strong>
|
|
1793
|
+
<span>${new Date(event.at).toLocaleString()}</span>
|
|
1794
|
+
</div>
|
|
1795
|
+
<p>
|
|
1796
|
+
<span class="pill">${escapeHtml6(event.status ?? "unknown")}</span>
|
|
1797
|
+
<span class="pill">provider: ${escapeHtml6(event.provider ?? "unknown")}</span>
|
|
1798
|
+
${event.fallbackProvider ? `<span class="pill">fallback: ${escapeHtml6(event.fallbackProvider)}</span>` : ""}
|
|
1799
|
+
${event.timedOut ? '<span class="pill danger">timed out</span>' : ""}
|
|
1800
|
+
</p>
|
|
1801
|
+
<dl>
|
|
1802
|
+
<div><dt>Attempt</dt><dd>${event.attempt ?? 0}</dd></div>
|
|
1803
|
+
<div><dt>Elapsed</dt><dd>${event.elapsedMs ?? 0}ms</dd></div>
|
|
1804
|
+
<div><dt>Budget</dt><dd>${event.latencyBudgetMs ?? 0}ms</dd></div>
|
|
1805
|
+
<div><dt>Session</dt><dd>${escapeHtml6(event.sessionId)}</dd></div>
|
|
1806
|
+
</dl>
|
|
1807
|
+
${event.error ? `<p class="muted">${escapeHtml6(event.error)}</p>` : ""}
|
|
1808
|
+
</article>
|
|
1809
|
+
`).join("")}</div>`;
|
|
1810
|
+
};
|
|
1811
|
+
var renderSessionKind = (kind, summary) => {
|
|
1812
|
+
const latest = summary.latest;
|
|
1813
|
+
const provider = latest?.provider ?? summary.providers[0] ?? "none";
|
|
1814
|
+
const status = latest?.status ?? "idle";
|
|
1815
|
+
const fallback = latest?.fallbackProvider && latest.fallbackProvider !== provider ? ` -> ${latest.fallbackProvider}` : "";
|
|
1816
|
+
return `<div>
|
|
1817
|
+
<dt>${escapeHtml6(kind.toUpperCase())}</dt>
|
|
1818
|
+
<dd>${escapeHtml6(provider)}${escapeHtml6(fallback)}</dd>
|
|
1819
|
+
<small>${escapeHtml6(status)} \xB7 ${summary.runCount} event${summary.runCount === 1 ? "" : "s"} \xB7 ${summary.errorCount} error${summary.errorCount === 1 ? "" : "s"} \xB7 ${summary.fallbackCount} fallback${summary.fallbackCount === 1 ? "" : "s"}</small>
|
|
1820
|
+
</div>`;
|
|
1821
|
+
};
|
|
1822
|
+
var renderSessionSummaries = (sessions) => {
|
|
1823
|
+
if (sessions.length === 0) {
|
|
1824
|
+
return '<p class="muted">No call-level routing summaries yet. Run a voice session or provider simulation.</p>';
|
|
1825
|
+
}
|
|
1826
|
+
return `<div class="session-grid">${sessions.slice(0, 12).map((session) => `
|
|
1827
|
+
<article class="card session ${escapeHtml6(session.status)}">
|
|
1828
|
+
<div class="card-header">
|
|
1829
|
+
<strong>${escapeHtml6(session.sessionId)}</strong>
|
|
1830
|
+
<span>${escapeHtml6(session.status)}</span>
|
|
1831
|
+
</div>
|
|
1832
|
+
<p>
|
|
1833
|
+
<span class="pill">${session.eventCount} routing events</span>
|
|
1834
|
+
<span class="pill">${session.fallbackCount} fallbacks</span>
|
|
1835
|
+
<span class="pill">${session.errorCount} errors</span>
|
|
1836
|
+
<span class="pill">${session.timeoutCount} timeouts</span>
|
|
1837
|
+
</p>
|
|
1838
|
+
<dl>
|
|
1839
|
+
${renderSessionKind("llm", session.kinds.llm)}
|
|
1840
|
+
${renderSessionKind("stt", session.kinds.stt)}
|
|
1841
|
+
${renderSessionKind("tts", session.kinds.tts)}
|
|
1842
|
+
</dl>
|
|
1843
|
+
</article>
|
|
1844
|
+
`).join("")}</div>`;
|
|
1845
|
+
};
|
|
1846
|
+
var renderSimulationControls = (kind, simulation) => {
|
|
1847
|
+
if (!simulation) {
|
|
1848
|
+
return "";
|
|
1849
|
+
}
|
|
1850
|
+
const configuredProviders = simulation.providers.filter((provider) => provider.configured !== false);
|
|
1851
|
+
if (configuredProviders.length === 0) {
|
|
1852
|
+
return `<p class="muted">No ${kind.toUpperCase()} providers are configured for simulation.</p>`;
|
|
1853
|
+
}
|
|
1854
|
+
const pathPrefix = simulation.pathPrefix ?? `/api/${kind}-simulate`;
|
|
1855
|
+
const failureProviders = simulation.failureProviders ?? configuredProviders.map(({ provider }) => provider);
|
|
1856
|
+
const canFail = (provider) => configuredProviders.some((entry) => entry.provider === provider) && (!simulation.fallbackRequiredProvider || configuredProviders.some((entry) => entry.provider === simulation.fallbackRequiredProvider));
|
|
1857
|
+
return `<div class="simulate-panel" data-sim-kind="${kind}" data-sim-prefix="${escapeHtml6(pathPrefix)}">
|
|
1858
|
+
<p class="muted">${escapeHtml6(simulation.failureMessage ?? `Simulate ${kind.toUpperCase()} provider failure without changing provider credentials.`)}</p>
|
|
1859
|
+
<div class="simulate-actions">
|
|
1860
|
+
${failureProviders.map((provider) => `<button type="button" data-provider-fail="${escapeHtml6(provider)}"${canFail(provider) ? "" : " disabled"}>Simulate ${escapeHtml6(provider)} ${kind.toUpperCase()} failure</button>`).join("")}
|
|
1861
|
+
${configuredProviders.map((provider) => `<button type="button" data-provider-recover="${escapeHtml6(provider.provider)}">Mark ${escapeHtml6(provider.provider)} recovered</button>`).join("")}
|
|
1862
|
+
</div>
|
|
1863
|
+
${simulation.fallbackRequiredProvider && !configuredProviders.some((entry) => entry.provider === simulation.fallbackRequiredProvider) ? `<p class="muted">${escapeHtml6(simulation.fallbackRequiredMessage ?? `Configure ${simulation.fallbackRequiredProvider} to enable fallback simulation.`)}</p>` : ""}
|
|
1864
|
+
<pre class="simulate-output" hidden></pre>
|
|
1865
|
+
</div>`;
|
|
1866
|
+
};
|
|
1867
|
+
var renderVoiceResilienceHTML = (input) => {
|
|
1868
|
+
const summary = summarizeRoutingEvents(input.routingEvents);
|
|
1869
|
+
const kindCounts = [...summary.byKind.entries()].map(([kind, count]) => `<span class="pill">${escapeHtml6(kind)}: ${String(count)}</span>`).join("");
|
|
1870
|
+
const links = input.links?.length ? input.links.map((link) => `<a href="${escapeHtml6(link.href)}">${escapeHtml6(link.label)}</a>`).join(" \xB7 ") : "";
|
|
1871
|
+
const snippet = escapeHtml6(`const sttSimulator = createVoiceIOProviderFailureSimulator({
|
|
1872
|
+
kind: 'stt',
|
|
1873
|
+
providers: ['deepgram', 'assemblyai'],
|
|
1874
|
+
fallback: ['deepgram', 'assemblyai'],
|
|
1875
|
+
onProviderEvent: async (event, input) => {
|
|
1876
|
+
await traceStore.append({
|
|
1877
|
+
at: event.at,
|
|
1878
|
+
payload: { ...event, providerStatus: event.status },
|
|
1879
|
+
sessionId: input.sessionId,
|
|
1880
|
+
type: 'session.error'
|
|
1881
|
+
});
|
|
1882
|
+
}
|
|
1883
|
+
});
|
|
1884
|
+
|
|
1885
|
+
app.use(
|
|
1886
|
+
createVoiceResilienceRoutes({
|
|
1887
|
+
store: traceStore,
|
|
1888
|
+
sttProviders: ['deepgram', 'assemblyai'],
|
|
1889
|
+
sttSimulation: {
|
|
1890
|
+
failureProviders: ['deepgram'],
|
|
1891
|
+
fallbackRequiredProvider: 'assemblyai',
|
|
1892
|
+
providers: [{ provider: 'deepgram' }, { provider: 'assemblyai' }],
|
|
1893
|
+
run: sttSimulator.run
|
|
1894
|
+
}
|
|
1895
|
+
})
|
|
1896
|
+
);
|
|
1897
|
+
|
|
1898
|
+
app.use(
|
|
1899
|
+
createVoiceProductionReadinessRoutes({
|
|
1900
|
+
links: { resilience: '/resilience' },
|
|
1901
|
+
store: traceStore
|
|
1902
|
+
})
|
|
1903
|
+
);`);
|
|
1904
|
+
return `<!doctype html>
|
|
1905
|
+
<html lang="en">
|
|
1906
|
+
<head>
|
|
1907
|
+
<meta charset="utf-8" />
|
|
1908
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
1909
|
+
<title>${escapeHtml6(input.title ?? "AbsoluteJS Voice Resilience")}</title>
|
|
1910
|
+
<style>
|
|
1911
|
+
:root { color-scheme: dark; }
|
|
1912
|
+
body { background: radial-gradient(circle at top left, #172554, #09090b 36%, #050505); color: #f4f4f5; font-family: ui-sans-serif, system-ui, sans-serif; margin: 0; padding: 24px; }
|
|
1913
|
+
main { display: grid; gap: 16px; margin: 0 auto; max-width: 1180px; }
|
|
1914
|
+
section, .card { background: rgba(19, 22, 27, 0.92); border: 1px solid #27272a; border-radius: 20px; padding: 20px; }
|
|
1915
|
+
.hero { background: linear-gradient(135deg, rgba(14, 165, 233, 0.18), rgba(245, 158, 11, 0.12)); }
|
|
1916
|
+
.grid, .provider-grid { display: grid; gap: 14px; grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
|
1917
|
+
.session-grid { display: grid; gap: 14px; grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
1918
|
+
.timeline { display: grid; gap: 12px; }
|
|
1919
|
+
.card-header { align-items: center; display: flex; gap: 12px; justify-content: space-between; }
|
|
1920
|
+
.card-header strong { font-size: 1.05rem; }
|
|
1921
|
+
.metric strong { display: block; font-size: 2rem; margin-top: 6px; }
|
|
1922
|
+
.muted, dt, span { color: #a1a1aa; }
|
|
1923
|
+
dl { display: grid; gap: 8px; grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
|
1924
|
+
dl div { background: #0f1217; border: 1px solid #27272a; border-radius: 12px; padding: 10px; }
|
|
1925
|
+
dd { font-weight: 800; margin: 4px 0 0; }
|
|
1926
|
+
.pill { background: #0f1217; border: 1px solid #3f3f46; border-radius: 999px; color: #d4d4d8; display: inline-flex; margin: 3px 4px 3px 0; padding: 5px 9px; }
|
|
1927
|
+
.danger { border-color: rgba(239, 68, 68, 0.75); color: #fecaca; }
|
|
1928
|
+
.event.error { border-color: rgba(239, 68, 68, 0.7); }
|
|
1929
|
+
.event.fallback, .session.fallback { border-color: rgba(245, 158, 11, 0.7); }
|
|
1930
|
+
.event.success, .provider.healthy, .session.healthy { border-color: rgba(34, 197, 94, 0.5); }
|
|
1931
|
+
.session.degraded { border-color: rgba(239, 68, 68, 0.7); }
|
|
1932
|
+
.provider.suppressed, .provider.degraded, .provider.rate-limited { border-color: rgba(239, 68, 68, 0.7); }
|
|
1933
|
+
.provider.recoverable { border-color: rgba(59, 130, 246, 0.7); }
|
|
1934
|
+
button { background: #f59e0b; border: 0; border-radius: 999px; color: #111827; cursor: pointer; font-weight: 800; padding: 10px 14px; }
|
|
1935
|
+
button:disabled { cursor: not-allowed; opacity: 0.45; }
|
|
1936
|
+
.simulate-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 12px; }
|
|
1937
|
+
.simulate-output { background: #050505; border: 1px solid #27272a; border-radius: 14px; color: #d4d4d8; overflow: auto; padding: 12px; white-space: pre-wrap; }
|
|
1938
|
+
.primitive { border-color: rgba(245, 158, 11, 0.45); }
|
|
1939
|
+
.primitive pre { background: #050505; border: 1px solid #27272a; border-radius: 14px; color: #fef3c7; overflow: auto; padding: 14px; }
|
|
1940
|
+
.primitive code { color: #fef3c7; }
|
|
1941
|
+
a { color: #f59e0b; }
|
|
1942
|
+
@media (max-width: 850px) { .grid, .provider-grid, .session-grid, dl { grid-template-columns: 1fr; } }
|
|
1943
|
+
</style>
|
|
1944
|
+
</head>
|
|
1945
|
+
<body>
|
|
1946
|
+
<main>
|
|
1947
|
+
<section class="hero">
|
|
1948
|
+
<h1>Provider routing and resilience</h1>
|
|
1949
|
+
<p>One view for the production reliability story: LLM failover, STT/TTS routing, latency budgets, timeouts, and fallback decisions.</p>
|
|
1950
|
+
${links ? `<p>${links}</p>` : ""}
|
|
1951
|
+
<p>${kindCounts || '<span class="pill">No routing events yet</span>'}</p>
|
|
1952
|
+
</section>
|
|
1953
|
+
<section class="primitive">
|
|
1954
|
+
<p class="muted">Copy into your app</p>
|
|
1955
|
+
<h2><code>createVoiceResilienceRoutes(...)</code> builds this failover proof surface</h2>
|
|
1956
|
+
<p class="muted">Mount one route group for provider health, routing traces, and failure simulation. Feed the same trace store into production readiness so unresolved provider errors fail the deploy gate while recovered fallback stays visible.</p>
|
|
1957
|
+
<pre><code>${snippet}</code></pre>
|
|
1958
|
+
</section>
|
|
1959
|
+
<section class="grid">
|
|
1960
|
+
<article class="card metric"><span>Total routing events</span><strong>${summary.total}</strong></article>
|
|
1961
|
+
<article class="card metric"><span>Fallbacks</span><strong>${summary.fallbacks}</strong></article>
|
|
1962
|
+
<article class="card metric"><span>Errors</span><strong>${summary.errors}</strong></article>
|
|
1963
|
+
<article class="card metric"><span>Timeouts</span><strong>${summary.timeouts}</strong></article>
|
|
1964
|
+
</section>
|
|
1965
|
+
<section>
|
|
1966
|
+
<h2>Call-level routing summaries</h2>
|
|
1967
|
+
<p class="muted">A compact per-call view of which LLM, STT, and TTS providers handled the session, including fallback and timeout counts.</p>
|
|
1968
|
+
${renderSessionSummaries(input.routingSessions)}
|
|
1969
|
+
</section>
|
|
1970
|
+
<section>
|
|
1971
|
+
<h2>LLM provider health</h2>
|
|
1972
|
+
${renderProviderCards("LLM", input.llmProviderHealth)}
|
|
1973
|
+
</section>
|
|
1974
|
+
<section>
|
|
1975
|
+
<h2>STT provider health</h2>
|
|
1976
|
+
${renderSimulationControls("stt", input.sttSimulation)}
|
|
1977
|
+
${renderProviderCards("STT", input.sttProviderHealth)}
|
|
1978
|
+
</section>
|
|
1979
|
+
<section>
|
|
1980
|
+
<h2>TTS provider health</h2>
|
|
1981
|
+
${renderSimulationControls("tts", input.ttsSimulation)}
|
|
1982
|
+
${renderProviderCards("TTS", input.ttsProviderHealth)}
|
|
1983
|
+
</section>
|
|
1984
|
+
<section>
|
|
1985
|
+
<h2>Routing timeline</h2>
|
|
1986
|
+
${renderTimeline(input.routingEvents)}
|
|
1987
|
+
</section>
|
|
1988
|
+
</main>
|
|
1989
|
+
<script>
|
|
1990
|
+
const showResult = (panel, result) => {
|
|
1991
|
+
const output = panel.querySelector(".simulate-output");
|
|
1992
|
+
if (!output) return;
|
|
1993
|
+
output.hidden = false;
|
|
1994
|
+
output.textContent = JSON.stringify(result, null, 2);
|
|
1995
|
+
};
|
|
1996
|
+
document.querySelectorAll("[data-sim-prefix]").forEach((panel) => {
|
|
1997
|
+
const prefix = panel.getAttribute("data-sim-prefix");
|
|
1998
|
+
panel.querySelectorAll("[data-provider-fail]").forEach((button) => {
|
|
1999
|
+
button.addEventListener("click", async () => {
|
|
2000
|
+
const provider = button.getAttribute("data-provider-fail");
|
|
2001
|
+
const response = await fetch(prefix + "/failure?provider=" + encodeURIComponent(provider || ""), { method: "POST" });
|
|
2002
|
+
showResult(panel, await response.json());
|
|
2003
|
+
if (response.ok) window.setTimeout(() => window.location.reload(), 450);
|
|
2004
|
+
});
|
|
2005
|
+
});
|
|
2006
|
+
panel.querySelectorAll("[data-provider-recover]").forEach((button) => {
|
|
2007
|
+
button.addEventListener("click", async () => {
|
|
2008
|
+
const provider = button.getAttribute("data-provider-recover");
|
|
2009
|
+
const response = await fetch(prefix + "/recovery?provider=" + encodeURIComponent(provider || ""), { method: "POST" });
|
|
2010
|
+
showResult(panel, await response.json());
|
|
2011
|
+
if (response.ok) window.setTimeout(() => window.location.reload(), 450);
|
|
2012
|
+
});
|
|
2013
|
+
});
|
|
2014
|
+
});
|
|
2015
|
+
</script>
|
|
2016
|
+
</body>
|
|
2017
|
+
</html>`;
|
|
2018
|
+
};
|
|
2019
|
+
var providerFromQuery = (value, providers) => typeof value === "string" && providers.some((provider) => provider.provider === value && provider.configured !== false) ? value : undefined;
|
|
2020
|
+
var registerSimulationRoutes = (routes, simulation, defaultPathPrefix) => {
|
|
2021
|
+
if (!simulation) {
|
|
2022
|
+
return routes;
|
|
2023
|
+
}
|
|
2024
|
+
const pathPrefix = simulation.pathPrefix ?? defaultPathPrefix;
|
|
2025
|
+
routes.post(`${pathPrefix}/failure`, async ({ query, set }) => {
|
|
2026
|
+
const provider = providerFromQuery(query.provider, simulation.providers);
|
|
2027
|
+
if (!provider) {
|
|
2028
|
+
set.status = 400;
|
|
2029
|
+
return {
|
|
2030
|
+
error: "Provider is not configured for simulation."
|
|
2031
|
+
};
|
|
2032
|
+
}
|
|
2033
|
+
if (simulation.failureProviders && !simulation.failureProviders.includes(provider)) {
|
|
2034
|
+
set.status = 400;
|
|
2035
|
+
return {
|
|
2036
|
+
error: `${provider} is not configured for failure simulation.`
|
|
2037
|
+
};
|
|
2038
|
+
}
|
|
2039
|
+
if (simulation.fallbackRequiredProvider && !simulation.providers.some((entry) => entry.provider === simulation.fallbackRequiredProvider && entry.configured !== false)) {
|
|
2040
|
+
set.status = 400;
|
|
2041
|
+
return {
|
|
2042
|
+
error: simulation.fallbackRequiredMessage ?? `Configure ${simulation.fallbackRequiredProvider} before simulating fallback.`
|
|
2043
|
+
};
|
|
2044
|
+
}
|
|
2045
|
+
return simulation.run(provider, "failure");
|
|
2046
|
+
});
|
|
2047
|
+
routes.post(`${pathPrefix}/recovery`, async ({ query, set }) => {
|
|
2048
|
+
const provider = providerFromQuery(query.provider, simulation.providers);
|
|
2049
|
+
if (!provider) {
|
|
2050
|
+
set.status = 400;
|
|
2051
|
+
return {
|
|
2052
|
+
error: "Provider is not configured for simulation."
|
|
2053
|
+
};
|
|
2054
|
+
}
|
|
2055
|
+
return simulation.run(provider, "recovery");
|
|
2056
|
+
});
|
|
2057
|
+
return routes;
|
|
2058
|
+
};
|
|
2059
|
+
var createVoiceResilienceRoutes = (options) => {
|
|
2060
|
+
const path = options.path ?? "/resilience";
|
|
2061
|
+
const routes = new Elysia2({
|
|
2062
|
+
name: options.name ?? "absolutejs-voice-resilience"
|
|
2063
|
+
}).get(path, async () => {
|
|
2064
|
+
const events = await options.store.list();
|
|
2065
|
+
const sttEvents = events.filter((event) => event.payload.kind === "stt");
|
|
2066
|
+
const ttsEvents = events.filter((event) => event.payload.kind === "tts");
|
|
2067
|
+
const routingEvents = listVoiceRoutingEvents(events);
|
|
2068
|
+
const data = {
|
|
2069
|
+
links: options.links,
|
|
2070
|
+
llmProviderHealth: await summarizeVoiceProviderHealth({
|
|
2071
|
+
events,
|
|
2072
|
+
providers: options.llmProviders ?? []
|
|
2073
|
+
}),
|
|
2074
|
+
routingEvents,
|
|
2075
|
+
routingSessions: summarizeVoiceRoutingSessions(routingEvents),
|
|
2076
|
+
sttProviderHealth: await summarizeVoiceProviderHealth({
|
|
2077
|
+
events: sttEvents,
|
|
2078
|
+
providers: options.sttProviders ?? []
|
|
2079
|
+
}),
|
|
2080
|
+
sttSimulation: options.sttSimulation,
|
|
2081
|
+
title: options.title,
|
|
2082
|
+
ttsProviderHealth: await summarizeVoiceProviderHealth({
|
|
2083
|
+
events: ttsEvents,
|
|
2084
|
+
providers: options.ttsProviders ?? []
|
|
2085
|
+
}),
|
|
2086
|
+
ttsSimulation: options.ttsSimulation
|
|
2087
|
+
};
|
|
2088
|
+
const body = await (options.render ?? renderVoiceResilienceHTML)(data);
|
|
2089
|
+
return new Response(body, {
|
|
2090
|
+
headers: {
|
|
2091
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
2092
|
+
...options.headers
|
|
2093
|
+
}
|
|
2094
|
+
});
|
|
2095
|
+
});
|
|
2096
|
+
registerSimulationRoutes(routes, options.sttSimulation, "/api/stt-simulate");
|
|
2097
|
+
registerSimulationRoutes(routes, options.ttsSimulation, "/api/tts-simulate");
|
|
2098
|
+
return routes;
|
|
2099
|
+
};
|
|
2100
|
+
|
|
2101
|
+
// src/providerDecisionTraces.ts
|
|
2102
|
+
var escapeHtml7 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
2103
|
+
var getString3 = (value) => typeof value === "string" ? value : undefined;
|
|
2104
|
+
var getNumber3 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
2105
|
+
var isDecisionTrace = (event) => Boolean(event && typeof event === "object" && "provider" in event && "reason" in event && "sessionId" in event && "status" in event && "surface" in event);
|
|
2106
|
+
var surfaceForKind = (kind) => {
|
|
2107
|
+
switch (kind) {
|
|
2108
|
+
case "stt":
|
|
2109
|
+
return "live-stt";
|
|
2110
|
+
case "tts":
|
|
2111
|
+
return "telephony-tts";
|
|
2112
|
+
case "llm":
|
|
2113
|
+
default:
|
|
2114
|
+
return "live-call";
|
|
2115
|
+
}
|
|
2116
|
+
};
|
|
2117
|
+
var statusRank = { fail: 2, pass: 0, warn: 1 };
|
|
2118
|
+
var reportStatus = (issues) => issues.reduce((status, issue) => statusRank[issue.status] > statusRank[status] ? issue.status : status, "pass");
|
|
2119
|
+
var uniqueSorted = (values) => [
|
|
2120
|
+
...new Set(values.filter((value) => typeof value === "string"))
|
|
2121
|
+
].sort();
|
|
2122
|
+
var createVoiceProviderDecisionTraceEvent = (input) => {
|
|
2123
|
+
const surface = input.surface ?? surfaceForKind(input.kind);
|
|
2124
|
+
const reason = input.reason ?? (input.status === "degraded" ? `Provider ${input.provider} degraded to ${input.fallbackProvider ?? input.selectedProvider ?? "lower-fidelity fallback"}.` : input.status === "fallback" ? `Fallback from ${input.provider} to ${input.fallbackProvider ?? input.selectedProvider ?? "next provider"}.` : input.status === "error" ? `Provider ${input.provider} errored before recovery.` : input.status === "skipped" ? `Provider ${input.provider} was skipped by policy.` : `Provider ${input.selectedProvider ?? input.provider} selected by policy.`);
|
|
2125
|
+
return {
|
|
2126
|
+
at: input.at ?? Date.now(),
|
|
2127
|
+
payload: {
|
|
2128
|
+
...input,
|
|
2129
|
+
providerDecision: true,
|
|
2130
|
+
reason,
|
|
2131
|
+
surface
|
|
2132
|
+
},
|
|
2133
|
+
scenarioId: input.scenarioId,
|
|
2134
|
+
sessionId: input.sessionId ?? `${surface}-provider-decision`,
|
|
2135
|
+
turnId: input.turnId,
|
|
2136
|
+
type: "provider.decision"
|
|
2137
|
+
};
|
|
2138
|
+
};
|
|
2139
|
+
var listVoiceProviderDecisionTraces = (events) => {
|
|
2140
|
+
if (events.every(isDecisionTrace)) {
|
|
2141
|
+
return [...events].sort((left, right) => right.at - left.at);
|
|
2142
|
+
}
|
|
2143
|
+
const traceEvents = events;
|
|
2144
|
+
const explicit = traceEvents.filter((event) => event.type === "provider.decision").map((event) => {
|
|
2145
|
+
const provider = getString3(event.payload.provider);
|
|
2146
|
+
const status = getString3(event.payload.status);
|
|
2147
|
+
const surface = getString3(event.payload.surface);
|
|
2148
|
+
if (!provider || !surface || status !== "error" && status !== "fallback" && status !== "degraded" && status !== "selected" && status !== "skipped" && status !== "success") {
|
|
2149
|
+
return;
|
|
2150
|
+
}
|
|
2151
|
+
return {
|
|
2152
|
+
at: event.at,
|
|
2153
|
+
elapsedMs: getNumber3(event.payload.elapsedMs),
|
|
2154
|
+
error: getString3(event.payload.error),
|
|
2155
|
+
fallbackProvider: getString3(event.payload.fallbackProvider),
|
|
2156
|
+
kind: event.payload.kind === "llm" || event.payload.kind === "stt" || event.payload.kind === "tts" ? event.payload.kind : undefined,
|
|
2157
|
+
latencyBudgetMs: getNumber3(event.payload.latencyBudgetMs),
|
|
2158
|
+
provider,
|
|
2159
|
+
reason: getString3(event.payload.reason) ?? `Provider ${provider} emitted ${status}.`,
|
|
2160
|
+
scenarioId: event.scenarioId,
|
|
2161
|
+
selectedProvider: getString3(event.payload.selectedProvider),
|
|
2162
|
+
sessionId: event.sessionId,
|
|
2163
|
+
status,
|
|
2164
|
+
surface,
|
|
2165
|
+
turnId: event.turnId
|
|
2166
|
+
};
|
|
2167
|
+
}).filter((event) => Boolean(event));
|
|
2168
|
+
const routing = listVoiceRoutingEvents(traceEvents).map((event) => ({
|
|
2169
|
+
at: event.at,
|
|
2170
|
+
elapsedMs: event.elapsedMs,
|
|
2171
|
+
error: event.error,
|
|
2172
|
+
fallbackProvider: event.fallbackProvider,
|
|
2173
|
+
kind: event.kind,
|
|
2174
|
+
latencyBudgetMs: event.latencyBudgetMs,
|
|
2175
|
+
provider: event.provider ?? event.selectedProvider ?? "unknown",
|
|
2176
|
+
reason: event.status === "fallback" ? `Fallback selected ${event.selectedProvider ?? event.fallbackProvider ?? "next provider"} after ${event.provider ?? "provider"} failed.` : event.status === "error" ? `Provider ${event.provider ?? "unknown"} errored before fallback recovery.` : `Provider ${event.selectedProvider ?? event.provider ?? "unknown"} completed successfully.`,
|
|
2177
|
+
scenarioId: event.scenarioId,
|
|
2178
|
+
selectedProvider: event.selectedProvider,
|
|
2179
|
+
sessionId: event.sessionId,
|
|
2180
|
+
status: event.status === "fallback" || event.status === "error" ? event.status : "success",
|
|
2181
|
+
surface: getString3(event.surface) ?? surfaceForKind(event.kind),
|
|
2182
|
+
turnId: event.turnId
|
|
2183
|
+
}));
|
|
2184
|
+
return [...explicit, ...routing].sort((left, right) => right.at - left.at);
|
|
2185
|
+
};
|
|
2186
|
+
var buildVoiceProviderDecisionTraceReport = async (options) => {
|
|
2187
|
+
const now = options.now ?? Date.now();
|
|
2188
|
+
const rawEvents = options.events ?? await options.store?.list() ?? [];
|
|
2189
|
+
const decisions = listVoiceProviderDecisionTraces(rawEvents).filter((decision) => {
|
|
2190
|
+
if (options.sessionId && decision.sessionId !== options.sessionId) {
|
|
2191
|
+
return false;
|
|
2192
|
+
}
|
|
2193
|
+
if (options.maxAgeMs !== undefined && now - decision.at > options.maxAgeMs) {
|
|
2194
|
+
return false;
|
|
2195
|
+
}
|
|
2196
|
+
return true;
|
|
2197
|
+
});
|
|
2198
|
+
const surfaces = new Map;
|
|
2199
|
+
const issues = [];
|
|
2200
|
+
for (const decision of decisions) {
|
|
2201
|
+
const group = surfaces.get(decision.surface) ?? [];
|
|
2202
|
+
group.push(decision);
|
|
2203
|
+
surfaces.set(decision.surface, group);
|
|
2204
|
+
}
|
|
2205
|
+
for (const surface of options.requiredSurfaces ?? []) {
|
|
2206
|
+
if (!surfaces.has(surface)) {
|
|
2207
|
+
issues.push({
|
|
2208
|
+
code: "voice.provider_decision_trace.surface_missing",
|
|
2209
|
+
message: `Surface ${surface} has no provider decision traces.`,
|
|
2210
|
+
status: "fail",
|
|
2211
|
+
surface
|
|
2212
|
+
});
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
const fallbackCount = decisions.filter((decision) => decision.status === "fallback").length;
|
|
2216
|
+
const degradedCount = decisions.filter((decision) => decision.status === "degraded").length;
|
|
2217
|
+
const statuses = new Set(decisions.map((decision) => decision.status));
|
|
2218
|
+
const providers = uniqueSorted(decisions.flatMap((decision) => [
|
|
2219
|
+
decision.provider,
|
|
2220
|
+
decision.selectedProvider,
|
|
2221
|
+
decision.fallbackProvider
|
|
2222
|
+
]));
|
|
2223
|
+
const fallbackProviders = uniqueSorted(decisions.flatMap((decision) => [
|
|
2224
|
+
decision.fallbackProvider,
|
|
2225
|
+
decision.status === "fallback" || decision.status === "degraded" ? decision.selectedProvider : undefined
|
|
2226
|
+
]));
|
|
2227
|
+
if (options.minDecisions !== undefined && decisions.length < options.minDecisions) {
|
|
2228
|
+
issues.push({
|
|
2229
|
+
code: "voice.provider_decision_trace.min_decisions",
|
|
2230
|
+
message: `Found ${String(decisions.length)} provider decision trace(s); expected at least ${String(options.minDecisions)}.`,
|
|
2231
|
+
status: "fail"
|
|
2232
|
+
});
|
|
2233
|
+
}
|
|
2234
|
+
if (options.minFallbacks !== undefined && fallbackCount < options.minFallbacks) {
|
|
2235
|
+
issues.push({
|
|
2236
|
+
code: "voice.provider_decision_trace.min_fallbacks",
|
|
2237
|
+
message: `Found ${String(fallbackCount)} provider fallback trace(s); expected at least ${String(options.minFallbacks)}.`,
|
|
2238
|
+
status: "fail"
|
|
2239
|
+
});
|
|
2240
|
+
}
|
|
2241
|
+
if (options.minDegraded !== undefined && degradedCount < options.minDegraded) {
|
|
2242
|
+
issues.push({
|
|
2243
|
+
code: "voice.provider_decision_trace.min_degraded",
|
|
2244
|
+
message: `Found ${String(degradedCount)} provider degradation trace(s); expected at least ${String(options.minDegraded)}.`,
|
|
2245
|
+
status: "fail"
|
|
2246
|
+
});
|
|
2247
|
+
}
|
|
2248
|
+
for (const status of options.requiredStatuses ?? []) {
|
|
2249
|
+
if (!statuses.has(status)) {
|
|
2250
|
+
issues.push({
|
|
2251
|
+
code: "voice.provider_decision_trace.status_missing",
|
|
2252
|
+
message: `Missing provider decision status: ${status}.`,
|
|
2253
|
+
status: "fail"
|
|
2254
|
+
});
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
for (const provider of options.requiredProviders ?? []) {
|
|
2258
|
+
if (!providers.includes(provider)) {
|
|
2259
|
+
issues.push({
|
|
2260
|
+
code: "voice.provider_decision_trace.provider_missing",
|
|
2261
|
+
message: `Missing provider decision provider: ${provider}.`,
|
|
2262
|
+
status: "fail"
|
|
2263
|
+
});
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
for (const provider of options.requiredFallbackProviders ?? []) {
|
|
2267
|
+
if (!fallbackProviders.includes(provider)) {
|
|
2268
|
+
issues.push({
|
|
2269
|
+
code: "voice.provider_decision_trace.fallback_provider_missing",
|
|
2270
|
+
message: `Missing provider decision fallback provider: ${provider}.`,
|
|
2271
|
+
status: "fail"
|
|
2272
|
+
});
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
for (const phrase of options.requiredReasonIncludes ?? []) {
|
|
2276
|
+
if (!decisions.some((decision) => decision.reason.includes(phrase))) {
|
|
2277
|
+
issues.push({
|
|
2278
|
+
code: "voice.provider_decision_trace.reason_missing",
|
|
2279
|
+
message: `Missing provider decision reason containing: ${phrase}.`,
|
|
2280
|
+
status: "fail"
|
|
2281
|
+
});
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
const surfaceReports = [...surfaces.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([surface, surfaceDecisions]) => {
|
|
2285
|
+
const surfaceIssues = issues.filter((issue) => issue.surface === surface);
|
|
2286
|
+
return {
|
|
2287
|
+
degraded: surfaceDecisions.filter((decision) => decision.status === "degraded").length,
|
|
2288
|
+
decisions: surfaceDecisions.length,
|
|
2289
|
+
errors: surfaceDecisions.filter((decision) => decision.status === "error").length,
|
|
2290
|
+
fallbacks: surfaceDecisions.filter((decision) => decision.status === "fallback").length,
|
|
2291
|
+
issues: surfaceIssues,
|
|
2292
|
+
latestAt: Math.max(...surfaceDecisions.map((decision) => decision.at)),
|
|
2293
|
+
providers: uniqueSorted(surfaceDecisions.flatMap((decision) => [
|
|
2294
|
+
decision.provider,
|
|
2295
|
+
decision.selectedProvider,
|
|
2296
|
+
decision.fallbackProvider
|
|
2297
|
+
])),
|
|
2298
|
+
reasons: uniqueSorted(surfaceDecisions.map((decision) => decision.reason)),
|
|
2299
|
+
selected: surfaceDecisions.filter((decision) => decision.status === "selected" || decision.status === "success").length,
|
|
2300
|
+
status: reportStatus(surfaceIssues),
|
|
2301
|
+
surface
|
|
2302
|
+
};
|
|
2303
|
+
});
|
|
2304
|
+
return {
|
|
2305
|
+
checkedAt: now,
|
|
2306
|
+
decisions,
|
|
2307
|
+
issues,
|
|
2308
|
+
status: reportStatus(issues),
|
|
2309
|
+
summary: {
|
|
2310
|
+
degraded: degradedCount,
|
|
2311
|
+
decisions: decisions.length,
|
|
2312
|
+
errors: decisions.filter((decision) => decision.status === "error").length,
|
|
2313
|
+
fallbacks: fallbackCount,
|
|
2314
|
+
providers: providers.length,
|
|
2315
|
+
selected: decisions.filter((decision) => decision.status === "selected" || decision.status === "success").length,
|
|
2316
|
+
surfaces: surfaces.size
|
|
2317
|
+
},
|
|
2318
|
+
surfaces: surfaceReports
|
|
2319
|
+
};
|
|
2320
|
+
};
|
|
2321
|
+
var renderVoiceProviderDecisionTraceMarkdown = (report) => [
|
|
2322
|
+
"# Voice Provider Decision Traces",
|
|
2323
|
+
"",
|
|
2324
|
+
`Status: **${report.status}**`,
|
|
2325
|
+
`Decisions: ${String(report.summary.decisions)}`,
|
|
2326
|
+
`Providers: ${String(report.summary.providers)}`,
|
|
2327
|
+
`Fallbacks: ${String(report.summary.fallbacks)}`,
|
|
2328
|
+
`Degraded: ${String(report.summary.degraded)}`,
|
|
2329
|
+
`Errors: ${String(report.summary.errors)}`,
|
|
2330
|
+
"",
|
|
2331
|
+
"| Surface | Status | Decisions | Selected | Fallbacks | Degraded | Errors | Providers |",
|
|
2332
|
+
"| --- | --- | ---: | ---: | ---: | ---: | ---: | --- |",
|
|
2333
|
+
...report.surfaces.map((surface) => `| ${surface.surface} | ${surface.status} | ${String(surface.decisions)} | ${String(surface.selected)} | ${String(surface.fallbacks)} | ${String(surface.degraded)} | ${String(surface.errors)} | ${surface.providers.join(", ")} |`),
|
|
2334
|
+
"",
|
|
2335
|
+
...report.issues.map((issue) => `- ${issue.status}: ${issue.message}`)
|
|
2336
|
+
].join(`
|
|
2337
|
+
`);
|
|
2338
|
+
var renderVoiceProviderDecisionTraceHTML = (report, title = "Provider Decision Traces") => `<!doctype html>
|
|
2339
|
+
<html lang="en">
|
|
2340
|
+
<head>
|
|
2341
|
+
<meta charset="utf-8" />
|
|
2342
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
2343
|
+
<title>${escapeHtml7(title)}</title>
|
|
2344
|
+
<style>
|
|
2345
|
+
body{font-family:ui-sans-serif,system-ui,sans-serif;margin:0;background:#f8fafc;color:#0f172a}
|
|
2346
|
+
main{max-width:1100px;margin:0 auto;padding:32px}
|
|
2347
|
+
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px}
|
|
2348
|
+
.card,.surface{background:white;border:1px solid #e2e8f0;border-radius:16px;padding:16px;box-shadow:0 12px 30px rgba(15,23,42,.06)}
|
|
2349
|
+
.status{display:inline-flex;border-radius:999px;padding:4px 10px;font-weight:700;background:#dcfce7;color:#166534}
|
|
2350
|
+
.status.fail{background:#fee2e2;color:#991b1b}.status.warn{background:#fef3c7;color:#92400e}
|
|
2351
|
+
.surfaces{display:grid;gap:14px;margin-top:18px}.muted{color:#64748b}
|
|
2352
|
+
code{background:#e2e8f0;border-radius:8px;padding:2px 6px}
|
|
2353
|
+
</style>
|
|
2354
|
+
</head>
|
|
2355
|
+
<body>
|
|
2356
|
+
<main>
|
|
2357
|
+
<p class="status ${report.status}">${escapeHtml7(report.status)}</p>
|
|
2358
|
+
<h1>${escapeHtml7(title)}</h1>
|
|
2359
|
+
<p class="muted">Runtime proof for why providers were selected, skipped, failed, or recovered by fallback.</p>
|
|
2360
|
+
<section class="grid">
|
|
2361
|
+
<article class="card"><strong>${String(report.summary.decisions)}</strong><p>decisions</p></article>
|
|
2362
|
+
<article class="card"><strong>${String(report.summary.providers)}</strong><p>providers</p></article>
|
|
2363
|
+
<article class="card"><strong>${String(report.summary.fallbacks)}</strong><p>fallbacks</p></article>
|
|
2364
|
+
<article class="card"><strong>${String(report.summary.degraded)}</strong><p>degraded</p></article>
|
|
2365
|
+
<article class="card"><strong>${String(report.summary.errors)}</strong><p>errors</p></article>
|
|
2366
|
+
</section>
|
|
2367
|
+
<section class="surfaces">
|
|
2368
|
+
${report.surfaces.map((surface) => `<article class="surface">
|
|
2369
|
+
<header><strong>${escapeHtml7(surface.surface)}</strong> <span class="status ${surface.status}">${escapeHtml7(surface.status)}</span></header>
|
|
2370
|
+
<p>${String(surface.decisions)} decision(s), ${String(surface.fallbacks)} fallback(s), ${String(surface.degraded)} degraded decision(s), ${String(surface.errors)} error(s).</p>
|
|
2371
|
+
<p class="muted">Providers: ${escapeHtml7(surface.providers.join(", ") || "none")}</p>
|
|
2372
|
+
<p>${surface.reasons.map((reason) => `<code>${escapeHtml7(reason)}</code>`).join(" ")}</p>
|
|
2373
|
+
</article>`).join(`
|
|
2374
|
+
`)}
|
|
2375
|
+
</section>
|
|
2376
|
+
</main>
|
|
2377
|
+
</body>
|
|
2378
|
+
</html>`;
|
|
2379
|
+
var createVoiceProviderDecisionTraceRoutes = (options) => {
|
|
2380
|
+
const path = options.path ?? "/api/voice/provider-decisions";
|
|
2381
|
+
const htmlPath = options.htmlPath ?? "/voice/provider-decisions";
|
|
2382
|
+
const markdownPath = options.markdownPath ?? "/voice/provider-decisions.md";
|
|
2383
|
+
const headers = options.headers ?? {};
|
|
2384
|
+
const title = options.title ?? "Provider Decision Traces";
|
|
2385
|
+
const report = () => buildVoiceProviderDecisionTraceReport(options);
|
|
2386
|
+
const app = new Elysia3({ name: options.name ?? "voice-provider-decisions" }).get(path, async () => new Response(JSON.stringify(await report(), null, 2), {
|
|
2387
|
+
headers: {
|
|
2388
|
+
"content-type": "application/json; charset=utf-8",
|
|
2389
|
+
...headers
|
|
2390
|
+
}
|
|
2391
|
+
}));
|
|
2392
|
+
if (htmlPath !== false) {
|
|
2393
|
+
app.get(htmlPath, async () => {
|
|
2394
|
+
const body = options.render ? await options.render(await report()) : renderVoiceProviderDecisionTraceHTML(await report(), title);
|
|
2395
|
+
return new Response(body, {
|
|
2396
|
+
headers: {
|
|
2397
|
+
"content-type": "text/html; charset=utf-8",
|
|
2398
|
+
...headers
|
|
2399
|
+
}
|
|
2400
|
+
});
|
|
2401
|
+
});
|
|
2402
|
+
}
|
|
2403
|
+
if (markdownPath !== false) {
|
|
2404
|
+
app.get(markdownPath, async () => new Response(renderVoiceProviderDecisionTraceMarkdown(await report()), {
|
|
2405
|
+
headers: {
|
|
2406
|
+
"content-type": "text/markdown; charset=utf-8",
|
|
2407
|
+
...headers
|
|
2408
|
+
}
|
|
2409
|
+
}));
|
|
2410
|
+
}
|
|
2411
|
+
return app;
|
|
2412
|
+
};
|
|
2413
|
+
|
|
2414
|
+
// src/trace.ts
|
|
2415
|
+
var createVoiceTraceEventId = (event) => [
|
|
2416
|
+
event.sessionId,
|
|
2417
|
+
event.turnId ?? "session",
|
|
2418
|
+
event.type,
|
|
2419
|
+
String(event.at ?? Date.now()),
|
|
2420
|
+
crypto.randomUUID()
|
|
2421
|
+
].map(encodeURIComponent).join(":");
|
|
2422
|
+
var createVoiceTraceEvent = (event) => ({
|
|
2423
|
+
...event,
|
|
2424
|
+
at: event.at,
|
|
2425
|
+
id: event.id ?? createVoiceTraceEventId({
|
|
2426
|
+
at: event.at,
|
|
2427
|
+
sessionId: event.sessionId,
|
|
2428
|
+
turnId: event.turnId,
|
|
2429
|
+
type: event.type
|
|
2430
|
+
})
|
|
2431
|
+
});
|
|
2432
|
+
var createVoiceTraceSinkDeliveryId = (events) => {
|
|
2433
|
+
const firstEvent = events[0];
|
|
2434
|
+
return [
|
|
2435
|
+
firstEvent?.sessionId ?? "trace",
|
|
2436
|
+
firstEvent?.traceId ?? "sink",
|
|
2437
|
+
String(firstEvent?.at ?? Date.now()),
|
|
2438
|
+
crypto.randomUUID()
|
|
2439
|
+
].map(encodeURIComponent).join(":");
|
|
2440
|
+
};
|
|
2441
|
+
var createVoiceTraceSinkDeliveryRecord = (input) => {
|
|
2442
|
+
const createdAt = input.createdAt ?? Date.now();
|
|
2443
|
+
return {
|
|
2444
|
+
createdAt,
|
|
2445
|
+
deliveredAt: input.deliveredAt,
|
|
2446
|
+
deliveryAttempts: input.deliveryAttempts,
|
|
2447
|
+
deliveryError: input.deliveryError,
|
|
2448
|
+
deliveryStatus: input.deliveryStatus ?? "pending",
|
|
2449
|
+
events: input.events,
|
|
2450
|
+
id: input.id ?? createVoiceTraceSinkDeliveryId(input.events),
|
|
2451
|
+
sinkDeliveries: input.sinkDeliveries,
|
|
2452
|
+
updatedAt: input.updatedAt ?? createdAt
|
|
2453
|
+
};
|
|
2454
|
+
};
|
|
2455
|
+
var matchesTraceFilter = (event, filter) => {
|
|
2456
|
+
if (filter.sessionId !== undefined && event.sessionId !== filter.sessionId) {
|
|
2457
|
+
return false;
|
|
2458
|
+
}
|
|
2459
|
+
if (filter.turnId !== undefined && event.turnId !== filter.turnId) {
|
|
2460
|
+
return false;
|
|
2461
|
+
}
|
|
2462
|
+
if (filter.scenarioId !== undefined && event.scenarioId !== filter.scenarioId) {
|
|
2463
|
+
return false;
|
|
2464
|
+
}
|
|
2465
|
+
if (filter.traceId !== undefined && event.traceId !== filter.traceId) {
|
|
2466
|
+
return false;
|
|
2467
|
+
}
|
|
2468
|
+
if (filter.type !== undefined) {
|
|
2469
|
+
const types = Array.isArray(filter.type) ? filter.type : [filter.type];
|
|
2470
|
+
if (!types.includes(event.type)) {
|
|
2471
|
+
return false;
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
return true;
|
|
2475
|
+
};
|
|
2476
|
+
var filterVoiceTraceEvents = (events, filter = {}) => {
|
|
2477
|
+
const sorted = events.filter((event) => matchesTraceFilter(event, filter)).sort((left, right) => left.at - right.at || left.id.localeCompare(right.id));
|
|
2478
|
+
return typeof filter.limit === "number" && filter.limit >= 0 ? sorted.slice(0, filter.limit) : sorted;
|
|
2479
|
+
};
|
|
2480
|
+
var isPruneTimeMatch = (event, options) => {
|
|
2481
|
+
if (typeof options.before === "number" && event.at >= options.before) {
|
|
2482
|
+
return false;
|
|
2483
|
+
}
|
|
2484
|
+
if (typeof options.beforeOrAt === "number" && event.at > options.beforeOrAt) {
|
|
2485
|
+
return false;
|
|
2486
|
+
}
|
|
2487
|
+
return true;
|
|
2488
|
+
};
|
|
2489
|
+
var selectVoiceTraceEventsForPrune = (events, options = {}) => {
|
|
2490
|
+
let candidates = filterVoiceTraceEvents(events, options.filter).filter((event) => isPruneTimeMatch(event, options));
|
|
2491
|
+
if (typeof options.keepNewest === "number" && options.keepNewest >= 0) {
|
|
2492
|
+
const newestIds = new Set([...candidates].sort((left, right) => right.at - left.at || right.id.localeCompare(left.id)).slice(0, options.keepNewest).map((event) => event.id));
|
|
2493
|
+
candidates = candidates.filter((event) => !newestIds.has(event.id));
|
|
2494
|
+
}
|
|
2495
|
+
return typeof options.limit === "number" && options.limit >= 0 ? candidates.slice(0, options.limit) : candidates;
|
|
2496
|
+
};
|
|
2497
|
+
var pruneVoiceTraceEvents = async (options) => {
|
|
2498
|
+
const events = await options.store.list(options.filter);
|
|
2499
|
+
const deleted = selectVoiceTraceEventsForPrune(events, options);
|
|
2500
|
+
if (!options.dryRun) {
|
|
2501
|
+
await Promise.all(deleted.map((event) => options.store.remove(event.id)));
|
|
2502
|
+
}
|
|
2503
|
+
return {
|
|
2504
|
+
deleted,
|
|
2505
|
+
deletedCount: deleted.length,
|
|
2506
|
+
dryRun: Boolean(options.dryRun),
|
|
2507
|
+
scannedCount: events.length
|
|
2508
|
+
};
|
|
2509
|
+
};
|
|
2510
|
+
var sleep = async (delayMs) => {
|
|
2511
|
+
if (delayMs <= 0) {
|
|
2512
|
+
return;
|
|
2513
|
+
}
|
|
2514
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
2515
|
+
};
|
|
2516
|
+
var toHex = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
2517
|
+
var signVoiceTraceSinkBody = async (input) => {
|
|
2518
|
+
const encoder = new TextEncoder;
|
|
2519
|
+
const key = await crypto.subtle.importKey("raw", encoder.encode(input.secret), {
|
|
2520
|
+
hash: "SHA-256",
|
|
2521
|
+
name: "HMAC"
|
|
2522
|
+
}, false, ["sign"]);
|
|
2523
|
+
const payload = encoder.encode(`${input.timestamp}.${input.body}`);
|
|
2524
|
+
const signature = await crypto.subtle.sign("HMAC", key, payload);
|
|
2525
|
+
return `sha256=${toHex(new Uint8Array(signature))}`;
|
|
2526
|
+
};
|
|
2527
|
+
var createVoiceTraceSinkDeliveryError = (input) => {
|
|
2528
|
+
if (input.response) {
|
|
2529
|
+
const statusText = input.response.statusText?.trim();
|
|
2530
|
+
return `Attempt ${input.attempt} failed with trace sink response ${input.response.status}${statusText ? ` ${statusText}` : ""}.`;
|
|
2531
|
+
}
|
|
2532
|
+
if (input.error instanceof Error) {
|
|
2533
|
+
return `Attempt ${input.attempt} failed: ${input.error.message}`;
|
|
2534
|
+
}
|
|
2535
|
+
return `Attempt ${input.attempt} failed: ${String(input.error)}`;
|
|
2536
|
+
};
|
|
2537
|
+
var normalizeVoiceTraceS3KeyPrefix = (prefix) => prefix?.trim().replace(/^\/+|\/+$/g, "") ?? "voice/trace-deliveries";
|
|
2538
|
+
var createVoiceTraceS3ObjectKey = (prefix, events) => {
|
|
2539
|
+
const firstEvent = events[0];
|
|
2540
|
+
const safeSessionId = encodeURIComponent(firstEvent?.sessionId ?? "trace");
|
|
2541
|
+
const safeEventId = encodeURIComponent(firstEvent?.id ?? crypto.randomUUID());
|
|
2542
|
+
return `${prefix}/${safeSessionId}/${Date.now()}-${safeEventId}.json`;
|
|
2543
|
+
};
|
|
2544
|
+
var resolveVoiceS3DeliveredTo = (options, key) => {
|
|
2545
|
+
const bucket = options.bucket;
|
|
2546
|
+
return bucket ? `s3://${bucket}/${key}` : `s3://${key}`;
|
|
2547
|
+
};
|
|
2548
|
+
var aggregateVoiceTraceSinkDeliveryStatus = (deliveries) => {
|
|
2549
|
+
const statuses = Object.values(deliveries).map((delivery) => delivery.status);
|
|
2550
|
+
if (statuses.length === 0 || statuses.every((status) => status === "skipped")) {
|
|
2551
|
+
return "skipped";
|
|
2552
|
+
}
|
|
2553
|
+
if (statuses.some((status) => status === "failed")) {
|
|
2554
|
+
return "failed";
|
|
2555
|
+
}
|
|
2556
|
+
return "delivered";
|
|
2557
|
+
};
|
|
2558
|
+
var createVoiceTraceHTTPSink = (options) => ({
|
|
2559
|
+
deliver: async ({ events }) => {
|
|
2560
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
2561
|
+
if (typeof fetchImpl !== "function") {
|
|
2562
|
+
return {
|
|
2563
|
+
attempts: 0,
|
|
2564
|
+
deliveredTo: options.url,
|
|
2565
|
+
error: "Trace sink delivery failed: fetch is not available in this runtime.",
|
|
2566
|
+
eventCount: events.length,
|
|
2567
|
+
status: "failed"
|
|
2568
|
+
};
|
|
2569
|
+
}
|
|
2570
|
+
const maxRetries = Math.max(0, options.retries ?? 0);
|
|
2571
|
+
const backoffMs = Math.max(0, options.backoffMs ?? 250);
|
|
2572
|
+
const timeoutMs = Math.max(0, options.timeoutMs ?? 1e4);
|
|
2573
|
+
const payload = options.body ? await options.body({ events }) : {
|
|
2574
|
+
eventCount: events.length,
|
|
2575
|
+
events,
|
|
2576
|
+
source: "absolutejs-voice"
|
|
2577
|
+
};
|
|
2578
|
+
const body = JSON.stringify(payload);
|
|
2579
|
+
let lastError = "Trace sink delivery failed.";
|
|
2580
|
+
for (let attempt = 1;attempt <= maxRetries + 1; attempt += 1) {
|
|
2581
|
+
let controller;
|
|
2582
|
+
let timeout;
|
|
2583
|
+
try {
|
|
2584
|
+
const headers = {
|
|
2585
|
+
"content-type": "application/json",
|
|
2586
|
+
...options.headers
|
|
2587
|
+
};
|
|
2588
|
+
if (options.signingSecret) {
|
|
2589
|
+
const timestamp = String(Date.now());
|
|
2590
|
+
headers["x-absolutejs-timestamp"] = timestamp;
|
|
2591
|
+
headers["x-absolutejs-signature"] = await signVoiceTraceSinkBody({
|
|
2592
|
+
body,
|
|
2593
|
+
secret: options.signingSecret,
|
|
2594
|
+
timestamp
|
|
2595
|
+
});
|
|
2596
|
+
}
|
|
2597
|
+
controller = timeoutMs > 0 ? new AbortController : undefined;
|
|
2598
|
+
if (controller && timeoutMs > 0) {
|
|
2599
|
+
timeout = setTimeout(() => controller?.abort(), timeoutMs);
|
|
2600
|
+
}
|
|
2601
|
+
const response = await fetchImpl(options.url, {
|
|
2602
|
+
body,
|
|
2603
|
+
headers,
|
|
2604
|
+
method: options.method ?? "POST",
|
|
2605
|
+
signal: controller?.signal
|
|
2606
|
+
});
|
|
2607
|
+
if (response.ok) {
|
|
2608
|
+
let responseBody;
|
|
2609
|
+
try {
|
|
2610
|
+
responseBody = await response.clone().json();
|
|
2611
|
+
} catch {
|
|
2612
|
+
responseBody = undefined;
|
|
2613
|
+
}
|
|
2614
|
+
return {
|
|
2615
|
+
attempts: attempt,
|
|
2616
|
+
deliveredAt: Date.now(),
|
|
2617
|
+
deliveredTo: options.url,
|
|
2618
|
+
eventCount: events.length,
|
|
2619
|
+
responseBody,
|
|
2620
|
+
status: "delivered"
|
|
2621
|
+
};
|
|
2622
|
+
}
|
|
2623
|
+
lastError = createVoiceTraceSinkDeliveryError({
|
|
2624
|
+
attempt,
|
|
2625
|
+
response
|
|
2626
|
+
});
|
|
2627
|
+
} catch (error) {
|
|
2628
|
+
lastError = createVoiceTraceSinkDeliveryError({
|
|
2629
|
+
attempt,
|
|
2630
|
+
error
|
|
2631
|
+
});
|
|
2632
|
+
} finally {
|
|
2633
|
+
if (timeout) {
|
|
2634
|
+
clearTimeout(timeout);
|
|
2635
|
+
}
|
|
2636
|
+
}
|
|
2637
|
+
if (attempt <= maxRetries) {
|
|
2638
|
+
await sleep(backoffMs * attempt);
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
return {
|
|
2642
|
+
attempts: maxRetries + 1,
|
|
2643
|
+
deliveredTo: options.url,
|
|
2644
|
+
error: lastError,
|
|
2645
|
+
eventCount: events.length,
|
|
2646
|
+
status: "failed"
|
|
2647
|
+
};
|
|
2648
|
+
},
|
|
2649
|
+
eventTypes: options.eventTypes,
|
|
2650
|
+
id: options.id,
|
|
2651
|
+
kind: options.kind ?? "http"
|
|
2652
|
+
});
|
|
2653
|
+
var createVoiceTraceS3Sink = (options) => {
|
|
2654
|
+
const client = options.client ?? new Bun.S3Client(options);
|
|
2655
|
+
const keyPrefix = normalizeVoiceTraceS3KeyPrefix(options.keyPrefix);
|
|
2656
|
+
return {
|
|
2657
|
+
deliver: async ({ events }) => {
|
|
2658
|
+
const key = createVoiceTraceS3ObjectKey(keyPrefix, events);
|
|
2659
|
+
const payload = options.body ? await options.body({ events, key }) : {
|
|
2660
|
+
eventCount: events.length,
|
|
2661
|
+
events,
|
|
2662
|
+
key,
|
|
2663
|
+
source: "absolutejs-voice"
|
|
2664
|
+
};
|
|
2665
|
+
try {
|
|
2666
|
+
const file = client.file(key, options);
|
|
2667
|
+
await file.write(JSON.stringify(payload), {
|
|
2668
|
+
type: options.contentType ?? "application/json"
|
|
2669
|
+
});
|
|
2670
|
+
return {
|
|
2671
|
+
attempts: 1,
|
|
2672
|
+
deliveredAt: Date.now(),
|
|
2673
|
+
deliveredTo: resolveVoiceS3DeliveredTo(options, key),
|
|
2674
|
+
eventCount: events.length,
|
|
2675
|
+
responseBody: { key },
|
|
2676
|
+
status: "delivered"
|
|
2677
|
+
};
|
|
2678
|
+
} catch (error) {
|
|
2679
|
+
return {
|
|
2680
|
+
attempts: 1,
|
|
2681
|
+
deliveredTo: resolveVoiceS3DeliveredTo(options, key),
|
|
2682
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2683
|
+
eventCount: events.length,
|
|
2684
|
+
status: "failed"
|
|
2685
|
+
};
|
|
2686
|
+
}
|
|
2687
|
+
},
|
|
2688
|
+
eventTypes: options.eventTypes,
|
|
2689
|
+
id: options.id,
|
|
2690
|
+
kind: options.kind ?? "s3"
|
|
2691
|
+
};
|
|
2692
|
+
};
|
|
2693
|
+
var deliverVoiceTraceEventsToSinks = async (input) => {
|
|
2694
|
+
const events = input.redact ? redactVoiceTraceEvents(input.events, input.redact) : input.events;
|
|
2695
|
+
const sinkDeliveries = {};
|
|
2696
|
+
for (const sink of input.sinks) {
|
|
2697
|
+
const sinkEvents = sink.eventTypes?.length ? events.filter((event) => sink.eventTypes?.includes(event.type)) : events;
|
|
2698
|
+
if (sinkEvents.length === 0) {
|
|
2699
|
+
sinkDeliveries[sink.id] = {
|
|
2700
|
+
attempts: 0,
|
|
2701
|
+
eventCount: 0,
|
|
2702
|
+
status: "skipped"
|
|
2703
|
+
};
|
|
2704
|
+
continue;
|
|
2705
|
+
}
|
|
2706
|
+
try {
|
|
2707
|
+
sinkDeliveries[sink.id] = await sink.deliver({
|
|
2708
|
+
events: sinkEvents
|
|
2709
|
+
});
|
|
2710
|
+
} catch (error) {
|
|
2711
|
+
sinkDeliveries[sink.id] = {
|
|
2712
|
+
attempts: 1,
|
|
2713
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2714
|
+
eventCount: sinkEvents.length,
|
|
2715
|
+
status: "failed"
|
|
2716
|
+
};
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
return {
|
|
2720
|
+
deliveredAt: Date.now(),
|
|
2721
|
+
eventCount: events.length,
|
|
2722
|
+
sinkDeliveries,
|
|
2723
|
+
status: aggregateVoiceTraceSinkDeliveryStatus(sinkDeliveries)
|
|
2724
|
+
};
|
|
2725
|
+
};
|
|
2726
|
+
var createVoiceTraceSinkStore = (options) => {
|
|
2727
|
+
const deliver = async (event) => {
|
|
2728
|
+
const result = await deliverVoiceTraceEventsToSinks({
|
|
2729
|
+
events: [event],
|
|
2730
|
+
redact: options.redact,
|
|
2731
|
+
sinks: options.sinks
|
|
2732
|
+
});
|
|
2733
|
+
await options.onDelivery?.(result);
|
|
2734
|
+
};
|
|
2735
|
+
return {
|
|
2736
|
+
append: async (event) => {
|
|
2737
|
+
const stored = await options.store.append(event);
|
|
2738
|
+
if (options.deliveryQueue) {
|
|
2739
|
+
const delivery2 = createVoiceTraceSinkDeliveryRecord({
|
|
2740
|
+
events: [stored]
|
|
2741
|
+
});
|
|
2742
|
+
await options.deliveryQueue.set(delivery2.id, delivery2);
|
|
2743
|
+
return stored;
|
|
2744
|
+
}
|
|
2745
|
+
const delivery = deliver(stored);
|
|
2746
|
+
if (options.awaitDelivery) {
|
|
2747
|
+
await delivery;
|
|
2748
|
+
} else {
|
|
2749
|
+
delivery.catch((error) => {
|
|
2750
|
+
options.onError?.(error);
|
|
2751
|
+
});
|
|
2752
|
+
}
|
|
2753
|
+
return stored;
|
|
2754
|
+
},
|
|
2755
|
+
get: (id) => options.store.get(id),
|
|
2756
|
+
list: (filter) => options.store.list(filter),
|
|
2757
|
+
remove: (id) => options.store.remove(id)
|
|
2758
|
+
};
|
|
2759
|
+
};
|
|
2760
|
+
var normalizeVoiceProfileTraceTaggerProfile = (profile) => typeof profile === "string" ? { id: profile } : profile?.id ? profile : undefined;
|
|
2761
|
+
var createVoiceProfileTraceTagger = (options) => {
|
|
2762
|
+
const profiles = new Map((options.profiles ?? []).map((profile) => [profile.id, profile]));
|
|
2763
|
+
const defaultProfile = normalizeVoiceProfileTraceTaggerProfile(options.defaultProfile);
|
|
2764
|
+
const resolveProfile = async (event) => {
|
|
2765
|
+
const resolved = normalizeVoiceProfileTraceTaggerProfile(await options.resolveProfile?.(event));
|
|
2766
|
+
const profile = resolved ?? defaultProfile;
|
|
2767
|
+
return profile ? profiles.get(profile.id) ?? profile : undefined;
|
|
2768
|
+
};
|
|
2769
|
+
return {
|
|
2770
|
+
append: async (event) => {
|
|
2771
|
+
const profile = await resolveProfile(event);
|
|
2772
|
+
if (!profile) {
|
|
2773
|
+
return options.store.append(event);
|
|
2774
|
+
}
|
|
2775
|
+
const metadata = {
|
|
2776
|
+
...event.metadata ?? {},
|
|
2777
|
+
benchmarkProfileId: profile.id,
|
|
2778
|
+
profileDescription: event.metadata?.profileDescription ?? profile.description,
|
|
2779
|
+
profileId: profile.id,
|
|
2780
|
+
profileLabel: event.metadata?.profileLabel ?? profile.label
|
|
2781
|
+
};
|
|
2782
|
+
const payload = event.payload && typeof event.payload === "object" ? {
|
|
2783
|
+
...event.payload,
|
|
2784
|
+
benchmarkProfileId: event.payload.benchmarkProfileId ?? profile.id,
|
|
2785
|
+
profileDescription: event.payload.profileDescription ?? profile.description,
|
|
2786
|
+
profileId: event.payload.profileId ?? profile.id,
|
|
2787
|
+
profileLabel: event.payload.profileLabel ?? profile.label
|
|
2788
|
+
} : event.payload;
|
|
2789
|
+
return options.store.append({
|
|
2790
|
+
...event,
|
|
2791
|
+
metadata,
|
|
2792
|
+
payload
|
|
2793
|
+
});
|
|
2794
|
+
},
|
|
2795
|
+
get: (id) => options.store.get(id),
|
|
2796
|
+
list: (filter) => options.store.list(filter),
|
|
2797
|
+
remove: (id) => options.store.remove(id)
|
|
2798
|
+
};
|
|
2799
|
+
};
|
|
2800
|
+
var createVoiceMemoryTraceSinkDeliveryStore = () => {
|
|
2801
|
+
const deliveries = new Map;
|
|
2802
|
+
return {
|
|
2803
|
+
get: async (id) => deliveries.get(id),
|
|
2804
|
+
list: async () => [...deliveries.values()].sort((left, right) => left.createdAt - right.createdAt || left.id.localeCompare(right.id)),
|
|
2805
|
+
remove: async (id) => {
|
|
2806
|
+
deliveries.delete(id);
|
|
2807
|
+
},
|
|
2808
|
+
set: async (id, delivery) => {
|
|
2809
|
+
deliveries.set(id, delivery);
|
|
2810
|
+
}
|
|
2811
|
+
};
|
|
2812
|
+
};
|
|
2813
|
+
var createVoiceMemoryTraceEventStore = () => {
|
|
2814
|
+
const events = new Map;
|
|
2815
|
+
const append = async (event) => {
|
|
2816
|
+
const stored = createVoiceTraceEvent(event);
|
|
2817
|
+
events.set(stored.id, stored);
|
|
2818
|
+
return stored;
|
|
2819
|
+
};
|
|
2820
|
+
const get = async (id) => events.get(id);
|
|
2821
|
+
const list = async (filter) => filterVoiceTraceEvents([...events.values()], filter);
|
|
2822
|
+
const remove = async (id) => {
|
|
2823
|
+
events.delete(id);
|
|
2824
|
+
};
|
|
2825
|
+
return { append, get, list, remove };
|
|
2826
|
+
};
|
|
2827
|
+
var exportVoiceTrace = async (input) => {
|
|
2828
|
+
const events = await input.store.list(input.filter);
|
|
2829
|
+
return {
|
|
2830
|
+
exportedAt: Date.now(),
|
|
2831
|
+
events: input.redact ? redactVoiceTraceEvents(events, input.redact) : events,
|
|
2832
|
+
filter: input.filter,
|
|
2833
|
+
redacted: Boolean(input.redact)
|
|
2834
|
+
};
|
|
2835
|
+
};
|
|
2836
|
+
var toNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
2837
|
+
var escapeHtml8 = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
2838
|
+
var formatTraceValue = (value) => {
|
|
2839
|
+
if (value === undefined || value === null) {
|
|
2840
|
+
return "";
|
|
2841
|
+
}
|
|
2842
|
+
if (typeof value === "string") {
|
|
2843
|
+
return value;
|
|
2844
|
+
}
|
|
2845
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
2846
|
+
return String(value);
|
|
2847
|
+
}
|
|
2848
|
+
try {
|
|
2849
|
+
return JSON.stringify(value);
|
|
2850
|
+
} catch {
|
|
2851
|
+
return String(value);
|
|
2852
|
+
}
|
|
2853
|
+
};
|
|
2854
|
+
var DEFAULT_REDACTION_KEYS = [
|
|
2855
|
+
"apiKey",
|
|
2856
|
+
"authorization",
|
|
2857
|
+
"creditCard",
|
|
2858
|
+
"email",
|
|
2859
|
+
"externalId",
|
|
2860
|
+
"password",
|
|
2861
|
+
"phone",
|
|
2862
|
+
"secret",
|
|
2863
|
+
"ssn",
|
|
2864
|
+
"token"
|
|
2865
|
+
];
|
|
2866
|
+
var DEFAULT_REDACTION_TEXT_KEYS = [
|
|
2867
|
+
"assistantText",
|
|
2868
|
+
"content",
|
|
2869
|
+
"error",
|
|
2870
|
+
"reason",
|
|
2871
|
+
"summary",
|
|
2872
|
+
"text"
|
|
2873
|
+
];
|
|
2874
|
+
var EMAIL_PATTERN = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi;
|
|
2875
|
+
var PHONE_PATTERN = /(?<!\d)(?:\+?1[\s.-]?)?(?:\(?\d{3}\)?[\s.-]?)\d{3}[\s.-]?\d{4}(?!\d)/g;
|
|
2876
|
+
var normalizeRedactionKey = (key) => key.trim().toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
2877
|
+
var resolveVoiceTraceRedactionOptions = (options = {}) => ({
|
|
2878
|
+
keys: typeof options === "boolean" ? DEFAULT_REDACTION_KEYS : options.keys ?? DEFAULT_REDACTION_KEYS,
|
|
2879
|
+
redactEmails: typeof options === "boolean" ? true : options.redactEmails ?? true,
|
|
2880
|
+
redactPhoneNumbers: typeof options === "boolean" ? true : options.redactPhoneNumbers ?? true,
|
|
2881
|
+
redactText: typeof options === "boolean" ? true : options.redactText ?? true,
|
|
2882
|
+
replacement: typeof options === "boolean" ? "[redacted]" : options.replacement ?? "[redacted]",
|
|
2883
|
+
textKeys: typeof options === "boolean" ? DEFAULT_REDACTION_TEXT_KEYS : options.textKeys ?? DEFAULT_REDACTION_TEXT_KEYS
|
|
2884
|
+
});
|
|
2885
|
+
var resolveReplacement = (input) => typeof input.options.replacement === "function" ? input.options.replacement({
|
|
2886
|
+
key: input.key,
|
|
2887
|
+
path: input.path,
|
|
2888
|
+
value: input.value
|
|
2889
|
+
}) : input.options.replacement;
|
|
2890
|
+
var redactVoiceTraceText = (value, options = {}, input = {}) => {
|
|
2891
|
+
const resolved = resolveVoiceTraceRedactionOptions(options);
|
|
2892
|
+
let redacted = value;
|
|
2893
|
+
const replacement = resolveReplacement({
|
|
2894
|
+
key: input.key,
|
|
2895
|
+
options: resolved,
|
|
2896
|
+
path: input.path ?? [],
|
|
2897
|
+
value
|
|
2898
|
+
});
|
|
2899
|
+
if (resolved.redactEmails) {
|
|
2900
|
+
redacted = redacted.replace(EMAIL_PATTERN, replacement);
|
|
2901
|
+
}
|
|
2902
|
+
if (resolved.redactPhoneNumbers) {
|
|
2903
|
+
redacted = redacted.replace(PHONE_PATTERN, replacement);
|
|
2904
|
+
}
|
|
2905
|
+
return redacted;
|
|
2906
|
+
};
|
|
2907
|
+
var redactTraceValue = (value, options, path) => {
|
|
2908
|
+
const key = path.at(-1);
|
|
2909
|
+
const normalizedKey = key ? normalizeRedactionKey(key) : undefined;
|
|
2910
|
+
const sensitiveKeys = new Set(options.keys.map(normalizeRedactionKey));
|
|
2911
|
+
const textKeys = new Set(options.textKeys.map(normalizeRedactionKey));
|
|
2912
|
+
if (normalizedKey && sensitiveKeys.has(normalizedKey) && (value === null || ["boolean", "number", "string", "undefined"].includes(typeof value))) {
|
|
2913
|
+
return resolveReplacement({
|
|
2914
|
+
key,
|
|
2915
|
+
options,
|
|
2916
|
+
path,
|
|
2917
|
+
value: String(value ?? "")
|
|
2918
|
+
});
|
|
2919
|
+
}
|
|
2920
|
+
if (typeof value === "string") {
|
|
2921
|
+
const shouldRedactText = options.redactText && (!normalizedKey || textKeys.has(normalizedKey) || path.length === 0);
|
|
2922
|
+
return shouldRedactText ? redactVoiceTraceText(value, options, {
|
|
2923
|
+
key,
|
|
2924
|
+
path
|
|
2925
|
+
}) : value;
|
|
2926
|
+
}
|
|
2927
|
+
if (Array.isArray(value)) {
|
|
2928
|
+
return value.map((item, index) => redactTraceValue(item, options, [...path, String(index)]));
|
|
2929
|
+
}
|
|
2930
|
+
if (typeof value === "object" && value) {
|
|
2931
|
+
return Object.fromEntries(Object.entries(value).map(([entryKey, entryValue]) => [
|
|
2932
|
+
entryKey,
|
|
2933
|
+
redactTraceValue(entryValue, options, [...path, entryKey])
|
|
2934
|
+
]));
|
|
2935
|
+
}
|
|
2936
|
+
return value;
|
|
2937
|
+
};
|
|
2938
|
+
var redactVoiceTraceEvent = (event, options = {}) => {
|
|
2939
|
+
const resolved = resolveVoiceTraceRedactionOptions(options);
|
|
2940
|
+
return {
|
|
2941
|
+
...event,
|
|
2942
|
+
metadata: redactTraceValue(event.metadata, resolved, ["metadata"]),
|
|
2943
|
+
payload: redactTraceValue(event.payload, resolved, ["payload"])
|
|
2944
|
+
};
|
|
2945
|
+
};
|
|
2946
|
+
var redactVoiceTraceEvents = (events, options = {}) => events.map((event) => redactVoiceTraceEvent(event, options));
|
|
2947
|
+
var summarizeVoiceTrace = (events) => {
|
|
2948
|
+
const sorted = filterVoiceTraceEvents(events);
|
|
2949
|
+
const firstEvent = sorted[0];
|
|
2950
|
+
const lastEvent = sorted.at(-1);
|
|
2951
|
+
const lifecycleEvents = sorted.filter((event) => event.type === "call.lifecycle");
|
|
2952
|
+
const startEvent = lifecycleEvents.find((event) => event.payload.type === "start");
|
|
2953
|
+
const endEvent = lifecycleEvents.toReversed().find((event) => event.payload.type === "end");
|
|
2954
|
+
const costEvents = sorted.filter((event) => event.type === "turn.cost");
|
|
2955
|
+
const toolEvents = sorted.filter((event) => event.type === "agent.tool");
|
|
2956
|
+
const startedAt = startEvent?.at ?? firstEvent?.at;
|
|
2957
|
+
const endedAt = endEvent?.at ?? lastEvent?.at;
|
|
2958
|
+
const failed = sorted.some((event) => event.type === "session.error") || endEvent?.payload.disposition === "failed";
|
|
2959
|
+
return {
|
|
2960
|
+
assistantReplyCount: sorted.filter((event) => event.type === "turn.assistant").length,
|
|
2961
|
+
callDurationMs: startedAt !== undefined && endedAt !== undefined ? Math.max(0, endedAt - startedAt) : undefined,
|
|
2962
|
+
cost: {
|
|
2963
|
+
estimatedRelativeCostUnits: costEvents.reduce((total, event) => total + toNumber(event.payload.estimatedRelativeCostUnits), 0),
|
|
2964
|
+
totalBillableAudioMs: costEvents.reduce((total, event) => total + toNumber(event.payload.totalBillableAudioMs), 0)
|
|
2965
|
+
},
|
|
2966
|
+
endedAt,
|
|
2967
|
+
errorCount: sorted.filter((event) => event.type === "session.error").length,
|
|
2968
|
+
eventCount: sorted.length,
|
|
2969
|
+
failed,
|
|
2970
|
+
handoffCount: sorted.filter((event) => event.type === "agent.handoff").length,
|
|
2971
|
+
modelCallCount: sorted.filter((event) => event.type === "agent.model").length,
|
|
2972
|
+
sessionId: firstEvent?.sessionId,
|
|
2973
|
+
startedAt,
|
|
2974
|
+
toolCallCount: toolEvents.length,
|
|
2975
|
+
toolErrorCount: toolEvents.filter((event) => event.payload.status === "error").length,
|
|
2976
|
+
traceId: firstEvent?.traceId,
|
|
2977
|
+
transcriptCount: sorted.filter((event) => event.type === "turn.transcript").length,
|
|
2978
|
+
turnCount: sorted.filter((event) => event.type === "turn.committed").length
|
|
2979
|
+
};
|
|
2980
|
+
};
|
|
2981
|
+
var evaluateVoiceTrace = (events, options = {}) => {
|
|
2982
|
+
const summary = summarizeVoiceTrace(events);
|
|
2983
|
+
const issues = [];
|
|
2984
|
+
const maxHandoffs = options.maxHandoffs ?? 3;
|
|
2985
|
+
const maxToolErrors = options.maxToolErrors ?? 0;
|
|
2986
|
+
const maxModelCallsPerTurn = options.maxModelCallsPerTurn ?? 6;
|
|
2987
|
+
const turnCountForRatio = Math.max(1, summary.turnCount);
|
|
2988
|
+
if (options.requireCompletedCall !== false && !summary.endedAt) {
|
|
2989
|
+
issues.push({
|
|
2990
|
+
code: "call-not-ended",
|
|
2991
|
+
message: "Trace does not include a call end lifecycle event.",
|
|
2992
|
+
severity: "warning"
|
|
2993
|
+
});
|
|
2994
|
+
}
|
|
2995
|
+
if (summary.failed) {
|
|
2996
|
+
issues.push({
|
|
2997
|
+
code: "session-error",
|
|
2998
|
+
message: "Trace contains a session error or failed call disposition.",
|
|
2999
|
+
severity: "error"
|
|
3000
|
+
});
|
|
3001
|
+
}
|
|
3002
|
+
if (options.requireTranscript !== false && summary.transcriptCount === 0) {
|
|
3003
|
+
issues.push({
|
|
3004
|
+
code: "missing-transcript",
|
|
3005
|
+
message: "Trace does not include any transcript events.",
|
|
3006
|
+
severity: "error"
|
|
3007
|
+
});
|
|
3008
|
+
}
|
|
3009
|
+
if (options.requireTurn !== false && summary.turnCount === 0) {
|
|
3010
|
+
issues.push({
|
|
3011
|
+
code: "missing-turn",
|
|
3012
|
+
message: "Trace does not include any committed turns.",
|
|
3013
|
+
severity: "error"
|
|
3014
|
+
});
|
|
3015
|
+
}
|
|
3016
|
+
if (options.requireAssistantReply !== false && summary.turnCount > 0 && summary.assistantReplyCount === 0) {
|
|
3017
|
+
issues.push({
|
|
3018
|
+
code: "missing-assistant-reply",
|
|
3019
|
+
message: "Trace has committed turns but no assistant replies.",
|
|
3020
|
+
severity: "warning"
|
|
3021
|
+
});
|
|
3022
|
+
}
|
|
3023
|
+
if (summary.toolErrorCount > maxToolErrors) {
|
|
3024
|
+
issues.push({
|
|
3025
|
+
code: "tool-errors",
|
|
3026
|
+
message: `Trace has ${summary.toolErrorCount} tool error(s), above the allowed ${maxToolErrors}.`,
|
|
3027
|
+
severity: "error"
|
|
3028
|
+
});
|
|
3029
|
+
}
|
|
3030
|
+
if (summary.handoffCount > maxHandoffs) {
|
|
3031
|
+
issues.push({
|
|
3032
|
+
code: "too-many-handoffs",
|
|
3033
|
+
message: `Trace has ${summary.handoffCount} handoff(s), above the allowed ${maxHandoffs}.`,
|
|
3034
|
+
severity: "warning"
|
|
3035
|
+
});
|
|
3036
|
+
}
|
|
3037
|
+
if (summary.modelCallCount / turnCountForRatio > maxModelCallsPerTurn) {
|
|
3038
|
+
issues.push({
|
|
3039
|
+
code: "too-many-model-calls",
|
|
3040
|
+
message: `Trace averages more than ${maxModelCallsPerTurn} model calls per committed turn.`,
|
|
3041
|
+
severity: "warning"
|
|
3042
|
+
});
|
|
3043
|
+
}
|
|
3044
|
+
return {
|
|
3045
|
+
issues,
|
|
3046
|
+
pass: !issues.some((issue) => issue.severity === "error"),
|
|
3047
|
+
summary
|
|
3048
|
+
};
|
|
3049
|
+
};
|
|
3050
|
+
var renderTraceEventMarkdown = (event, startedAt) => {
|
|
3051
|
+
const offset = startedAt === undefined ? `${event.at}` : `+${Math.max(0, event.at - startedAt)}ms`;
|
|
3052
|
+
const label = `- ${offset} [${event.type}]`;
|
|
3053
|
+
switch (event.type) {
|
|
3054
|
+
case "turn.transcript":
|
|
3055
|
+
return `${label} ${event.payload.isFinal ? "final" : "partial"} "${formatTraceValue(event.payload.text)}"`;
|
|
3056
|
+
case "turn.committed":
|
|
3057
|
+
return `${label} committed "${formatTraceValue(event.payload.text)}"`;
|
|
3058
|
+
case "turn.assistant":
|
|
3059
|
+
return event.payload.text ? `${label} assistant "${formatTraceValue(event.payload.text)}"` : `${label} ${formatTraceValue(event.payload.status)}`;
|
|
3060
|
+
case "agent.tool":
|
|
3061
|
+
return `${label} ${formatTraceValue(event.payload.toolName)} ${formatTraceValue(event.payload.status)}`;
|
|
3062
|
+
case "agent.context":
|
|
3063
|
+
return `${label} ${formatTraceValue(event.payload.fromAgentId)} -> ${formatTraceValue(event.payload.targetAgentId)} ${formatTraceValue(event.payload.status)}`;
|
|
3064
|
+
case "agent.handoff":
|
|
3065
|
+
return `${label} ${formatTraceValue(event.payload.fromAgentId)} -> ${formatTraceValue(event.payload.targetAgentId)}`;
|
|
3066
|
+
case "session.error":
|
|
3067
|
+
return `${label} ${formatTraceValue(event.payload.error)}`;
|
|
3068
|
+
case "call.lifecycle":
|
|
3069
|
+
return `${label} ${formatTraceValue(event.payload.type)} ${formatTraceValue(event.payload.disposition)}`.trim();
|
|
3070
|
+
default:
|
|
3071
|
+
return `${label} ${formatTraceValue(event.payload)}`;
|
|
3072
|
+
}
|
|
3073
|
+
};
|
|
3074
|
+
var renderVoiceTraceMarkdown = (events, options = {}) => {
|
|
3075
|
+
const sorted = filterVoiceTraceEvents(options.redact ? redactVoiceTraceEvents(events, options.redact) : events);
|
|
3076
|
+
const summary = summarizeVoiceTrace(sorted);
|
|
3077
|
+
const evaluation = evaluateVoiceTrace(sorted, options.evaluation);
|
|
3078
|
+
const lines = [
|
|
3079
|
+
`# ${options.title ?? `Voice Trace ${summary.sessionId ?? ""}`.trim()}`,
|
|
3080
|
+
"",
|
|
3081
|
+
`Pass: ${evaluation.pass ? "yes" : "no"}`,
|
|
3082
|
+
`Session: ${summary.sessionId ?? "unknown"}`,
|
|
3083
|
+
`Events: ${summary.eventCount}`,
|
|
3084
|
+
`Turns: ${summary.turnCount}`,
|
|
3085
|
+
`Transcripts: ${summary.transcriptCount}`,
|
|
3086
|
+
`Assistant replies: ${summary.assistantReplyCount}`,
|
|
3087
|
+
`Model calls: ${summary.modelCallCount}`,
|
|
3088
|
+
`Tool calls: ${summary.toolCallCount}`,
|
|
3089
|
+
`Handoffs: ${summary.handoffCount}`,
|
|
3090
|
+
`Errors: ${summary.errorCount}`,
|
|
3091
|
+
`Estimated cost units: ${summary.cost.estimatedRelativeCostUnits}`,
|
|
3092
|
+
""
|
|
3093
|
+
];
|
|
3094
|
+
if (evaluation.issues.length > 0) {
|
|
3095
|
+
lines.push("## Issues", "");
|
|
3096
|
+
for (const issue of evaluation.issues) {
|
|
3097
|
+
lines.push(`- [${issue.severity}] ${issue.code}: ${issue.message}`);
|
|
3098
|
+
}
|
|
3099
|
+
lines.push("");
|
|
3100
|
+
}
|
|
3101
|
+
lines.push("## Timeline", "");
|
|
3102
|
+
for (const event of sorted) {
|
|
3103
|
+
lines.push(renderTraceEventMarkdown(event, summary.startedAt));
|
|
3104
|
+
}
|
|
3105
|
+
return lines.join(`
|
|
3106
|
+
`);
|
|
3107
|
+
};
|
|
3108
|
+
var renderVoiceTraceHTML = (events, options = {}) => {
|
|
3109
|
+
const markdown = renderVoiceTraceMarkdown(events, options);
|
|
3110
|
+
const renderEvents = options.redact ? redactVoiceTraceEvents(events, options.redact) : events;
|
|
3111
|
+
const summary = summarizeVoiceTrace(renderEvents);
|
|
3112
|
+
const evaluation = evaluateVoiceTrace(renderEvents, options.evaluation);
|
|
3113
|
+
const eventRows = filterVoiceTraceEvents(renderEvents).map((event) => {
|
|
3114
|
+
const offset = summary.startedAt === undefined ? event.at : Math.max(0, event.at - summary.startedAt);
|
|
3115
|
+
return [
|
|
3116
|
+
"<tr>",
|
|
3117
|
+
`<td>${escapeHtml8(String(offset))}</td>`,
|
|
3118
|
+
`<td>${escapeHtml8(event.type)}</td>`,
|
|
3119
|
+
`<td>${escapeHtml8(event.turnId ?? "")}</td>`,
|
|
3120
|
+
`<td><code>${escapeHtml8(JSON.stringify(event.payload))}</code></td>`,
|
|
3121
|
+
"</tr>"
|
|
3122
|
+
].join("");
|
|
3123
|
+
}).join(`
|
|
3124
|
+
`);
|
|
3125
|
+
return [
|
|
3126
|
+
"<!doctype html>",
|
|
3127
|
+
'<html lang="en">',
|
|
3128
|
+
"<head>",
|
|
3129
|
+
'<meta charset="utf-8" />',
|
|
3130
|
+
'<meta name="viewport" content="width=device-width, initial-scale=1" />',
|
|
3131
|
+
`<title>${escapeHtml8(options.title ?? "Voice Trace")}</title>`,
|
|
3132
|
+
"<style>",
|
|
3133
|
+
"body{font-family:ui-sans-serif,system-ui,sans-serif;margin:2rem;line-height:1.45;background:#f8f7f2;color:#181713}",
|
|
3134
|
+
"main{max-width:1100px;margin:auto}",
|
|
3135
|
+
".summary{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:.75rem;margin:1rem 0}",
|
|
3136
|
+
".card{background:white;border:1px solid #ded9cc;border-radius:12px;padding:1rem}",
|
|
3137
|
+
".pass{color:#126b3a}.fail{color:#9d2222}",
|
|
3138
|
+
"table{border-collapse:collapse;width:100%;background:white;border:1px solid #ded9cc}",
|
|
3139
|
+
"th,td{border-bottom:1px solid #eee8dc;padding:.65rem;text-align:left;vertical-align:top}",
|
|
3140
|
+
"code{white-space:pre-wrap;word-break:break-word}",
|
|
3141
|
+
"pre{background:#181713;color:#f8f7f2;padding:1rem;border-radius:12px;overflow:auto}",
|
|
3142
|
+
"</style>",
|
|
3143
|
+
"</head>",
|
|
3144
|
+
"<body><main>",
|
|
3145
|
+
`<h1>${escapeHtml8(options.title ?? `Voice Trace ${summary.sessionId ?? ""}`.trim())}</h1>`,
|
|
3146
|
+
`<p class="${evaluation.pass ? "pass" : "fail"}">QA: ${evaluation.pass ? "pass" : "fail"}</p>`,
|
|
3147
|
+
'<section class="summary">',
|
|
3148
|
+
`<div class="card"><strong>Events</strong><br>${summary.eventCount}</div>`,
|
|
3149
|
+
`<div class="card"><strong>Turns</strong><br>${summary.turnCount}</div>`,
|
|
3150
|
+
`<div class="card"><strong>Transcripts</strong><br>${summary.transcriptCount}</div>`,
|
|
3151
|
+
`<div class="card"><strong>Tool errors</strong><br>${summary.toolErrorCount}</div>`,
|
|
3152
|
+
`<div class="card"><strong>Cost units</strong><br>${summary.cost.estimatedRelativeCostUnits}</div>`,
|
|
3153
|
+
"</section>",
|
|
3154
|
+
"<h2>Timeline</h2>",
|
|
3155
|
+
"<table><thead><tr><th>Offset ms</th><th>Type</th><th>Turn</th><th>Payload</th></tr></thead><tbody>",
|
|
3156
|
+
eventRows,
|
|
3157
|
+
"</tbody></table>",
|
|
3158
|
+
"<h2>Markdown Export</h2>",
|
|
3159
|
+
`<pre>${escapeHtml8(markdown)}</pre>`,
|
|
3160
|
+
"</main></body></html>"
|
|
3161
|
+
].join(`
|
|
3162
|
+
`);
|
|
3163
|
+
};
|
|
3164
|
+
var buildVoiceTraceReplay = (events, options = {}) => ({
|
|
3165
|
+
evaluation: evaluateVoiceTrace(options.redact ? redactVoiceTraceEvents(events, options.redact) : events, options.evaluation),
|
|
3166
|
+
html: renderVoiceTraceHTML(events, options),
|
|
3167
|
+
markdown: renderVoiceTraceMarkdown(events, options),
|
|
3168
|
+
summary: summarizeVoiceTrace(options.redact ? redactVoiceTraceEvents(events, options.redact) : events)
|
|
3169
|
+
});
|
|
3170
|
+
|
|
3171
|
+
// src/proofTrends.ts
|
|
1419
3172
|
var DEFAULT_VOICE_PROOF_TRENDS_MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
|
1420
3173
|
var DEFAULT_VOICE_PROOF_TREND_PROFILE_DEFINITIONS = [
|
|
1421
3174
|
{
|
|
@@ -1748,6 +3501,9 @@ var buildVoiceRealCallProfileEvidenceFromTraceEvents = (events, options = {}) =>
|
|
|
1748
3501
|
const providerP95Ms = maxNumber(providers.map((provider) => provider.p95Ms));
|
|
1749
3502
|
const runtimeChannel = summarizeRuntimeChannelTraceEvidence(sessionEvents);
|
|
1750
3503
|
const surfaces = readRealCallProfileTraceSurfaces(sessionEvents);
|
|
3504
|
+
if (providers.length === 0 && runtimeChannel === undefined && liveLatencies.length === 0 && surfaces.length === 0) {
|
|
3505
|
+
return;
|
|
3506
|
+
}
|
|
1751
3507
|
return {
|
|
1752
3508
|
generatedAt: new Date(Math.max(...sessionEvents.map((event) => event.at))).toISOString(),
|
|
1753
3509
|
liveP95Ms: percentile(liveLatencies, 95),
|
|
@@ -2212,6 +3968,98 @@ var uniqueVoiceRealCallProfileRecoveryLoopActions = (actions) => {
|
|
|
2212
3968
|
return true;
|
|
2213
3969
|
});
|
|
2214
3970
|
};
|
|
3971
|
+
var normalizeRealCallProfileRecoveryProvider = (role, provider) => {
|
|
3972
|
+
if (!provider) {
|
|
3973
|
+
return;
|
|
3974
|
+
}
|
|
3975
|
+
if (typeof provider === "string") {
|
|
3976
|
+
return {
|
|
3977
|
+
provider,
|
|
3978
|
+
selectedProvider: provider,
|
|
3979
|
+
status: "selected"
|
|
3980
|
+
};
|
|
3981
|
+
}
|
|
3982
|
+
return {
|
|
3983
|
+
...provider,
|
|
3984
|
+
selectedProvider: provider.selectedProvider ?? provider.provider,
|
|
3985
|
+
status: provider.status ?? "selected"
|
|
3986
|
+
};
|
|
3987
|
+
};
|
|
3988
|
+
var profileRealCallRecoveryEvent = (event, profileId, metadata = {}) => ({
|
|
3989
|
+
...event,
|
|
3990
|
+
metadata: {
|
|
3991
|
+
...metadata,
|
|
3992
|
+
...event.metadata ?? {},
|
|
3993
|
+
benchmarkProfileId: event.metadata?.benchmarkProfileId ?? profileId,
|
|
3994
|
+
profileId: event.metadata?.profileId ?? profileId
|
|
3995
|
+
},
|
|
3996
|
+
payload: {
|
|
3997
|
+
...event.payload ?? {},
|
|
3998
|
+
benchmarkProfileId: event.payload.benchmarkProfileId ?? profileId,
|
|
3999
|
+
profileId: event.payload.profileId ?? profileId
|
|
4000
|
+
}
|
|
4001
|
+
});
|
|
4002
|
+
var appendVoiceRealCallProfileRecoveryEvidence = async (options) => {
|
|
4003
|
+
const at = options.at ?? Date.now();
|
|
4004
|
+
const scenarioId = options.scenarioId ?? "real-call-profile-recovery";
|
|
4005
|
+
const sessionId = options.sessionId ?? `real-call-profile-recovery-${options.profileId}-${new Date(at).toISOString()}`;
|
|
4006
|
+
const browser = options.browser ?? {};
|
|
4007
|
+
const live = options.live ?? {};
|
|
4008
|
+
const providerEvents = [
|
|
4009
|
+
["llm", 320, "live-call"],
|
|
4010
|
+
["stt", 82, "live-stt"],
|
|
4011
|
+
["tts", 45, "live-tts"]
|
|
4012
|
+
].flatMap(([role, defaultElapsedMs, defaultSurface], index) => {
|
|
4013
|
+
const provider = normalizeRealCallProfileRecoveryProvider(role, options.providers?.[role]);
|
|
4014
|
+
if (!provider) {
|
|
4015
|
+
return [];
|
|
4016
|
+
}
|
|
4017
|
+
return [
|
|
4018
|
+
profileRealCallRecoveryEvent(createVoiceProviderDecisionTraceEvent({
|
|
4019
|
+
at: at + index,
|
|
4020
|
+
elapsedMs: provider.elapsedMs ?? defaultElapsedMs,
|
|
4021
|
+
kind: role,
|
|
4022
|
+
provider: provider.provider,
|
|
4023
|
+
reason: provider.reason ?? `Real-call profile recovery selected ${provider.provider} for ${role.toUpperCase()} evidence.`,
|
|
4024
|
+
scenarioId,
|
|
4025
|
+
selectedProvider: provider.selectedProvider,
|
|
4026
|
+
sessionId,
|
|
4027
|
+
status: provider.status,
|
|
4028
|
+
surface: provider.surface ?? defaultSurface
|
|
4029
|
+
}), options.profileId, options.metadata)
|
|
4030
|
+
];
|
|
4031
|
+
});
|
|
4032
|
+
const browserEvents = browser === false ? [] : [
|
|
4033
|
+
profileRealCallRecoveryEvent(createVoiceTraceEvent({
|
|
4034
|
+
at: at + 3,
|
|
4035
|
+
payload: {
|
|
4036
|
+
firstAudioLatencyMs: browser.firstAudioLatencyMs ?? 420,
|
|
4037
|
+
messageCount: browser.messageCount,
|
|
4038
|
+
openSockets: browser.openSockets,
|
|
4039
|
+
receivedBytes: browser.receivedBytes,
|
|
4040
|
+
sentBytes: browser.sentBytes,
|
|
4041
|
+
status: browser.status ?? "pass"
|
|
4042
|
+
},
|
|
4043
|
+
scenarioId,
|
|
4044
|
+
sessionId,
|
|
4045
|
+
type: "client.browser_media"
|
|
4046
|
+
}), options.profileId, options.metadata)
|
|
4047
|
+
];
|
|
4048
|
+
const liveEvents = live === false ? [] : [
|
|
4049
|
+
profileRealCallRecoveryEvent(createVoiceTraceEvent({
|
|
4050
|
+
at: at + 4,
|
|
4051
|
+
payload: {
|
|
4052
|
+
latencyMs: live.latencyMs ?? 420,
|
|
4053
|
+
status: live.status ?? "pass"
|
|
4054
|
+
},
|
|
4055
|
+
scenarioId,
|
|
4056
|
+
sessionId,
|
|
4057
|
+
type: "client.live_latency"
|
|
4058
|
+
}), options.profileId, options.metadata)
|
|
4059
|
+
];
|
|
4060
|
+
const events = await Promise.all([...providerEvents, ...browserEvents, ...liveEvents].map((event) => options.store.append(event)));
|
|
4061
|
+
return { events, sessionId };
|
|
4062
|
+
};
|
|
2215
4063
|
var runVoiceRealCallProfileRecoveryLoop = async (options) => {
|
|
2216
4064
|
const baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
2217
4065
|
const requestTimeoutMs = options.requestTimeoutMs ?? 5000;
|
|
@@ -3040,7 +4888,7 @@ var buildVoiceProofTrendRecommendationReport = (report, options = {}) => {
|
|
|
3040
4888
|
}
|
|
3041
4889
|
};
|
|
3042
4890
|
};
|
|
3043
|
-
var
|
|
4891
|
+
var escapeHtml9 = (value) => String(value).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
3044
4892
|
var escapeMarkdown = (value) => value.replaceAll("|", "\\|");
|
|
3045
4893
|
var renderVoiceProofTrendRecommendationMarkdown = (report, title = "Voice Provider Runtime Recommendations") => [
|
|
3046
4894
|
`# ${title}`,
|
|
@@ -3073,11 +4921,11 @@ var renderVoiceProofTrendRecommendationMarkdown = (report, title = "Voice Provid
|
|
|
3073
4921
|
].join(`
|
|
3074
4922
|
`);
|
|
3075
4923
|
var renderVoiceProofTrendRecommendationHTML = (report, title = "Voice Provider Runtime Recommendations") => {
|
|
3076
|
-
const cards = report.recommendations.map((recommendation) => `<article class="${
|
|
3077
|
-
const issues = report.issues.length === 0 ? "<li>None</li>" : report.issues.map((issue) => `<li>${
|
|
3078
|
-
const providerRows = report.providers.length === 0 ? "<li>No provider-specific samples were present.</li>" : report.providers.map((provider) => `<li><strong>#${String(provider.rank)} ${
|
|
3079
|
-
const profileRows = report.profiles.length === 0 ? "<li>No benchmark profiles were present.</li>" : report.profiles.map((profile) => `<li><strong>${
|
|
3080
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width,initial-scale=1" /><title>${
|
|
4924
|
+
const cards = report.recommendations.map((recommendation) => `<article class="${escapeHtml9(recommendation.status)}"><p class="eyebrow">${escapeHtml9(recommendation.surface)} \xB7 ${escapeHtml9(recommendation.status)}</p><h2>${escapeHtml9(recommendation.recommendation)}</h2><p>${escapeHtml9(recommendation.nextMove)}</p><pre>${escapeHtml9(JSON.stringify(recommendation.evidence, null, 2))}</pre></article>`).join("");
|
|
4925
|
+
const issues = report.issues.length === 0 ? "<li>None</li>" : report.issues.map((issue) => `<li>${escapeHtml9(issue)}</li>`).join("");
|
|
4926
|
+
const providerRows = report.providers.length === 0 ? "<li>No provider-specific samples were present.</li>" : report.providers.map((provider) => `<li><strong>#${String(provider.rank)} ${escapeHtml9(provider.label ?? provider.id)}</strong><span>${escapeHtml9(provider.role ?? "provider")} \xB7 ${escapeHtml9(provider.status)} \xB7 p95 ${escapeHtml9(provider.p95Ms ?? "n/a")}ms \xB7 ${escapeHtml9(provider.samples ?? "n/a")} sample(s)</span><small>${escapeHtml9(provider.nextMove)}</small></li>`).join("");
|
|
4927
|
+
const profileRows = report.profiles.length === 0 ? "<li>No benchmark profiles were present.</li>" : report.profiles.map((profile) => `<li><strong>${escapeHtml9(profile.label ?? profile.id)}</strong><span>${escapeHtml9(profile.status)} \xB7 ${escapeHtml9(formatProviderMix(profile.bestProviders))}</span><small>${escapeHtml9(profile.nextMove)}</small></li>`).join("");
|
|
4928
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width,initial-scale=1" /><title>${escapeHtml9(title)}</title><style>body{background:#101418;color:#f7f3e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1120px;padding:32px}.hero,article{background:#17201d;border:1px solid #2e3d36;border-radius:24px;margin-bottom:16px;padding:22px}.hero{background:linear-gradient(135deg,rgba(20,184,166,.18),rgba(245,158,11,.12))}.eyebrow{color:#5eead4;font-weight:900;letter-spacing:.1em;text-transform:uppercase}h1{font-size:clamp(2.2rem,6vw,4.7rem);letter-spacing:-.06em;line-height:.92;margin:.2rem 0 1rem}.summary{display:flex;flex-wrap:wrap;gap:10px}.pill{border:1px solid #42534a;border-radius:999px;padding:8px 12px}.pass{border-color:rgba(34,197,94,.55)}.warn{border-color:rgba(245,158,11,.7)}.fail{border-color:rgba(239,68,68,.75)}pre{background:#0b1110;border-radius:14px;overflow:auto;padding:12px}a{color:#5eead4}li{margin:.45rem 0}li span,li small{display:block;color:#c9d3ca}</style></head><body><main><section class="hero"><p class="eyebrow">Sustained proof recommendations</p><h1>${escapeHtml9(title)}</h1><p>Generated ${escapeHtml9(report.generatedAt)} from ${escapeHtml9(report.source)}.</p><div class="summary"><span class="pill">Status ${escapeHtml9(report.status)}</span><span class="pill">Provider ${report.summary.keepCurrentProviderPath ? "keep" : "change"}</span><span class="pill">Best mix ${escapeHtml9(formatProviderMix(report.bestProviders))}</span><span class="pill">Profiles ${String(report.profiles.length)}</span><span class="pill">Runtime ${report.summary.keepCurrentRuntimeChannel ? "keep" : "tune"}</span><span class="pill">${String(report.summary.recommendedActions)} action(s)</span></div></section>${cards}<section class="hero"><h2>Benchmark Profiles</h2><ul>${profileRows}</ul></section><section class="hero"><h2>Provider Comparison</h2><ul>${providerRows}</ul></section><section class="hero"><h2>Issues</h2><ul>${issues}</ul></section></main></body></html>`;
|
|
3081
4929
|
};
|
|
3082
4930
|
var renderVoiceRealCallProfileHistoryMarkdown = (report, title = "Voice Real-Call Profile History") => [
|
|
3083
4931
|
`# ${title}`,
|
|
@@ -3111,18 +4959,18 @@ var renderVoiceRealCallProfileHistoryMarkdown = (report, title = "Voice Real-Cal
|
|
|
3111
4959
|
].join(`
|
|
3112
4960
|
`);
|
|
3113
4961
|
var renderVoiceRealCallProfileHistoryHTML = (report, title = "Voice Real-Call Profile History") => {
|
|
3114
|
-
const profileRows = report.summary.profiles?.length ? report.summary.profiles.map((profile) => `<tr><td>${
|
|
3115
|
-
const defaultRows = report.defaults.profiles.length > 0 ? report.defaults.profiles.map((profile) => `<tr><td>${
|
|
3116
|
-
const recommendations = report.recommendations.recommendations.map((recommendation) => `<article class="${
|
|
3117
|
-
const issues = report.issues.length === 0 ? "<li>None</li>" : report.issues.map((issue) => `<li>${
|
|
3118
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width,initial-scale=1" /><title>${
|
|
4962
|
+
const profileRows = report.summary.profiles?.length ? report.summary.profiles.map((profile) => `<tr><td>${escapeHtml9(profile.label ?? profile.id)}</td><td>${escapeHtml9(profile.status ?? "unknown")}</td><td>${escapeHtml9(profile.maxLiveP95Ms ?? "n/a")}</td><td>${escapeHtml9(profile.maxProviderP95Ms ?? "n/a")}</td><td>${escapeHtml9(profile.maxTurnP95Ms ?? "n/a")}</td><td>${escapeHtml9(formatProviderMix(profile.providers ?? []))}</td></tr>`).join("") : '<tr><td colspan="6">No profiles present.</td></tr>';
|
|
4963
|
+
const defaultRows = report.defaults.profiles.length > 0 ? report.defaults.profiles.map((profile) => `<tr><td>${escapeHtml9(profile.label ?? profile.profileId)}</td><td>${escapeHtml9(profile.status)}</td><td>${escapeHtml9(Object.entries(profile.providerRoutes).map(([role, provider]) => `${role}: ${provider}`).join(", ") || "n/a")}</td><td>${escapeHtml9(profile.latencyBudgets.maxLiveP95Ms ?? "n/a")}</td><td>${escapeHtml9(profile.latencyBudgets.maxProviderP95Ms ?? "n/a")}</td><td>${escapeHtml9(profile.latencyBudgets.maxTurnP95Ms ?? "n/a")}</td></tr>`).join("") : '<tr><td colspan="6">No actionable defaults present.</td></tr>';
|
|
4964
|
+
const recommendations = report.recommendations.recommendations.map((recommendation) => `<article class="${escapeHtml9(recommendation.status)}"><p class="eyebrow">${escapeHtml9(recommendation.surface)} \xB7 ${escapeHtml9(recommendation.status)}</p><h2>${escapeHtml9(recommendation.recommendation)}</h2><p>${escapeHtml9(recommendation.nextMove)}</p></article>`).join("");
|
|
4965
|
+
const issues = report.issues.length === 0 ? "<li>None</li>" : report.issues.map((issue) => `<li>${escapeHtml9(issue)}</li>`).join("");
|
|
4966
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width,initial-scale=1" /><title>${escapeHtml9(title)}</title><style>body{background:#111510;color:#f6f0dd;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1120px;padding:32px}.hero,article,.card{background:#182117;border:1px solid #32412d;border-radius:24px;margin-bottom:16px;padding:22px}.hero{background:linear-gradient(135deg,rgba(132,204,22,.16),rgba(20,184,166,.12))}.eyebrow{color:#bef264;font-weight:900;letter-spacing:.1em;text-transform:uppercase}h1{font-size:clamp(2.2rem,6vw,4.7rem);letter-spacing:-.06em;line-height:.92;margin:.2rem 0 1rem}.summary{display:flex;flex-wrap:wrap;gap:10px}.pill{border:1px solid #52624b;border-radius:999px;padding:8px 12px}.pass{border-color:rgba(34,197,94,.55)}.warn{border-color:rgba(245,158,11,.7)}.fail{border-color:rgba(239,68,68,.75)}table{border-collapse:collapse;width:100%}td,th{border-bottom:1px solid #32412d;padding:10px;text-align:left}</style></head><body><main><section class="hero"><p class="eyebrow">Real-call benchmark history</p><h1>${escapeHtml9(title)}</h1><p>Generated ${escapeHtml9(report.generatedAt)} from ${escapeHtml9(report.source)}.</p><div class="summary"><span class="pill">Status ${escapeHtml9(report.status)}</span><span class="pill">Reports ${String(report.reports)}</span><span class="pill">Profiles ${String(report.summary.profileCount)}</span><span class="pill">Defaults ${String(report.defaults.summary.actionableProfiles)}/${String(report.defaults.summary.profileCount)}</span><span class="pill">Cycles ${String(report.summary.cycles ?? 0)}</span><span class="pill">Best mix ${escapeHtml9(formatProviderMix(report.recommendations.bestProviders))}</span></div></section><section class="card"><h2>Profiles</h2><table><thead><tr><th>Profile</th><th>Status</th><th>Live p95</th><th>Provider p95</th><th>Turn p95</th><th>Provider mix</th></tr></thead><tbody>${profileRows}</tbody></table></section><section class="card"><h2>Actionable Defaults</h2><table><thead><tr><th>Profile</th><th>Status</th><th>Provider routes</th><th>Live budget</th><th>Provider budget</th><th>Turn budget</th></tr></thead><tbody>${defaultRows}</tbody></table></section>${recommendations}<section class="card"><h2>Issues</h2><ul>${issues}</ul></section></main></body></html>`;
|
|
3119
4967
|
};
|
|
3120
4968
|
var createVoiceProofTrendRecommendationRoutes = (options) => {
|
|
3121
4969
|
const path = options.path ?? "/api/voice/proof-trend-recommendations";
|
|
3122
4970
|
const htmlPath = options.htmlPath === undefined ? "/voice/proof-trend-recommendations" : options.htmlPath;
|
|
3123
4971
|
const markdownPath = options.markdownPath === undefined ? "/voice/proof-trend-recommendations.md" : options.markdownPath;
|
|
3124
4972
|
const title = options.title ?? "Voice Provider Runtime Recommendations";
|
|
3125
|
-
const routes = new
|
|
4973
|
+
const routes = new Elysia4({
|
|
3126
4974
|
name: options.name ?? "absolutejs-voice-proof-trend-recommendations"
|
|
3127
4975
|
});
|
|
3128
4976
|
const loadReport = async () => {
|
|
@@ -3164,7 +5012,7 @@ var createVoiceRealCallProfileHistoryRoutes = (options = {}) => {
|
|
|
3164
5012
|
const htmlPath = options.htmlPath === undefined ? "/voice/real-call-profile-history" : options.htmlPath;
|
|
3165
5013
|
const markdownPath = options.markdownPath === undefined ? "/voice/real-call-profile-history.md" : options.markdownPath;
|
|
3166
5014
|
const title = options.title ?? "Voice Real-Call Profile History";
|
|
3167
|
-
const routes = new
|
|
5015
|
+
const routes = new Elysia4({
|
|
3168
5016
|
name: options.name ?? "absolutejs-voice-real-call-profile-history"
|
|
3169
5017
|
});
|
|
3170
5018
|
const loadReport = async () => {
|
|
@@ -3216,7 +5064,7 @@ var loadVoiceRealCallProfileHistoryRouteReport = async (options) => {
|
|
|
3216
5064
|
};
|
|
3217
5065
|
var createVoiceRealCallProfileRecoveryActionRoutes = (options = {}) => {
|
|
3218
5066
|
const path = options.path ?? "/api/voice/real-call-profile-history";
|
|
3219
|
-
const routes = new
|
|
5067
|
+
const routes = new Elysia4({
|
|
3220
5068
|
name: options.name ?? "absolutejs-voice-real-call-profile-recovery-actions"
|
|
3221
5069
|
});
|
|
3222
5070
|
const actionPath = (actionId) => `${path}${realCallProfileActionPaths[actionId]}`;
|
|
@@ -3391,7 +5239,7 @@ var createVoiceRealCallProfileRecoveryActionRoutes = (options = {}) => {
|
|
|
3391
5239
|
};
|
|
3392
5240
|
var createVoiceProofTrendRoutes = (options) => {
|
|
3393
5241
|
const path = options.path ?? "/api/voice/proof-trends";
|
|
3394
|
-
const routes = new
|
|
5242
|
+
const routes = new Elysia4({
|
|
3395
5243
|
name: options.name ?? "absolutejs-voice-proof-trends"
|
|
3396
5244
|
});
|
|
3397
5245
|
routes.get(path, async () => {
|
|
@@ -3510,7 +5358,7 @@ var DEFAULT_LINKS2 = [
|
|
|
3510
5358
|
{ href: "/voice/proof-trends", label: "Trend page" },
|
|
3511
5359
|
{ href: "/api/voice/proof-trends", label: "Trend JSON" }
|
|
3512
5360
|
];
|
|
3513
|
-
var
|
|
5361
|
+
var escapeHtml10 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
3514
5362
|
var formatMs = (value) => typeof value === "number" && Number.isFinite(value) ? `${Math.round(value)}ms` : "n/a";
|
|
3515
5363
|
var statusLabel = (report) => {
|
|
3516
5364
|
if (!report) {
|
|
@@ -3560,19 +5408,19 @@ var createVoiceProofTrendsViewModel = (snapshot, options = {}) => {
|
|
|
3560
5408
|
var renderVoiceProofTrendsHTML = (snapshot, options = {}) => {
|
|
3561
5409
|
const model = createVoiceProofTrendsViewModel(snapshot, options);
|
|
3562
5410
|
const metrics = model.metrics.length ? `<div class="absolute-voice-proof-trends__metrics">${model.metrics.map((metric) => `<article>
|
|
3563
|
-
<span>${
|
|
3564
|
-
<strong>${
|
|
3565
|
-
</article>`).join("")}</div>` : `<p class="absolute-voice-proof-trends__empty">${model.error ?
|
|
3566
|
-
const links = model.links.length ? `<p class="absolute-voice-proof-trends__links">${model.links.map((link) => `<a href="${
|
|
3567
|
-
return `<section class="absolute-voice-proof-trends absolute-voice-proof-trends--${
|
|
5411
|
+
<span>${escapeHtml10(metric.label)}</span>
|
|
5412
|
+
<strong>${escapeHtml10(metric.value)}</strong>
|
|
5413
|
+
</article>`).join("")}</div>` : `<p class="absolute-voice-proof-trends__empty">${model.error ? escapeHtml10(model.error) : "Run the sustained proof trends script to populate evidence."}</p>`;
|
|
5414
|
+
const links = model.links.length ? `<p class="absolute-voice-proof-trends__links">${model.links.map((link) => `<a href="${escapeHtml10(link.href)}">${escapeHtml10(link.label)}</a>`).join("")}</p>` : "";
|
|
5415
|
+
return `<section class="absolute-voice-proof-trends absolute-voice-proof-trends--${escapeHtml10(model.status)}">
|
|
3568
5416
|
<header class="absolute-voice-proof-trends__header">
|
|
3569
|
-
<span class="absolute-voice-proof-trends__eyebrow">${
|
|
3570
|
-
<strong class="absolute-voice-proof-trends__label">${
|
|
5417
|
+
<span class="absolute-voice-proof-trends__eyebrow">${escapeHtml10(model.title)}</span>
|
|
5418
|
+
<strong class="absolute-voice-proof-trends__label">${escapeHtml10(model.label)}</strong>
|
|
3571
5419
|
</header>
|
|
3572
|
-
<p class="absolute-voice-proof-trends__description">${
|
|
5420
|
+
<p class="absolute-voice-proof-trends__description">${escapeHtml10(model.description)}</p>
|
|
3573
5421
|
${metrics}
|
|
3574
5422
|
${links}
|
|
3575
|
-
${model.error ? `<p class="absolute-voice-proof-trends__error">${
|
|
5423
|
+
${model.error ? `<p class="absolute-voice-proof-trends__error">${escapeHtml10(model.error)}</p>` : ""}
|
|
3576
5424
|
</section>`;
|
|
3577
5425
|
};
|
|
3578
5426
|
var getVoiceProofTrendsCSS = () => `.absolute-voice-proof-trends{border:1px solid #99f6e4;border-radius:20px;background:#f0fdfa;color:#0f172a;padding:18px;box-shadow:0 18px 40px rgba(13,148,136,.12);font-family:inherit}.absolute-voice-proof-trends--warning,.absolute-voice-proof-trends--error{border-color:#f2a7a7;background:#fff7f4}.absolute-voice-proof-trends__header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-proof-trends__eyebrow{color:#0f766e;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-proof-trends__label{font-size:24px;line-height:1}.absolute-voice-proof-trends__description,.absolute-voice-proof-trends__empty{color:#475569}.absolute-voice-proof-trends__metrics{display:grid;gap:10px;grid-template-columns:repeat(auto-fit,minmax(130px,1fr));margin-top:14px}.absolute-voice-proof-trends__metrics article{background:#fff;border:1px solid #ccfbf1;border-radius:16px;padding:12px}.absolute-voice-proof-trends__metrics span{color:#64748b;display:block;font-size:12px;font-weight:800;text-transform:uppercase}.absolute-voice-proof-trends__metrics strong{display:block;font-size:20px;margin-top:4px}.absolute-voice-proof-trends__links{display:flex;flex-wrap:wrap;gap:8px;margin:14px 0 0}.absolute-voice-proof-trends__links a{border:1px solid #99f6e4;border-radius:999px;color:#0f766e;font-weight:800;padding:6px 10px;text-decoration:none}.absolute-voice-proof-trends__error{color:#9f1239;font-weight:700}`;
|
|
@@ -3780,7 +5628,7 @@ var DEFAULT_LINKS3 = [
|
|
|
3780
5628
|
{ href: "/production-readiness", label: "Readiness page" },
|
|
3781
5629
|
{ href: "/voice/slo-readiness-thresholds", label: "Gate thresholds" }
|
|
3782
5630
|
];
|
|
3783
|
-
var
|
|
5631
|
+
var escapeHtml11 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
3784
5632
|
var formatExplanationValue = (value, unit) => {
|
|
3785
5633
|
if (value === undefined || value === null) {
|
|
3786
5634
|
return "n/a";
|
|
@@ -3821,23 +5669,23 @@ var createVoiceReadinessFailuresViewModel = (snapshot, options = {}) => {
|
|
|
3821
5669
|
};
|
|
3822
5670
|
var renderVoiceReadinessFailuresHTML = (snapshot, options = {}) => {
|
|
3823
5671
|
const model = createVoiceReadinessFailuresViewModel(snapshot, options);
|
|
3824
|
-
const failures = model.failures.length ? `<div class="absolute-voice-readiness-failures__items">${model.failures.map((failure) => `<article class="absolute-voice-readiness-failures__item absolute-voice-readiness-failures__item--${
|
|
3825
|
-
<span>${
|
|
3826
|
-
<strong>${
|
|
3827
|
-
<p>Observed ${
|
|
3828
|
-
<p>${
|
|
3829
|
-
<p class="absolute-voice-readiness-failures__links">${failure.evidenceHref ? `<a href="${
|
|
3830
|
-
</article>`).join("")}</div>` : `<p class="absolute-voice-readiness-failures__empty">${model.error ?
|
|
3831
|
-
const links = model.links.length ? `<p class="absolute-voice-readiness-failures__links">${model.links.map((link) => `<a href="${
|
|
3832
|
-
return `<section class="absolute-voice-readiness-failures absolute-voice-readiness-failures--${
|
|
5672
|
+
const failures = model.failures.length ? `<div class="absolute-voice-readiness-failures__items">${model.failures.map((failure) => `<article class="absolute-voice-readiness-failures__item absolute-voice-readiness-failures__item--${escapeHtml11(failure.status)}">
|
|
5673
|
+
<span>${escapeHtml11(failure.status.toUpperCase())}</span>
|
|
5674
|
+
<strong>${escapeHtml11(failure.label)}</strong>
|
|
5675
|
+
<p>Observed ${escapeHtml11(failure.observed)} against ${escapeHtml11(failure.thresholdLabel)} ${escapeHtml11(failure.threshold)}.</p>
|
|
5676
|
+
<p>${escapeHtml11(failure.remediation)}</p>
|
|
5677
|
+
<p class="absolute-voice-readiness-failures__links">${failure.evidenceHref ? `<a href="${escapeHtml11(failure.evidenceHref)}">Evidence</a>` : ""}${failure.sourceHref ? `<a href="${escapeHtml11(failure.sourceHref)}">Threshold source</a>` : ""}</p>
|
|
5678
|
+
</article>`).join("")}</div>` : `<p class="absolute-voice-readiness-failures__empty">${model.error ? escapeHtml11(model.error) : "No calibrated readiness gate explanations are open."}</p>`;
|
|
5679
|
+
const links = model.links.length ? `<p class="absolute-voice-readiness-failures__links">${model.links.map((link) => `<a href="${escapeHtml11(link.href)}">${escapeHtml11(link.label)}</a>`).join("")}</p>` : "";
|
|
5680
|
+
return `<section class="absolute-voice-readiness-failures absolute-voice-readiness-failures--${escapeHtml11(model.status)}">
|
|
3833
5681
|
<header class="absolute-voice-readiness-failures__header">
|
|
3834
|
-
<span class="absolute-voice-readiness-failures__eyebrow">${
|
|
3835
|
-
<strong class="absolute-voice-readiness-failures__label">${
|
|
5682
|
+
<span class="absolute-voice-readiness-failures__eyebrow">${escapeHtml11(model.title)}</span>
|
|
5683
|
+
<strong class="absolute-voice-readiness-failures__label">${escapeHtml11(model.label)}</strong>
|
|
3836
5684
|
</header>
|
|
3837
|
-
<p class="absolute-voice-readiness-failures__description">${
|
|
5685
|
+
<p class="absolute-voice-readiness-failures__description">${escapeHtml11(model.description)}</p>
|
|
3838
5686
|
${failures}
|
|
3839
5687
|
${links}
|
|
3840
|
-
${model.error ? `<p class="absolute-voice-readiness-failures__error">${
|
|
5688
|
+
${model.error ? `<p class="absolute-voice-readiness-failures__error">${escapeHtml11(model.error)}</p>` : ""}
|
|
3841
5689
|
</section>`;
|
|
3842
5690
|
};
|
|
3843
5691
|
var getVoiceReadinessFailuresCSS = () => `.absolute-voice-readiness-failures{border:1px solid #fed7aa;border-radius:20px;background:#fff7ed;color:#1c1917;padding:18px;box-shadow:0 18px 40px rgba(234,88,12,.12);font-family:inherit}.absolute-voice-readiness-failures--ready{border-color:#86efac;background:#f0fdf4}.absolute-voice-readiness-failures--error{border-color:#fda4af;background:#fff1f2}.absolute-voice-readiness-failures__header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-readiness-failures__eyebrow{color:#9a3412;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-readiness-failures__label{font-size:24px;line-height:1}.absolute-voice-readiness-failures__description,.absolute-voice-readiness-failures__empty{color:#57534e}.absolute-voice-readiness-failures__items{display:grid;gap:10px;margin-top:14px}.absolute-voice-readiness-failures__item{background:white;border:1px solid #fed7aa;border-radius:16px;padding:12px}.absolute-voice-readiness-failures__item--fail{border-color:#fb7185}.absolute-voice-readiness-failures__item span{color:#9a3412;display:block;font-size:12px;font-weight:900;text-transform:uppercase}.absolute-voice-readiness-failures__item strong{display:block;font-size:18px;margin-top:4px}.absolute-voice-readiness-failures__item p{margin:.45rem 0 0}.absolute-voice-readiness-failures__links{display:flex;flex-wrap:wrap;gap:8px;margin:14px 0 0}.absolute-voice-readiness-failures__links a{border:1px solid #fdba74;border-radius:999px;color:#9a3412;font-weight:800;padding:6px 10px;text-decoration:none}.absolute-voice-readiness-failures__error{color:#9f1239;font-weight:700}`;
|
|
@@ -4054,7 +5902,7 @@ var createVoiceProviderSimulationControlsStore = (options) => {
|
|
|
4054
5902
|
};
|
|
4055
5903
|
|
|
4056
5904
|
// src/client/providerSimulationControlsWidget.ts
|
|
4057
|
-
var
|
|
5905
|
+
var escapeHtml12 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
4058
5906
|
var formatKind = (kind) => (kind ?? "stt").toUpperCase();
|
|
4059
5907
|
var createVoiceProviderSimulationControlsViewModel = (snapshot, options) => {
|
|
4060
5908
|
const configuredProviders = options.providers.filter((provider) => provider.configured !== false);
|
|
@@ -4074,18 +5922,18 @@ var createVoiceProviderSimulationControlsViewModel = (snapshot, options) => {
|
|
|
4074
5922
|
};
|
|
4075
5923
|
var renderVoiceProviderSimulationControlsHTML = (snapshot, options) => {
|
|
4076
5924
|
const model = createVoiceProviderSimulationControlsViewModel(snapshot, options);
|
|
4077
|
-
const failureButtons = model.failureProviders.map((provider) => `<button type="button" data-voice-provider-fail="${
|
|
4078
|
-
const recoveryButtons = model.providers.map((provider) => `<button type="button" data-voice-provider-recover="${
|
|
5925
|
+
const failureButtons = model.failureProviders.map((provider) => `<button type="button" data-voice-provider-fail="${escapeHtml12(provider.provider)}"${!model.canSimulateFailure || snapshot.isRunning ? " disabled" : ""}>Simulate ${escapeHtml12(provider.provider)} ${escapeHtml12(formatKind(options.kind))} failure</button>`).join("");
|
|
5926
|
+
const recoveryButtons = model.providers.map((provider) => `<button type="button" data-voice-provider-recover="${escapeHtml12(provider.provider)}"${snapshot.isRunning ? " disabled" : ""}>Mark ${escapeHtml12(provider.provider)} recovered</button>`).join("");
|
|
4079
5927
|
return `<section class="absolute-voice-provider-simulation absolute-voice-provider-simulation--${snapshot.error ? "error" : snapshot.isRunning ? "running" : "ready"}">
|
|
4080
5928
|
<header class="absolute-voice-provider-simulation__header">
|
|
4081
|
-
<span class="absolute-voice-provider-simulation__eyebrow">${
|
|
4082
|
-
<strong class="absolute-voice-provider-simulation__label">${
|
|
5929
|
+
<span class="absolute-voice-provider-simulation__eyebrow">${escapeHtml12(model.title)}</span>
|
|
5930
|
+
<strong class="absolute-voice-provider-simulation__label">${escapeHtml12(model.label)}</strong>
|
|
4083
5931
|
</header>
|
|
4084
|
-
<p class="absolute-voice-provider-simulation__description">${
|
|
4085
|
-
${model.canSimulateFailure ? "" : `<p class="absolute-voice-provider-simulation__empty">${
|
|
5932
|
+
<p class="absolute-voice-provider-simulation__description">${escapeHtml12(model.description)}</p>
|
|
5933
|
+
${model.canSimulateFailure ? "" : `<p class="absolute-voice-provider-simulation__empty">${escapeHtml12(options.fallbackRequiredMessage ?? "Configure fallback providers before simulating failure.")}</p>`}
|
|
4086
5934
|
<div class="absolute-voice-provider-simulation__actions">${failureButtons}${recoveryButtons}</div>
|
|
4087
|
-
${snapshot.error ? `<p class="absolute-voice-provider-simulation__error">${
|
|
4088
|
-
${model.resultText ? `<pre class="absolute-voice-provider-simulation__result">${
|
|
5935
|
+
${snapshot.error ? `<p class="absolute-voice-provider-simulation__error">${escapeHtml12(snapshot.error)}</p>` : ""}
|
|
5936
|
+
${model.resultText ? `<pre class="absolute-voice-provider-simulation__result">${escapeHtml12(model.resultText)}</pre>` : ""}
|
|
4089
5937
|
</section>`;
|
|
4090
5938
|
};
|
|
4091
5939
|
var bindVoiceProviderSimulationControls = (element, store) => {
|
|
@@ -4346,7 +6194,7 @@ var createVoiceProviderCapabilitiesStore = (path = "/api/provider-capabilities",
|
|
|
4346
6194
|
// src/client/providerCapabilitiesWidget.ts
|
|
4347
6195
|
var DEFAULT_TITLE7 = "Provider Capabilities";
|
|
4348
6196
|
var DEFAULT_DESCRIPTION7 = "Configured, selected, and healthy voice providers for this deployment.";
|
|
4349
|
-
var
|
|
6197
|
+
var escapeHtml13 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
4350
6198
|
var formatProvider = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
|
|
4351
6199
|
var formatKind2 = (kind) => kind.toUpperCase();
|
|
4352
6200
|
var formatStatus2 = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
|
|
@@ -4401,25 +6249,25 @@ var createVoiceProviderCapabilitiesViewModel = (snapshot, options = {}) => {
|
|
|
4401
6249
|
};
|
|
4402
6250
|
var renderVoiceProviderCapabilitiesHTML = (snapshot, options = {}) => {
|
|
4403
6251
|
const model = createVoiceProviderCapabilitiesViewModel(snapshot, options);
|
|
4404
|
-
const capabilities = model.capabilities.length ? `<div class="absolute-voice-provider-capabilities__providers">${model.capabilities.map((capability) => `<article class="absolute-voice-provider-capabilities__provider absolute-voice-provider-capabilities__provider--${
|
|
6252
|
+
const capabilities = model.capabilities.length ? `<div class="absolute-voice-provider-capabilities__providers">${model.capabilities.map((capability) => `<article class="absolute-voice-provider-capabilities__provider absolute-voice-provider-capabilities__provider--${escapeHtml13(capability.status)}">
|
|
4405
6253
|
<header>
|
|
4406
|
-
<strong>${
|
|
4407
|
-
<span>${
|
|
6254
|
+
<strong>${escapeHtml13(capability.label)}</strong>
|
|
6255
|
+
<span>${escapeHtml13(formatStatus2(capability.status))}</span>
|
|
4408
6256
|
</header>
|
|
4409
|
-
<p>${
|
|
6257
|
+
<p>${escapeHtml13(capability.detail)}</p>
|
|
4410
6258
|
<dl>${capability.rows.map((row) => `<div>
|
|
4411
|
-
<dt>${
|
|
4412
|
-
<dd>${
|
|
6259
|
+
<dt>${escapeHtml13(row.label)}</dt>
|
|
6260
|
+
<dd>${escapeHtml13(row.value)}</dd>
|
|
4413
6261
|
</div>`).join("")}</dl>
|
|
4414
6262
|
</article>`).join("")}</div>` : '<p class="absolute-voice-provider-capabilities__empty">Configure provider capabilities to see deployment coverage.</p>';
|
|
4415
|
-
return `<section class="absolute-voice-provider-capabilities absolute-voice-provider-capabilities--${
|
|
6263
|
+
return `<section class="absolute-voice-provider-capabilities absolute-voice-provider-capabilities--${escapeHtml13(model.status)}">
|
|
4416
6264
|
<header class="absolute-voice-provider-capabilities__header">
|
|
4417
|
-
<span class="absolute-voice-provider-capabilities__eyebrow">${
|
|
4418
|
-
<strong class="absolute-voice-provider-capabilities__label">${
|
|
6265
|
+
<span class="absolute-voice-provider-capabilities__eyebrow">${escapeHtml13(model.title)}</span>
|
|
6266
|
+
<strong class="absolute-voice-provider-capabilities__label">${escapeHtml13(model.label)}</strong>
|
|
4419
6267
|
</header>
|
|
4420
|
-
<p class="absolute-voice-provider-capabilities__description">${
|
|
6268
|
+
<p class="absolute-voice-provider-capabilities__description">${escapeHtml13(model.description)}</p>
|
|
4421
6269
|
${capabilities}
|
|
4422
|
-
${model.error ? `<p class="absolute-voice-provider-capabilities__error">${
|
|
6270
|
+
${model.error ? `<p class="absolute-voice-provider-capabilities__error">${escapeHtml13(model.error)}</p>` : ""}
|
|
4423
6271
|
</section>`;
|
|
4424
6272
|
};
|
|
4425
6273
|
var getVoiceProviderCapabilitiesCSS = () => `.absolute-voice-provider-capabilities{border:1px solid #bfd7ea;border-radius:20px;background:#f6fbff;color:#08131f;padding:18px;box-shadow:0 18px 40px rgba(14,51,78,.12);font-family:inherit}.absolute-voice-provider-capabilities--error,.absolute-voice-provider-capabilities--warning{border-color:#f2a7a7;background:#fff5f3}.absolute-voice-provider-capabilities__header,.absolute-voice-provider-capabilities__provider header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-provider-capabilities__eyebrow{color:#255f85;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-provider-capabilities__label{font-size:24px;line-height:1}.absolute-voice-provider-capabilities__description,.absolute-voice-provider-capabilities__provider p,.absolute-voice-provider-capabilities__provider dt,.absolute-voice-provider-capabilities__empty{color:#405467}.absolute-voice-provider-capabilities__providers{display:grid;gap:12px;margin-top:14px}.absolute-voice-provider-capabilities__provider{background:#fff;border:1px solid #d7e7f3;border-radius:16px;padding:14px}.absolute-voice-provider-capabilities__provider--selected,.absolute-voice-provider-capabilities__provider--healthy{border-color:#86efac}.absolute-voice-provider-capabilities__provider--degraded,.absolute-voice-provider-capabilities__provider--rate-limited,.absolute-voice-provider-capabilities__provider--suppressed,.absolute-voice-provider-capabilities__provider--unconfigured{border-color:#f2a7a7}.absolute-voice-provider-capabilities__provider p{margin:10px 0}.absolute-voice-provider-capabilities__provider dl{display:grid;gap:8px;grid-template-columns:repeat(2,minmax(0,1fr));margin:0}.absolute-voice-provider-capabilities__provider div{background:#f6fbff;border:1px solid #d7e7f3;border-radius:12px;padding:8px}.absolute-voice-provider-capabilities__provider dt{font-size:12px}.absolute-voice-provider-capabilities__provider dd{font-weight:800;margin:4px 0 0}.absolute-voice-provider-capabilities__empty{margin:14px 0 0}.absolute-voice-provider-capabilities__error{color:#9f1239;font-weight:700}`;
|
|
@@ -4676,7 +6524,7 @@ function useVoiceProviderContracts(path = "/api/provider-contracts", options = {
|
|
|
4676
6524
|
// src/client/providerContractsWidget.ts
|
|
4677
6525
|
var DEFAULT_TITLE8 = "Provider Contracts";
|
|
4678
6526
|
var DEFAULT_DESCRIPTION8 = "Production contract coverage for provider env, latency, fallback, streaming, and capabilities.";
|
|
4679
|
-
var
|
|
6527
|
+
var escapeHtml14 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
4680
6528
|
var formatProvider2 = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
|
|
4681
6529
|
var formatStatus3 = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
|
|
4682
6530
|
var contractDetail = (row) => {
|
|
@@ -4720,26 +6568,26 @@ var createVoiceProviderContractsViewModel = (snapshot, options = {}) => {
|
|
|
4720
6568
|
};
|
|
4721
6569
|
var renderVoiceProviderContractsHTML = (snapshot, options = {}) => {
|
|
4722
6570
|
const model = createVoiceProviderContractsViewModel(snapshot, options);
|
|
4723
|
-
const rows = model.rows.length ? `<div class="absolute-voice-provider-contracts__rows">${model.rows.map((row) => `<article class="absolute-voice-provider-contracts__row absolute-voice-provider-contracts__row--${
|
|
6571
|
+
const rows = model.rows.length ? `<div class="absolute-voice-provider-contracts__rows">${model.rows.map((row) => `<article class="absolute-voice-provider-contracts__row absolute-voice-provider-contracts__row--${escapeHtml14(row.status)}">
|
|
4724
6572
|
<header>
|
|
4725
|
-
<strong>${
|
|
4726
|
-
<span>${
|
|
6573
|
+
<strong>${escapeHtml14(row.label)}</strong>
|
|
6574
|
+
<span>${escapeHtml14(formatStatus3(row.status))}</span>
|
|
4727
6575
|
</header>
|
|
4728
|
-
<p>${
|
|
4729
|
-
${row.remediations.length ? `<ul class="absolute-voice-provider-contracts__remediations">${row.remediations.map((remediation) => `<li>${remediation.href ? `<a href="${
|
|
6576
|
+
<p>${escapeHtml14(row.detail)}</p>
|
|
6577
|
+
${row.remediations.length ? `<ul class="absolute-voice-provider-contracts__remediations">${row.remediations.map((remediation) => `<li>${remediation.href ? `<a href="${escapeHtml14(remediation.href)}">${escapeHtml14(remediation.label)}</a>` : `<strong>${escapeHtml14(remediation.label)}</strong>`}<span>${escapeHtml14(remediation.detail)}</span></li>`).join("")}</ul>` : ""}
|
|
4730
6578
|
<dl>${row.rows.map((item) => `<div>
|
|
4731
|
-
<dt>${
|
|
4732
|
-
<dd>${
|
|
6579
|
+
<dt>${escapeHtml14(item.label)}</dt>
|
|
6580
|
+
<dd>${escapeHtml14(item.value)}</dd>
|
|
4733
6581
|
</div>`).join("")}</dl>
|
|
4734
6582
|
</article>`).join("")}</div>` : '<p class="absolute-voice-provider-contracts__empty">Configure provider contracts to see production coverage.</p>';
|
|
4735
|
-
return `<section class="absolute-voice-provider-contracts absolute-voice-provider-contracts--${
|
|
6583
|
+
return `<section class="absolute-voice-provider-contracts absolute-voice-provider-contracts--${escapeHtml14(model.status)}">
|
|
4736
6584
|
<header class="absolute-voice-provider-contracts__header">
|
|
4737
|
-
<span class="absolute-voice-provider-contracts__eyebrow">${
|
|
4738
|
-
<strong class="absolute-voice-provider-contracts__label">${
|
|
6585
|
+
<span class="absolute-voice-provider-contracts__eyebrow">${escapeHtml14(model.title)}</span>
|
|
6586
|
+
<strong class="absolute-voice-provider-contracts__label">${escapeHtml14(model.label)}</strong>
|
|
4739
6587
|
</header>
|
|
4740
|
-
<p class="absolute-voice-provider-contracts__description">${
|
|
6588
|
+
<p class="absolute-voice-provider-contracts__description">${escapeHtml14(model.description)}</p>
|
|
4741
6589
|
${rows}
|
|
4742
|
-
${model.error ? `<p class="absolute-voice-provider-contracts__error">${
|
|
6590
|
+
${model.error ? `<p class="absolute-voice-provider-contracts__error">${escapeHtml14(model.error)}</p>` : ""}
|
|
4743
6591
|
</section>`;
|
|
4744
6592
|
};
|
|
4745
6593
|
var getVoiceProviderContractsCSS = () => `.absolute-voice-provider-contracts{border:1px solid #b8dcc7;border-radius:20px;background:#f7fff9;color:#09140d;padding:18px;box-shadow:0 18px 40px rgba(21,83,45,.12);font-family:inherit}.absolute-voice-provider-contracts--error,.absolute-voice-provider-contracts--warning{border-color:#f2a7a7;background:#fff7f4}.absolute-voice-provider-contracts__header,.absolute-voice-provider-contracts__row header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-provider-contracts__eyebrow{color:#166534;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-provider-contracts__label{font-size:24px;line-height:1}.absolute-voice-provider-contracts__description,.absolute-voice-provider-contracts__row p,.absolute-voice-provider-contracts__row dt,.absolute-voice-provider-contracts__empty{color:#405448}.absolute-voice-provider-contracts__rows{display:grid;gap:12px;margin-top:14px}.absolute-voice-provider-contracts__row{background:#fff;border:1px solid #d6eadb;border-radius:16px;padding:14px}.absolute-voice-provider-contracts__row--pass{border-color:#86efac}.absolute-voice-provider-contracts__row--warn,.absolute-voice-provider-contracts__row--fail{border-color:#f2a7a7}.absolute-voice-provider-contracts__row p{margin:10px 0}.absolute-voice-provider-contracts__remediations{display:grid;gap:8px;list-style:none;margin:0 0 10px;padding:0}.absolute-voice-provider-contracts__remediations li{background:#fff7ed;border:1px solid #fed7aa;border-radius:12px;display:grid;gap:3px;padding:8px}.absolute-voice-provider-contracts__remediations a,.absolute-voice-provider-contracts__remediations strong{color:#9a3412}.absolute-voice-provider-contracts__remediations span{color:#7c2d12}.absolute-voice-provider-contracts__row dl{display:grid;gap:8px;grid-template-columns:repeat(2,minmax(0,1fr));margin:0}.absolute-voice-provider-contracts__row div{background:#f7fff9;border:1px solid #d6eadb;border-radius:12px;padding:8px}.absolute-voice-provider-contracts__row dt{font-size:12px}.absolute-voice-provider-contracts__row dd{font-weight:800;margin:4px 0 0}.absolute-voice-provider-contracts__error{color:#9f1239;font-weight:700}`;
|
|
@@ -4936,7 +6784,7 @@ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {
|
|
|
4936
6784
|
// src/client/providerStatusWidget.ts
|
|
4937
6785
|
var DEFAULT_TITLE9 = "Voice Providers";
|
|
4938
6786
|
var DEFAULT_DESCRIPTION9 = "Live provider health, fallback counts, latency, and suppression state from your self-hosted trace store.";
|
|
4939
|
-
var
|
|
6787
|
+
var escapeHtml15 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
4940
6788
|
var formatProvider3 = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
|
|
4941
6789
|
var formatStatus4 = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
|
|
4942
6790
|
var formatLatency = (value) => typeof value === "number" ? `${value}ms` : "No samples";
|
|
@@ -4992,25 +6840,25 @@ var createVoiceProviderStatusViewModel = (snapshot, options = {}) => {
|
|
|
4992
6840
|
};
|
|
4993
6841
|
var renderVoiceProviderStatusHTML = (snapshot, options = {}) => {
|
|
4994
6842
|
const model = createVoiceProviderStatusViewModel(snapshot, options);
|
|
4995
|
-
const providers = model.providers.length ? `<div class="absolute-voice-provider-status__providers">${model.providers.map((provider) => `<article class="absolute-voice-provider-status__provider absolute-voice-provider-status__provider--${
|
|
6843
|
+
const providers = model.providers.length ? `<div class="absolute-voice-provider-status__providers">${model.providers.map((provider) => `<article class="absolute-voice-provider-status__provider absolute-voice-provider-status__provider--${escapeHtml15(provider.status)}">
|
|
4996
6844
|
<header>
|
|
4997
|
-
<strong>${
|
|
4998
|
-
<span>${
|
|
6845
|
+
<strong>${escapeHtml15(provider.label)}</strong>
|
|
6846
|
+
<span>${escapeHtml15(formatStatus4(provider.status))}</span>
|
|
4999
6847
|
</header>
|
|
5000
|
-
<p>${
|
|
6848
|
+
<p>${escapeHtml15(provider.detail)}</p>
|
|
5001
6849
|
<dl>${provider.rows.map((row) => `<div>
|
|
5002
|
-
<dt>${
|
|
5003
|
-
<dd>${
|
|
6850
|
+
<dt>${escapeHtml15(row.label)}</dt>
|
|
6851
|
+
<dd>${escapeHtml15(row.value)}</dd>
|
|
5004
6852
|
</div>`).join("")}</dl>
|
|
5005
6853
|
</article>`).join("")}</div>` : '<p class="absolute-voice-provider-status__empty">Run voice traffic to see provider health.</p>';
|
|
5006
|
-
return `<section class="absolute-voice-provider-status absolute-voice-provider-status--${
|
|
6854
|
+
return `<section class="absolute-voice-provider-status absolute-voice-provider-status--${escapeHtml15(model.status)}">
|
|
5007
6855
|
<header class="absolute-voice-provider-status__header">
|
|
5008
|
-
<span class="absolute-voice-provider-status__eyebrow">${
|
|
5009
|
-
<strong class="absolute-voice-provider-status__label">${
|
|
6856
|
+
<span class="absolute-voice-provider-status__eyebrow">${escapeHtml15(model.title)}</span>
|
|
6857
|
+
<strong class="absolute-voice-provider-status__label">${escapeHtml15(model.label)}</strong>
|
|
5010
6858
|
</header>
|
|
5011
|
-
<p class="absolute-voice-provider-status__description">${
|
|
6859
|
+
<p class="absolute-voice-provider-status__description">${escapeHtml15(model.description)}</p>
|
|
5012
6860
|
${providers}
|
|
5013
|
-
${model.error ? `<p class="absolute-voice-provider-status__error">${
|
|
6861
|
+
${model.error ? `<p class="absolute-voice-provider-status__error">${escapeHtml15(model.error)}</p>` : ""}
|
|
5014
6862
|
</section>`;
|
|
5015
6863
|
};
|
|
5016
6864
|
var getVoiceProviderStatusCSS = () => `.absolute-voice-provider-status{border:1px solid #d8d2c4;border-radius:20px;background:#fffaf0;color:#16130d;padding:18px;box-shadow:0 18px 40px rgba(47,37,18,.12);font-family:inherit}.absolute-voice-provider-status--error,.absolute-voice-provider-status--warning{border-color:#f2a7a7;background:#fff5f3}.absolute-voice-provider-status__header,.absolute-voice-provider-status__provider header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-provider-status__eyebrow{color:#73664f;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-provider-status__label{font-size:24px;line-height:1}.absolute-voice-provider-status__description,.absolute-voice-provider-status__provider p,.absolute-voice-provider-status__provider dt,.absolute-voice-provider-status__empty{color:#514733}.absolute-voice-provider-status__providers{display:grid;gap:12px;margin-top:14px}.absolute-voice-provider-status__provider{background:#fff;border:1px solid #eee4d2;border-radius:16px;padding:14px}.absolute-voice-provider-status__provider--degraded,.absolute-voice-provider-status__provider--rate-limited,.absolute-voice-provider-status__provider--suppressed{border-color:#f2a7a7}.absolute-voice-provider-status__provider--recoverable{border-color:#fbbf24}.absolute-voice-provider-status__provider p{margin:10px 0}.absolute-voice-provider-status__provider dl{display:grid;gap:8px;grid-template-columns:repeat(2,minmax(0,1fr));margin:0}.absolute-voice-provider-status__provider div{background:#fffaf0;border:1px solid #eee4d2;border-radius:12px;padding:8px}.absolute-voice-provider-status__provider dt{font-size:12px}.absolute-voice-provider-status__provider dd{font-weight:800;margin:4px 0 0}.absolute-voice-provider-status__empty{margin:14px 0 0}.absolute-voice-provider-status__error{color:#9f1239;font-weight:700}`;
|
|
@@ -5239,7 +7087,7 @@ var createVoiceRoutingStatusStore = (path = "/api/routing/latest", options = {})
|
|
|
5239
7087
|
// src/client/routingStatusWidget.ts
|
|
5240
7088
|
var DEFAULT_TITLE10 = "Voice Routing";
|
|
5241
7089
|
var DEFAULT_DESCRIPTION10 = "Latest provider routing decision from the self-hosted trace store.";
|
|
5242
|
-
var
|
|
7090
|
+
var escapeHtml16 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
5243
7091
|
var formatValue = (value, fallback = "None") => typeof value === "string" && value.trim() ? value : typeof value === "number" && Number.isFinite(value) ? String(value) : fallback;
|
|
5244
7092
|
var formatProviderRoutes = (routes) => routes && typeof routes === "object" ? Object.entries(routes).map(([role, provider]) => `${role}: ${formatValue(provider)}`).join(", ") || "None" : "None";
|
|
5245
7093
|
var getProviderRoute = (routes, role) => routes && typeof routes === "object" ? formatValue(routes[role], "Not configured") : "Not configured";
|
|
@@ -5321,22 +7169,22 @@ var createVoiceRoutingStatusViewModel = (snapshot, options = {}) => {
|
|
|
5321
7169
|
var renderVoiceRoutingStatusHTML = (snapshot, options = {}) => {
|
|
5322
7170
|
const model = createVoiceRoutingStatusViewModel(snapshot, options);
|
|
5323
7171
|
const activeStack = model.activeStack.length ? `<div class="absolute-voice-routing-status__stack" aria-label="Active voice stack">${model.activeStack.map((item) => `<div>
|
|
5324
|
-
<span>${
|
|
5325
|
-
<strong>${
|
|
7172
|
+
<span>${escapeHtml16(item.label)}</span>
|
|
7173
|
+
<strong>${escapeHtml16(item.value)}</strong>
|
|
5326
7174
|
</div>`).join("")}</div>` : "";
|
|
5327
7175
|
const rows = model.rows.length ? `<div class="absolute-voice-routing-status__grid">${model.rows.map((row) => `<div>
|
|
5328
|
-
<span>${
|
|
5329
|
-
<strong>${
|
|
7176
|
+
<span>${escapeHtml16(row.label)}</span>
|
|
7177
|
+
<strong>${escapeHtml16(row.value)}</strong>
|
|
5330
7178
|
</div>`).join("")}</div>` : '<p class="absolute-voice-routing-status__empty">Start a voice session to see the selected provider.</p>';
|
|
5331
|
-
return `<section class="absolute-voice-routing-status absolute-voice-routing-status--${
|
|
7179
|
+
return `<section class="absolute-voice-routing-status absolute-voice-routing-status--${escapeHtml16(model.status)}">
|
|
5332
7180
|
<header class="absolute-voice-routing-status__header">
|
|
5333
|
-
<span class="absolute-voice-routing-status__eyebrow">${
|
|
5334
|
-
<strong class="absolute-voice-routing-status__label">${
|
|
7181
|
+
<span class="absolute-voice-routing-status__eyebrow">${escapeHtml16(model.title)}</span>
|
|
7182
|
+
<strong class="absolute-voice-routing-status__label">${escapeHtml16(model.label)}</strong>
|
|
5335
7183
|
</header>
|
|
5336
|
-
<p class="absolute-voice-routing-status__description">${
|
|
7184
|
+
<p class="absolute-voice-routing-status__description">${escapeHtml16(model.description)}</p>
|
|
5337
7185
|
${activeStack}
|
|
5338
7186
|
${rows}
|
|
5339
|
-
${model.error ? `<p class="absolute-voice-routing-status__error">${
|
|
7187
|
+
${model.error ? `<p class="absolute-voice-routing-status__error">${escapeHtml16(model.error)}</p>` : ""}
|
|
5340
7188
|
</section>`;
|
|
5341
7189
|
};
|
|
5342
7190
|
var getVoiceRoutingStatusCSS = () => `.absolute-voice-routing-status{border:1px solid #d8d2c4;border-radius:20px;background:#fffaf0;color:#16130d;padding:18px;box-shadow:0 18px 40px rgba(47,37,18,.12);font-family:inherit}.absolute-voice-routing-status--error{border-color:#f2a7a7;background:#fff5f3}.absolute-voice-routing-status__header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-routing-status__eyebrow{color:#73664f;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-routing-status__label{font-size:24px;line-height:1}.absolute-voice-routing-status__description{color:#514733;margin:12px 0 0}.absolute-voice-routing-status__stack{background:linear-gradient(135deg,#16130d,#49391f);border-radius:18px;color:#fff;display:grid;gap:8px;grid-template-columns:repeat(5,minmax(0,1fr));margin-top:14px;padding:12px}.absolute-voice-routing-status__stack div{border-left:1px solid rgba(255,255,255,.18);padding-left:10px}.absolute-voice-routing-status__stack div:first-child{border-left:0;padding-left:0}.absolute-voice-routing-status__stack span{color:#e9d9b8;display:block;font-size:11px;font-weight:800;letter-spacing:.08em;margin-bottom:5px;text-transform:uppercase}.absolute-voice-routing-status__stack strong{display:block;font-size:13px;line-height:1.25;overflow-wrap:anywhere}.absolute-voice-routing-status__grid{display:grid;gap:8px;grid-template-columns:repeat(2,minmax(0,1fr));margin-top:14px}.absolute-voice-routing-status__grid div{background:#fff;border:1px solid #eee4d2;border-radius:14px;padding:10px 12px}.absolute-voice-routing-status__grid span{color:#655944;display:block;font-size:12px;margin-bottom:4px}.absolute-voice-routing-status__grid strong{overflow-wrap:anywhere}.absolute-voice-routing-status__empty{color:#655944;margin:14px 0 0}.absolute-voice-routing-status__error{color:#9f1239;font-weight:700}@media (max-width:760px){.absolute-voice-routing-status__stack{grid-template-columns:repeat(2,minmax(0,1fr))}.absolute-voice-routing-status__stack div{border-left:0;border-top:1px solid rgba(255,255,255,.18);padding-left:0;padding-top:8px}.absolute-voice-routing-status__stack div:first-child{border-top:0;padding-top:0}}`;
|
|
@@ -5550,8 +7398,8 @@ var createVoiceTraceTimelineStore = (path = "/api/voice-traces", options = {}) =
|
|
|
5550
7398
|
};
|
|
5551
7399
|
|
|
5552
7400
|
// src/client/agentSquadStatus.ts
|
|
5553
|
-
var
|
|
5554
|
-
var getPayloadString = (event, key) =>
|
|
7401
|
+
var getString4 = (value) => typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
7402
|
+
var getPayloadString = (event, key) => getString4(event.payload?.[key]);
|
|
5555
7403
|
var eventStatus = (event) => {
|
|
5556
7404
|
const status = getPayloadString(event, "status");
|
|
5557
7405
|
if (status === "blocked")
|
|
@@ -5767,7 +7615,7 @@ var createVoiceTurnLatencyStore = (path = "/api/turn-latency", options = {}) =>
|
|
|
5767
7615
|
var DEFAULT_TITLE11 = "Turn Latency";
|
|
5768
7616
|
var DEFAULT_DESCRIPTION11 = "Per-turn timing from first transcript to commit and assistant response start.";
|
|
5769
7617
|
var DEFAULT_PROOF_LABEL = "Run latency proof";
|
|
5770
|
-
var
|
|
7618
|
+
var escapeHtml17 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
5771
7619
|
var formatMs2 = (value) => typeof value === "number" ? `${Math.round(value)}ms` : "n/a";
|
|
5772
7620
|
var createVoiceTurnLatencyViewModel = (snapshot, options = {}) => {
|
|
5773
7621
|
const turns = (snapshot.report?.turns ?? []).map((turn) => ({
|
|
@@ -5795,25 +7643,25 @@ var createVoiceTurnLatencyViewModel = (snapshot, options = {}) => {
|
|
|
5795
7643
|
};
|
|
5796
7644
|
var renderVoiceTurnLatencyHTML = (snapshot, options = {}) => {
|
|
5797
7645
|
const model = createVoiceTurnLatencyViewModel(snapshot, options);
|
|
5798
|
-
const turns = model.turns.length ? `<div class="absolute-voice-turn-latency__turns">${model.turns.map((turn) => `<article class="absolute-voice-turn-latency__turn absolute-voice-turn-latency__turn--${
|
|
7646
|
+
const turns = model.turns.length ? `<div class="absolute-voice-turn-latency__turns">${model.turns.map((turn) => `<article class="absolute-voice-turn-latency__turn absolute-voice-turn-latency__turn--${escapeHtml17(turn.status)}">
|
|
5799
7647
|
<header>
|
|
5800
|
-
<strong>${
|
|
5801
|
-
<span>${
|
|
7648
|
+
<strong>${escapeHtml17(turn.label)}</strong>
|
|
7649
|
+
<span>${escapeHtml17(turn.status)}</span>
|
|
5802
7650
|
</header>
|
|
5803
7651
|
<dl>${turn.rows.map((row) => `<div>
|
|
5804
|
-
<dt>${
|
|
5805
|
-
<dd>${
|
|
7652
|
+
<dt>${escapeHtml17(row.label)}</dt>
|
|
7653
|
+
<dd>${escapeHtml17(row.value)}</dd>
|
|
5806
7654
|
</div>`).join("")}</dl>
|
|
5807
7655
|
</article>`).join("")}</div>` : '<p class="absolute-voice-turn-latency__empty">Complete a voice turn to see latency diagnostics.</p>';
|
|
5808
|
-
return `<section class="absolute-voice-turn-latency absolute-voice-turn-latency--${
|
|
7656
|
+
return `<section class="absolute-voice-turn-latency absolute-voice-turn-latency--${escapeHtml17(model.status)}">
|
|
5809
7657
|
<header class="absolute-voice-turn-latency__header">
|
|
5810
|
-
<span class="absolute-voice-turn-latency__eyebrow">${
|
|
5811
|
-
<strong class="absolute-voice-turn-latency__label">${
|
|
7658
|
+
<span class="absolute-voice-turn-latency__eyebrow">${escapeHtml17(model.title)}</span>
|
|
7659
|
+
<strong class="absolute-voice-turn-latency__label">${escapeHtml17(model.label)}</strong>
|
|
5812
7660
|
</header>
|
|
5813
|
-
<p class="absolute-voice-turn-latency__description">${
|
|
5814
|
-
${model.showProofAction ? `<button class="absolute-voice-turn-latency__proof" data-absolute-voice-turn-latency-proof type="button">${
|
|
7661
|
+
<p class="absolute-voice-turn-latency__description">${escapeHtml17(model.description)}</p>
|
|
7662
|
+
${model.showProofAction ? `<button class="absolute-voice-turn-latency__proof" data-absolute-voice-turn-latency-proof type="button">${escapeHtml17(model.proofLabel ?? DEFAULT_PROOF_LABEL)}</button>` : ""}
|
|
5815
7663
|
${turns}
|
|
5816
|
-
${model.error ? `<p class="absolute-voice-turn-latency__error">${
|
|
7664
|
+
${model.error ? `<p class="absolute-voice-turn-latency__error">${escapeHtml17(model.error)}</p>` : ""}
|
|
5817
7665
|
</section>`;
|
|
5818
7666
|
};
|
|
5819
7667
|
var mountVoiceTurnLatency = (element, path = "/api/turn-latency", options = {}) => {
|
|
@@ -6046,7 +7894,7 @@ var createVoiceTurnQualityStore = (path = "/api/turn-quality", options = {}) =>
|
|
|
6046
7894
|
// src/client/turnQualityWidget.ts
|
|
6047
7895
|
var DEFAULT_TITLE12 = "Turn Quality";
|
|
6048
7896
|
var DEFAULT_DESCRIPTION12 = "Per-turn STT confidence, fallback selection, corrections, and transcript coverage.";
|
|
6049
|
-
var
|
|
7897
|
+
var escapeHtml18 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
6050
7898
|
var formatConfidence = (value) => typeof value === "number" ? `${Math.round(value * 100)}%` : "n/a";
|
|
6051
7899
|
var formatMaybe = (value) => value === undefined || value === "" ? "n/a" : String(value);
|
|
6052
7900
|
var getTurnDetail = (turn) => {
|
|
@@ -6096,25 +7944,25 @@ var createVoiceTurnQualityViewModel = (snapshot, options = {}) => {
|
|
|
6096
7944
|
};
|
|
6097
7945
|
var renderVoiceTurnQualityHTML = (snapshot, options = {}) => {
|
|
6098
7946
|
const model = createVoiceTurnQualityViewModel(snapshot, options);
|
|
6099
|
-
const turns = model.turns.length ? `<div class="absolute-voice-turn-quality__turns">${model.turns.map((turn) => `<article class="absolute-voice-turn-quality__turn absolute-voice-turn-quality__turn--${
|
|
7947
|
+
const turns = model.turns.length ? `<div class="absolute-voice-turn-quality__turns">${model.turns.map((turn) => `<article class="absolute-voice-turn-quality__turn absolute-voice-turn-quality__turn--${escapeHtml18(turn.status)}">
|
|
6100
7948
|
<header>
|
|
6101
|
-
<strong>${
|
|
6102
|
-
<span>${
|
|
7949
|
+
<strong>${escapeHtml18(turn.label)}</strong>
|
|
7950
|
+
<span>${escapeHtml18(turn.status)}</span>
|
|
6103
7951
|
</header>
|
|
6104
|
-
<p>${
|
|
7952
|
+
<p>${escapeHtml18(turn.detail)}</p>
|
|
6105
7953
|
<dl>${turn.rows.map((row) => `<div>
|
|
6106
|
-
<dt>${
|
|
6107
|
-
<dd>${
|
|
7954
|
+
<dt>${escapeHtml18(row.label)}</dt>
|
|
7955
|
+
<dd>${escapeHtml18(row.value)}</dd>
|
|
6108
7956
|
</div>`).join("")}</dl>
|
|
6109
7957
|
</article>`).join("")}</div>` : '<p class="absolute-voice-turn-quality__empty">Complete a voice turn to see STT quality diagnostics.</p>';
|
|
6110
|
-
return `<section class="absolute-voice-turn-quality absolute-voice-turn-quality--${
|
|
7958
|
+
return `<section class="absolute-voice-turn-quality absolute-voice-turn-quality--${escapeHtml18(model.status)}">
|
|
6111
7959
|
<header class="absolute-voice-turn-quality__header">
|
|
6112
|
-
<span class="absolute-voice-turn-quality__eyebrow">${
|
|
6113
|
-
<strong class="absolute-voice-turn-quality__label">${
|
|
7960
|
+
<span class="absolute-voice-turn-quality__eyebrow">${escapeHtml18(model.title)}</span>
|
|
7961
|
+
<strong class="absolute-voice-turn-quality__label">${escapeHtml18(model.label)}</strong>
|
|
6114
7962
|
</header>
|
|
6115
|
-
<p class="absolute-voice-turn-quality__description">${
|
|
7963
|
+
<p class="absolute-voice-turn-quality__description">${escapeHtml18(model.description)}</p>
|
|
6116
7964
|
${turns}
|
|
6117
|
-
${model.error ? `<p class="absolute-voice-turn-quality__error">${
|
|
7965
|
+
${model.error ? `<p class="absolute-voice-turn-quality__error">${escapeHtml18(model.error)}</p>` : ""}
|
|
6118
7966
|
</section>`;
|
|
6119
7967
|
};
|
|
6120
7968
|
var getVoiceTurnQualityCSS = () => `.absolute-voice-turn-quality{border:1px solid #e4d1a3;border-radius:20px;background:#fff9eb;color:#17120a;padding:18px;box-shadow:0 18px 40px rgba(73,48,14,.12);font-family:inherit}.absolute-voice-turn-quality--error,.absolute-voice-turn-quality--warning{border-color:#f2a7a7;background:#fff5f3}.absolute-voice-turn-quality__header,.absolute-voice-turn-quality__turn header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-turn-quality__eyebrow{color:#8a5a0a;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-turn-quality__label{font-size:24px;line-height:1}.absolute-voice-turn-quality__description,.absolute-voice-turn-quality__turn p,.absolute-voice-turn-quality__turn dt,.absolute-voice-turn-quality__empty{color:#5a4930}.absolute-voice-turn-quality__turns{display:grid;gap:12px;margin-top:14px}.absolute-voice-turn-quality__turn{background:#fff;border:1px solid #f0dfba;border-radius:16px;padding:14px}.absolute-voice-turn-quality__turn--pass{border-color:#86efac}.absolute-voice-turn-quality__turn--warn,.absolute-voice-turn-quality__turn--unknown{border-color:#fbbf24}.absolute-voice-turn-quality__turn--fail{border-color:#f2a7a7}.absolute-voice-turn-quality__turn p{margin:10px 0}.absolute-voice-turn-quality__turn dl{display:grid;gap:8px;grid-template-columns:repeat(2,minmax(0,1fr));margin:0}.absolute-voice-turn-quality__turn div{background:#fff9eb;border:1px solid #f0dfba;border-radius:12px;padding:8px}.absolute-voice-turn-quality__turn dt{font-size:12px}.absolute-voice-turn-quality__turn dd{font-weight:800;margin:4px 0 0}.absolute-voice-turn-quality__empty{margin:14px 0 0}.absolute-voice-turn-quality__error{color:#9f1239;font-weight:700}`;
|