@absolutejs/voice 0.0.22-beta.102 → 0.0.22-beta.103

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/index.d.ts CHANGED
@@ -11,6 +11,7 @@ export { createVoiceAgent, createVoiceAgentSquad, createVoiceAgentTool } from '.
11
11
  export { createVoiceToolIdempotencyKey, createVoiceToolRuntime } from './toolRuntime';
12
12
  export { createVoiceToolContract, createVoiceToolContractHTMLHandler, createVoiceToolContractJSONHandler, createVoiceToolContractRoutes, createVoiceToolRuntimeContractDefaults, renderVoiceToolContractHTML, runVoiceToolContractSuite, runVoiceToolContract } from './toolContract';
13
13
  export { createVoiceTurnLatencyHTMLHandler, createVoiceTurnLatencyJSONHandler, createVoiceTurnLatencyRoutes, renderVoiceTurnLatencyHTML, summarizeVoiceTurnLatency } from './turnLatency';
14
+ export { createVoiceLiveLatencyRoutes, renderVoiceLiveLatencyHTML, summarizeVoiceLiveLatency } from './liveLatency';
14
15
  export { createVoiceTurnQualityHTMLHandler, createVoiceTurnQualityJSONHandler, createVoiceTurnQualityRoutes, renderVoiceTurnQualityHTML, summarizeVoiceTurnQuality } from './turnQuality';
15
16
  export { createVoiceOutcomeContractHTMLHandler, createVoiceOutcomeContractJSONHandler, createVoiceOutcomeContractRoutes, renderVoiceOutcomeContractHTML, runVoiceOutcomeContractSuite } from './outcomeContract';
16
17
  export { applyVoiceTelephonyOutcome, createMemoryVoiceTelephonyWebhookIdempotencyStore, createVoiceTelephonyOutcomePolicy, createVoiceTelephonyWebhookHandler, createVoiceTelephonyWebhookRoutes, parseVoiceTelephonyWebhookEvent, resolveVoiceTelephonyOutcome, signVoiceTwilioWebhook, verifyVoiceTwilioWebhookSignature, voiceTelephonyOutcomeToRouteResult } from './telephonyOutcome';
@@ -63,6 +64,7 @@ export type { OpenAIVoiceTTSOptions, OpenAIVoiceTTSVoice } from './openaiTTS';
63
64
  export type { VoiceProviderHealthStatus, VoiceProviderHealthSummary, VoiceProviderHealthSummaryOptions } from './providerHealth';
64
65
  export type { VoiceProviderCapabilityDefinition, VoiceProviderCapabilityHandlerOptions, VoiceProviderCapabilityHTMLHandlerOptions, VoiceProviderCapabilityKind, VoiceProviderCapabilityOptions, VoiceProviderCapabilityReport, VoiceProviderCapabilityRoutesOptions, VoiceProviderCapabilitySummary } from './providerCapabilities';
65
66
  export type { VoiceTurnLatencyHTMLHandlerOptions, VoiceTurnLatencyItem, VoiceTurnLatencyOptions, VoiceTurnLatencyReport, VoiceTurnLatencyRoutesOptions, VoiceTurnLatencyStage, VoiceTurnLatencyStatus } from './turnLatency';
67
+ export type { VoiceLiveLatencyOptions, VoiceLiveLatencyReport, VoiceLiveLatencyRoutesOptions, VoiceLiveLatencySample, VoiceLiveLatencyStatus } from './liveLatency';
66
68
  export type { VoiceTurnQualityHTMLHandlerOptions, VoiceTurnQualityItem, VoiceTurnQualityOptions, VoiceTurnQualityReport, VoiceTurnQualityRoutesOptions, VoiceTurnQualityStatus } from './turnQuality';
67
69
  export type { VoiceOutcomeContractDefinition, VoiceOutcomeContractHTMLHandlerOptions, VoiceOutcomeContractIssue, VoiceOutcomeContractOptions, VoiceOutcomeContractReport, VoiceOutcomeContractRoutesOptions, VoiceOutcomeContractStatus, VoiceOutcomeContractSuiteReport } from './outcomeContract';
68
70
  export type { VoiceTelephonyOutcomeAction, VoiceTelephonyOutcomeDecision, VoiceTelephonyOutcomePolicy, VoiceTelephonyOutcomeProviderEvent, VoiceTelephonyOutcomeRouteResult, VoiceTelephonyOutcomeStatusDecision, VoiceTelephonyWebhookDecision, VoiceTelephonyWebhookHandlerOptions, VoiceTelephonyWebhookIdempotencyStore, VoiceTelephonyWebhookParseInput, VoiceTelephonyWebhookProvider, VoiceTelephonyWebhookRoutesOptions, VoiceTelephonyWebhookVerificationResult, StoredVoiceTelephonyWebhookDecision } from './telephonyOutcome';
package/dist/index.js CHANGED
@@ -11407,10 +11407,82 @@ var createVoiceTurnLatencyRoutes = (options) => {
11407
11407
  }
11408
11408
  return routes;
11409
11409
  };
11410
- // src/turnQuality.ts
11410
+ // src/liveLatency.ts
11411
11411
  import { Elysia as Elysia19 } from "elysia";
11412
- var DEFAULT_CONFIDENCE_WARN_THRESHOLD = 0.72;
11413
11412
  var escapeHtml20 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
11413
+ var percentile = (values, percentileValue) => {
11414
+ if (values.length === 0) {
11415
+ return;
11416
+ }
11417
+ const sorted = [...values].sort((left, right) => left - right);
11418
+ const index = Math.min(sorted.length - 1, Math.max(0, Math.ceil(percentileValue / 100 * sorted.length) - 1));
11419
+ return Math.round(sorted[index] ?? 0);
11420
+ };
11421
+ var getLatency = (payload) => typeof payload.latencyMs === "number" ? payload.latencyMs : typeof payload.elapsedMs === "number" ? payload.elapsedMs : undefined;
11422
+ var summarizeVoiceLiveLatency = async (options) => {
11423
+ const warnAfterMs = options.warnAfterMs ?? 1800;
11424
+ const failAfterMs = options.failAfterMs ?? 3200;
11425
+ const events = await options.store.list({
11426
+ limit: options.limit ?? 100,
11427
+ type: "client.live_latency"
11428
+ });
11429
+ const recent = events.map((event) => {
11430
+ const latencyMs = getLatency(event.payload);
11431
+ if (latencyMs === undefined) {
11432
+ return;
11433
+ }
11434
+ return {
11435
+ at: event.at,
11436
+ latencyMs,
11437
+ sessionId: event.sessionId,
11438
+ status: typeof event.payload.status === "string" ? event.payload.status : undefined,
11439
+ traceId: event.traceId
11440
+ };
11441
+ }).filter((sample) => Boolean(sample)).sort((left, right) => right.at - left.at);
11442
+ const latencies = recent.map((sample) => sample.latencyMs);
11443
+ const failed = latencies.filter((value) => value > failAfterMs).length;
11444
+ const warnings = latencies.filter((value) => value > warnAfterMs && value <= failAfterMs).length;
11445
+ return {
11446
+ averageLatencyMs: latencies.length > 0 ? Math.round(latencies.reduce((total, value) => total + value, 0) / latencies.length) : undefined,
11447
+ checkedAt: Date.now(),
11448
+ failed,
11449
+ p50LatencyMs: percentile(latencies, 50),
11450
+ p95LatencyMs: percentile(latencies, 95),
11451
+ recent,
11452
+ status: latencies.length === 0 ? "empty" : failed > 0 ? "fail" : warnings > 0 ? "warn" : "pass",
11453
+ total: latencies.length,
11454
+ warnings
11455
+ };
11456
+ };
11457
+ var formatMs3 = (value) => typeof value === "number" ? `${Math.round(value)}ms` : "n/a";
11458
+ var renderVoiceLiveLatencyHTML = (report, options = {}) => {
11459
+ const title = options.title ?? "Voice Live Latency";
11460
+ const rows = report.recent.map((sample) => `<tr><td>${escapeHtml20(sample.sessionId)}</td><td>${escapeHtml20(formatMs3(sample.latencyMs))}</td><td>${escapeHtml20(sample.status ?? "unknown")}</td><td>${escapeHtml20(new Date(sample.at).toLocaleString())}</td></tr>`).join("");
11461
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml20(title)}</title><style>body{background:#0c0f14;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1060px;padding:32px}.hero{background:linear-gradient(135deg,rgba(94,234,212,.16),rgba(245,158,11,.1));border:1px solid #26313d;border-radius:28px;margin-bottom:18px;padding:28px}.eyebrow{color:#5eead4;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,5rem);line-height:.9;margin:.2rem 0 1rem}.status{border:1px solid #3f3f46;border-radius:999px;display:inline-flex;padding:8px 12px}.pass{color:#86efac}.warn,.empty{color:#fbbf24}.fail{color:#fca5a5}.metrics{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));margin:18px 0}.metrics article,table{background:#141922;border:1px solid #26313d;border-radius:18px}.metrics article{padding:16px}.metrics span{color:#a8b0b8}.metrics strong{display:block;font-size:2rem;margin-top:.25rem}table{border-collapse:collapse;overflow:hidden;width:100%}td,th{border-bottom:1px solid #26313d;padding:12px;text-align:left}@media(max-width:760px){main{padding:20px}}</style></head><body><main><section class="hero"><p class="eyebrow">Browser proof</p><h1>${escapeHtml20(title)}</h1><p>Recent real browser speech-to-assistant response measurements from persisted <code>client.live_latency</code> traces.</p><p class="status ${escapeHtml20(report.status)}">Status: ${escapeHtml20(report.status)}</p><section class="metrics"><article><span>p50</span><strong>${escapeHtml20(formatMs3(report.p50LatencyMs))}</strong></article><article><span>p95</span><strong>${escapeHtml20(formatMs3(report.p95LatencyMs))}</strong></article><article><span>Average</span><strong>${escapeHtml20(formatMs3(report.averageLatencyMs))}</strong></article><article><span>Samples</span><strong>${String(report.total)}</strong></article></section></section><table><thead><tr><th>Session</th><th>Latency</th><th>Status</th><th>Measured</th></tr></thead><tbody>${rows || '<tr><td colspan="4">No live latency samples yet.</td></tr>'}</tbody></table></main></body></html>`;
11462
+ };
11463
+ var createVoiceLiveLatencyRoutes = (options) => {
11464
+ const path = options.path ?? "/api/live-latency";
11465
+ const htmlPath = options.htmlPath === undefined ? "/live-latency" : options.htmlPath;
11466
+ const routes = new Elysia19({
11467
+ name: options.name ?? "absolutejs-voice-live-latency"
11468
+ }).get(path, () => summarizeVoiceLiveLatency(options));
11469
+ if (htmlPath) {
11470
+ routes.get(htmlPath, async () => {
11471
+ const report = await summarizeVoiceLiveLatency(options);
11472
+ return new Response(renderVoiceLiveLatencyHTML(report, options), {
11473
+ headers: {
11474
+ "content-type": "text/html; charset=utf-8",
11475
+ ...options.headers
11476
+ }
11477
+ });
11478
+ });
11479
+ }
11480
+ return routes;
11481
+ };
11482
+ // src/turnQuality.ts
11483
+ import { Elysia as Elysia20 } from "elysia";
11484
+ var DEFAULT_CONFIDENCE_WARN_THRESHOLD = 0.72;
11485
+ var escapeHtml21 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
11414
11486
  var getTurnLatencyMs = (turn) => {
11415
11487
  const firstTranscriptAt = turn.transcripts.map((transcript) => transcript.endedAtMs ?? transcript.startedAtMs).filter((value) => typeof value === "number").sort((left, right) => left - right)[0];
11416
11488
  if (firstTranscriptAt === undefined) {
@@ -11481,24 +11553,24 @@ var summarizeVoiceTurnQuality = async (options) => {
11481
11553
  };
11482
11554
  var renderVoiceTurnQualityHTML = (report, options = {}) => {
11483
11555
  const title = options.title ?? "Voice Turn Quality";
11484
- const turns = report.turns.map((turn) => `<article class="turn ${escapeHtml20(turn.status)}">
11556
+ const turns = report.turns.map((turn) => `<article class="turn ${escapeHtml21(turn.status)}">
11485
11557
  <div class="turn-header">
11486
11558
  <div>
11487
- <p class="eyebrow">${escapeHtml20(turn.sessionId)} \xB7 ${escapeHtml20(turn.turnId)}</p>
11488
- <h2>${escapeHtml20(turn.text || "Empty turn")}</h2>
11559
+ <p class="eyebrow">${escapeHtml21(turn.sessionId)} \xB7 ${escapeHtml21(turn.turnId)}</p>
11560
+ <h2>${escapeHtml21(turn.text || "Empty turn")}</h2>
11489
11561
  </div>
11490
- <strong>${escapeHtml20(turn.status)}</strong>
11562
+ <strong>${escapeHtml21(turn.status)}</strong>
11491
11563
  </div>
11492
11564
  <dl>
11493
- <div><dt>Source</dt><dd>${escapeHtml20(turn.source ?? "unknown")}</dd></div>
11565
+ <div><dt>Source</dt><dd>${escapeHtml21(turn.source ?? "unknown")}</dd></div>
11494
11566
  <div><dt>Confidence</dt><dd>${turn.averageConfidence === undefined ? "n/a" : `${Math.round(turn.averageConfidence * 100)}%`}</dd></div>
11495
- <div><dt>Fallback</dt><dd>${turn.fallbackUsed ? `yes (${escapeHtml20(turn.fallbackSelectionReason ?? "selected")})` : "no"}</dd></div>
11496
- <div><dt>Correction</dt><dd>${turn.correctionChanged ? `changed${turn.correctionProvider ? ` by ${escapeHtml20(turn.correctionProvider)}` : ""}` : "none"}</dd></div>
11567
+ <div><dt>Fallback</dt><dd>${turn.fallbackUsed ? `yes (${escapeHtml21(turn.fallbackSelectionReason ?? "selected")})` : "no"}</dd></div>
11568
+ <div><dt>Correction</dt><dd>${turn.correctionChanged ? `changed${turn.correctionProvider ? ` by ${escapeHtml21(turn.correctionProvider)}` : ""}` : "none"}</dd></div>
11497
11569
  <div><dt>Transcripts</dt><dd>${String(turn.selectedTranscriptCount)} selected \xB7 ${String(turn.finalTranscriptCount)} final \xB7 ${String(turn.partialTranscriptCount)} partial</dd></div>
11498
11570
  <div><dt>Cost</dt><dd>${turn.costUnits === undefined ? "n/a" : String(turn.costUnits)}</dd></div>
11499
11571
  </dl>
11500
11572
  </article>`).join("");
11501
- return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml20(title)}</title><style>body{background:#101316;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}.hero,.turn{background:#181d22;border:1px solid #2a323a;border-radius:20px;margin-bottom:16px;padding:20px}.hero{background:linear-gradient(135deg,rgba(251,191,36,.16),rgba(34,197,94,.1))}.eyebrow{color:#fbbf24;font-size:.78rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}h1{font-size:clamp(2.3rem,6vw,5rem);letter-spacing:-.06em;line-height:.9;margin:.2rem 0 1rem}h2{margin:.2rem 0 1rem}.summary{display:flex;flex-wrap:wrap;gap:10px}.pill{background:#0f1217;border:1px solid #3f3f46;border-radius:999px;padding:7px 10px}.turn-header{align-items:flex-start;display:flex;gap:16px;justify-content:space-between}.pass{color:#86efac}.warn,.unknown{color:#fde68a}.fail{color:#fca5a5}.turn.fail{border-color:rgba(248,113,113,.45)}dl{display:grid;gap:8px;grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}dt{color:#a8b0b8;font-size:.8rem}dd{margin:0}@media(max-width:800px){main{padding:18px}.turn-header{display:block}}</style></head><body><main><section class="hero"><p class="eyebrow">Realtime STT Debugging</p><h1>${escapeHtml20(title)}</h1><div class="summary"><span class="pill ${escapeHtml20(report.status)}">${escapeHtml20(report.status)}</span><span class="pill">${String(report.total)} turns</span><span class="pill">${String(report.warnings)} warnings</span><span class="pill">${String(report.failed)} failed</span><span class="pill">${String(report.sessions)} sessions</span></div></section>${turns || '<section class="turn"><p>No committed turns found.</p></section>'}</main></body></html>`;
11573
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml21(title)}</title><style>body{background:#101316;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}.hero,.turn{background:#181d22;border:1px solid #2a323a;border-radius:20px;margin-bottom:16px;padding:20px}.hero{background:linear-gradient(135deg,rgba(251,191,36,.16),rgba(34,197,94,.1))}.eyebrow{color:#fbbf24;font-size:.78rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}h1{font-size:clamp(2.3rem,6vw,5rem);letter-spacing:-.06em;line-height:.9;margin:.2rem 0 1rem}h2{margin:.2rem 0 1rem}.summary{display:flex;flex-wrap:wrap;gap:10px}.pill{background:#0f1217;border:1px solid #3f3f46;border-radius:999px;padding:7px 10px}.turn-header{align-items:flex-start;display:flex;gap:16px;justify-content:space-between}.pass{color:#86efac}.warn,.unknown{color:#fde68a}.fail{color:#fca5a5}.turn.fail{border-color:rgba(248,113,113,.45)}dl{display:grid;gap:8px;grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}dt{color:#a8b0b8;font-size:.8rem}dd{margin:0}@media(max-width:800px){main{padding:18px}.turn-header{display:block}}</style></head><body><main><section class="hero"><p class="eyebrow">Realtime STT Debugging</p><h1>${escapeHtml21(title)}</h1><div class="summary"><span class="pill ${escapeHtml21(report.status)}">${escapeHtml21(report.status)}</span><span class="pill">${String(report.total)} turns</span><span class="pill">${String(report.warnings)} warnings</span><span class="pill">${String(report.failed)} failed</span><span class="pill">${String(report.sessions)} sessions</span></div></section>${turns || '<section class="turn"><p>No committed turns found.</p></section>'}</main></body></html>`;
11502
11574
  };
11503
11575
  var createVoiceTurnQualityJSONHandler = (options) => async () => summarizeVoiceTurnQuality(options);
11504
11576
  var createVoiceTurnQualityHTMLHandler = (options) => async () => {
@@ -11515,7 +11587,7 @@ var createVoiceTurnQualityHTMLHandler = (options) => async () => {
11515
11587
  var createVoiceTurnQualityRoutes = (options) => {
11516
11588
  const path = options.path ?? "/api/turn-quality";
11517
11589
  const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
11518
- const routes = new Elysia19({
11590
+ const routes = new Elysia20({
11519
11591
  name: options.name ?? "absolutejs-voice-turn-quality"
11520
11592
  }).get(path, createVoiceTurnQualityJSONHandler(options));
11521
11593
  if (htmlPath) {
@@ -11524,8 +11596,8 @@ var createVoiceTurnQualityRoutes = (options) => {
11524
11596
  return routes;
11525
11597
  };
11526
11598
  // src/outcomeContract.ts
11527
- import { Elysia as Elysia20 } from "elysia";
11528
- var escapeHtml21 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
11599
+ import { Elysia as Elysia21 } from "elysia";
11600
+ var escapeHtml22 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
11529
11601
  var getPayloadString = (event, key) => typeof event.payload[key] === "string" ? event.payload[key] : undefined;
11530
11602
  var toList = async (input) => Array.isArray(input) ? input : await input?.list() ?? [];
11531
11603
  var hydrateSessions = async (input) => {
@@ -11633,9 +11705,9 @@ var renderVoiceOutcomeContractHTML = (report, options = {}) => {
11633
11705
  const contracts = report.contracts.map((contract) => `<section class="contract ${contract.pass ? "pass" : "fail"}">
11634
11706
  <div class="contract-header">
11635
11707
  <div>
11636
- <p class="eyebrow">${escapeHtml21(contract.contractId)}</p>
11637
- <h2>${escapeHtml21(contract.label ?? contract.contractId)}</h2>
11638
- ${contract.description ? `<p>${escapeHtml21(contract.description)}</p>` : ""}
11708
+ <p class="eyebrow">${escapeHtml22(contract.contractId)}</p>
11709
+ <h2>${escapeHtml22(contract.label ?? contract.contractId)}</h2>
11710
+ ${contract.description ? `<p>${escapeHtml22(contract.description)}</p>` : ""}
11639
11711
  </div>
11640
11712
  <strong>${contract.pass ? "pass" : "fail"}</strong>
11641
11713
  </div>
@@ -11646,9 +11718,9 @@ var renderVoiceOutcomeContractHTML = (report, options = {}) => {
11646
11718
  <span>handoffs ${String(contract.matched.handoffs)}</span>
11647
11719
  <span>events ${String(contract.matched.integrationEvents)}</span>
11648
11720
  </div>
11649
- ${contract.issues.length ? `<ul>${contract.issues.map((issue) => `<li>${escapeHtml21(issue.message)}</li>`).join("")}</ul>` : ""}
11721
+ ${contract.issues.length ? `<ul>${contract.issues.map((issue) => `<li>${escapeHtml22(issue.message)}</li>`).join("")}</ul>` : ""}
11650
11722
  </section>`).join("");
11651
- return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml21(title)}</title><style>body{background:#101316;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}.hero,.contract{background:#181d22;border:1px solid #2a323a;border-radius:20px;margin-bottom:16px;padding:20px}.hero{background:linear-gradient(135deg,rgba(34,197,94,.14),rgba(14,165,233,.12))}.eyebrow{color:#7dd3fc;font-size:.78rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}h1{font-size:clamp(2.3rem,6vw,5rem);letter-spacing:-.06em;line-height:.9;margin:.2rem 0 1rem}h2{margin:.2rem 0}.summary,.grid{display:flex;flex-wrap:wrap;gap:10px}.pill,.grid span{background:#0f1217;border:1px solid #3f3f46;border-radius:999px;padding:7px 10px}.contract-header{display:flex;gap:16px;justify-content:space-between}.pass{color:#86efac}.fail{color:#fca5a5}.contract.fail{border-color:rgba(248,113,113,.45)}li{margin:8px 0}@media(max-width:800px){main{padding:18px}.contract-header{display:block}}</style></head><body><main><section class="hero"><p class="eyebrow">Business Outcome Verification</p><h1>${escapeHtml21(title)}</h1><div class="summary"><span class="pill ${report.status}">${report.status}</span><span class="pill">${String(report.passed)} passing</span><span class="pill">${String(report.failed)} failing</span><span class="pill">${String(report.total)} contracts</span></div></section>${contracts || '<section class="contract"><p>No outcome contracts configured.</p></section>'}</main></body></html>`;
11723
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml22(title)}</title><style>body{background:#101316;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}.hero,.contract{background:#181d22;border:1px solid #2a323a;border-radius:20px;margin-bottom:16px;padding:20px}.hero{background:linear-gradient(135deg,rgba(34,197,94,.14),rgba(14,165,233,.12))}.eyebrow{color:#7dd3fc;font-size:.78rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}h1{font-size:clamp(2.3rem,6vw,5rem);letter-spacing:-.06em;line-height:.9;margin:.2rem 0 1rem}h2{margin:.2rem 0}.summary,.grid{display:flex;flex-wrap:wrap;gap:10px}.pill,.grid span{background:#0f1217;border:1px solid #3f3f46;border-radius:999px;padding:7px 10px}.contract-header{display:flex;gap:16px;justify-content:space-between}.pass{color:#86efac}.fail{color:#fca5a5}.contract.fail{border-color:rgba(248,113,113,.45)}li{margin:8px 0}@media(max-width:800px){main{padding:18px}.contract-header{display:block}}</style></head><body><main><section class="hero"><p class="eyebrow">Business Outcome Verification</p><h1>${escapeHtml22(title)}</h1><div class="summary"><span class="pill ${report.status}">${report.status}</span><span class="pill">${String(report.passed)} passing</span><span class="pill">${String(report.failed)} failing</span><span class="pill">${String(report.total)} contracts</span></div></section>${contracts || '<section class="contract"><p>No outcome contracts configured.</p></section>'}</main></body></html>`;
11652
11724
  };
11653
11725
  var createVoiceOutcomeContractJSONHandler = (options) => async () => runVoiceOutcomeContractSuite(options);
11654
11726
  var createVoiceOutcomeContractHTMLHandler = (options) => async () => {
@@ -11664,7 +11736,7 @@ var createVoiceOutcomeContractHTMLHandler = (options) => async () => {
11664
11736
  var createVoiceOutcomeContractRoutes = (options) => {
11665
11737
  const path = options.path ?? "/api/outcome-contracts";
11666
11738
  const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
11667
- const routes = new Elysia20({
11739
+ const routes = new Elysia21({
11668
11740
  name: options.name ?? "absolutejs-voice-outcome-contracts"
11669
11741
  }).get(path, createVoiceOutcomeContractJSONHandler(options));
11670
11742
  if (htmlPath) {
@@ -11673,7 +11745,7 @@ var createVoiceOutcomeContractRoutes = (options) => {
11673
11745
  return routes;
11674
11746
  };
11675
11747
  // src/telephonyOutcome.ts
11676
- import { Elysia as Elysia21 } from "elysia";
11748
+ import { Elysia as Elysia22 } from "elysia";
11677
11749
  var DEFAULT_COMPLETED_STATUSES = [
11678
11750
  "answered",
11679
11751
  "completed",
@@ -12320,7 +12392,7 @@ var createVoiceTelephonyWebhookHandler = (options = {}) => async (input) => {
12320
12392
  var createVoiceTelephonyWebhookRoutes = (options = {}) => {
12321
12393
  const path = options.path ?? "/api/voice/telephony/webhook";
12322
12394
  const handler = createVoiceTelephonyWebhookHandler(options);
12323
- return new Elysia21({
12395
+ return new Elysia22({
12324
12396
  name: options.name ?? "absolutejs-voice-telephony-webhooks"
12325
12397
  }).post(path, async ({ query, request }) => {
12326
12398
  try {
@@ -14588,7 +14660,7 @@ var createVoiceMemoryStore = () => {
14588
14660
  return { get, getOrCreate, list, remove, set };
14589
14661
  };
14590
14662
  // src/opsWebhook.ts
14591
- import { Elysia as Elysia22 } from "elysia";
14663
+ import { Elysia as Elysia23 } from "elysia";
14592
14664
  var toHex5 = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
14593
14665
  var signVoiceOpsWebhookBody = async (input) => {
14594
14666
  const encoder = new TextEncoder;
@@ -14718,7 +14790,7 @@ var verifyVoiceOpsWebhookSignature = async (input) => {
14718
14790
  };
14719
14791
  var createVoiceOpsWebhookReceiverRoutes = (options = {}) => {
14720
14792
  const path = options.path ?? "/api/voice-ops/webhook";
14721
- return new Elysia22().post(path, async ({ body, request, set }) => {
14793
+ return new Elysia23().post(path, async ({ body, request, set }) => {
14722
14794
  const bodyText = typeof body === "string" ? body : JSON.stringify(body);
14723
14795
  if (options.signingSecret) {
14724
14796
  const verification = await verifyVoiceOpsWebhookSignature({
@@ -16447,7 +16519,7 @@ var createVoiceSTTRoutingCorrectionHandler = (mode = "generic") => {
16447
16519
  };
16448
16520
  // src/telephony/twilio.ts
16449
16521
  import { Buffer as Buffer3 } from "buffer";
16450
- import { Elysia as Elysia23 } from "elysia";
16522
+ import { Elysia as Elysia24 } from "elysia";
16451
16523
  var TWILIO_MULAW_SAMPLE_RATE = 8000;
16452
16524
  var VOICE_PCM_SAMPLE_RATE = 16000;
16453
16525
  var escapeXml2 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
@@ -16477,7 +16549,7 @@ var resolveTwilioStreamParameters = async (parameters, input) => {
16477
16549
  return parameters;
16478
16550
  };
16479
16551
  var joinUrlPath = (origin, path) => `${origin.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
16480
- var escapeHtml22 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&#39;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
16552
+ var escapeHtml23 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&#39;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
16481
16553
  var getWebhookVerificationUrl = (webhook, input) => {
16482
16554
  if (!webhook?.verificationUrl) {
16483
16555
  return;
@@ -16520,23 +16592,23 @@ var buildTwilioVoiceSetupStatus = async (options, input) => {
16520
16592
  };
16521
16593
  var renderTwilioVoiceSetupHTML = (status, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
16522
16594
  <p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Twilio setup</p>
16523
- <h1>${escapeHtml22(title)}</h1>
16595
+ <h1>${escapeHtml23(title)}</h1>
16524
16596
  <p><strong>Status:</strong> ${status.ready ? "Ready" : "Needs attention"}</p>
16525
16597
  <section>
16526
16598
  <h2>URLs</h2>
16527
16599
  <ul>
16528
- <li><strong>TwiML:</strong> <code>${escapeHtml22(status.urls.twiml)}</code></li>
16529
- <li><strong>Media stream:</strong> <code>${escapeHtml22(status.urls.stream)}</code></li>
16530
- <li><strong>Status webhook:</strong> <code>${escapeHtml22(status.urls.webhook)}</code></li>
16600
+ <li><strong>TwiML:</strong> <code>${escapeHtml23(status.urls.twiml)}</code></li>
16601
+ <li><strong>Media stream:</strong> <code>${escapeHtml23(status.urls.stream)}</code></li>
16602
+ <li><strong>Status webhook:</strong> <code>${escapeHtml23(status.urls.webhook)}</code></li>
16531
16603
  </ul>
16532
16604
  </section>
16533
16605
  <section>
16534
16606
  <h2>Signing</h2>
16535
16607
  <p>Mode: <code>${status.signing.mode}</code></p>
16536
- ${status.signing.verificationUrl ? `<p>Verification URL: <code>${escapeHtml22(status.signing.verificationUrl)}</code></p>` : ""}
16608
+ ${status.signing.verificationUrl ? `<p>Verification URL: <code>${escapeHtml23(status.signing.verificationUrl)}</code></p>` : ""}
16537
16609
  </section>
16538
- ${status.missing.length ? `<section><h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml22(name)}</code></li>`).join("")}</ul></section>` : ""}
16539
- ${status.warnings.length ? `<section><h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml22(warning)}</li>`).join("")}</ul></section>` : ""}
16610
+ ${status.missing.length ? `<section><h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml23(name)}</code></li>`).join("")}</ul></section>` : ""}
16611
+ ${status.warnings.length ? `<section><h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml23(warning)}</li>`).join("")}</ul></section>` : ""}
16540
16612
  </main>`;
16541
16613
  var extractTwilioStreamUrl = (twiml) => twiml.match(/<Stream\b[^>]*\surl="([^"]+)"/i)?.[1]?.replaceAll("&amp;", "&");
16542
16614
  var createSmokeCheck = (name, status, message, details) => ({
@@ -16547,20 +16619,20 @@ var createSmokeCheck = (name, status, message, details) => ({
16547
16619
  });
16548
16620
  var renderTwilioVoiceSmokeHTML = (report, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
16549
16621
  <p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Twilio smoke test</p>
16550
- <h1>${escapeHtml22(title)}</h1>
16622
+ <h1>${escapeHtml23(title)}</h1>
16551
16623
  <p><strong>Status:</strong> ${report.pass ? "Pass" : "Fail"}</p>
16552
16624
  <section>
16553
16625
  <h2>Checks</h2>
16554
16626
  <ul>
16555
- ${report.checks.map((check) => `<li><strong>${escapeHtml22(check.name)}</strong>: ${escapeHtml22(check.status)}${check.message ? ` - ${escapeHtml22(check.message)}` : ""}</li>`).join("")}
16627
+ ${report.checks.map((check) => `<li><strong>${escapeHtml23(check.name)}</strong>: ${escapeHtml23(check.status)}${check.message ? ` - ${escapeHtml23(check.message)}` : ""}</li>`).join("")}
16556
16628
  </ul>
16557
16629
  </section>
16558
16630
  <section>
16559
16631
  <h2>Observed URLs</h2>
16560
16632
  <ul>
16561
- <li><strong>TwiML:</strong> <code>${escapeHtml22(report.setup.urls.twiml)}</code></li>
16562
- <li><strong>Stream:</strong> <code>${escapeHtml22(report.twiml?.streamUrl ?? report.setup.urls.stream)}</code></li>
16563
- <li><strong>Webhook:</strong> <code>${escapeHtml22(report.setup.urls.webhook)}</code></li>
16633
+ <li><strong>TwiML:</strong> <code>${escapeHtml23(report.setup.urls.twiml)}</code></li>
16634
+ <li><strong>Stream:</strong> <code>${escapeHtml23(report.twiml?.streamUrl ?? report.setup.urls.stream)}</code></li>
16635
+ <li><strong>Webhook:</strong> <code>${escapeHtml23(report.setup.urls.webhook)}</code></li>
16564
16636
  </ul>
16565
16637
  </section>
16566
16638
  </main>`;
@@ -17020,7 +17092,7 @@ var createTwilioVoiceRoutes = (options) => {
17020
17092
  const smokePath = options.smoke?.path === false ? false : options.smoke?.path ?? "/api/voice/twilio/smoke";
17021
17093
  const bridges = new WeakMap;
17022
17094
  const webhookPolicy = options.webhook?.policy ?? options.outcomePolicy ?? createVoiceTelephonyOutcomePolicy();
17023
- const app = new Elysia23({
17095
+ const app = new Elysia24({
17024
17096
  name: options.name ?? "absolutejs-voice-twilio"
17025
17097
  }).get(twimlPath, async ({ query, request }) => {
17026
17098
  const streamUrl = await resolveTwilioStreamUrl(options, {
@@ -17156,9 +17228,9 @@ var createTwilioVoiceRoutes = (options) => {
17156
17228
  };
17157
17229
  // src/telephony/telnyx.ts
17158
17230
  import { Buffer as Buffer4 } from "buffer";
17159
- import { Elysia as Elysia24 } from "elysia";
17231
+ import { Elysia as Elysia25 } from "elysia";
17160
17232
  var escapeXml3 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
17161
- var escapeHtml23 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&#39;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
17233
+ var escapeHtml24 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&#39;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
17162
17234
  var joinUrlPath2 = (origin, path) => `${origin.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
17163
17235
  var resolveRequestOrigin2 = (request) => {
17164
17236
  const url = new URL(request.url);
@@ -17359,21 +17431,21 @@ var buildTelnyxVoiceSetupStatus = async (options, input) => {
17359
17431
  };
17360
17432
  var renderTelnyxSetupHTML = (status, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
17361
17433
  <p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Telnyx setup</p>
17362
- <h1>${escapeHtml23(title)}</h1>
17434
+ <h1>${escapeHtml24(title)}</h1>
17363
17435
  <p><strong>Status:</strong> ${status.ready ? "Ready" : "Needs attention"}</p>
17364
17436
  <ul>
17365
- <li><strong>TeXML:</strong> <code>${escapeHtml23(status.urls.texml)}</code></li>
17366
- <li><strong>Media stream:</strong> <code>${escapeHtml23(status.urls.stream)}</code></li>
17367
- <li><strong>Status webhook:</strong> <code>${escapeHtml23(status.urls.webhook)}</code></li>
17437
+ <li><strong>TeXML:</strong> <code>${escapeHtml24(status.urls.texml)}</code></li>
17438
+ <li><strong>Media stream:</strong> <code>${escapeHtml24(status.urls.stream)}</code></li>
17439
+ <li><strong>Status webhook:</strong> <code>${escapeHtml24(status.urls.webhook)}</code></li>
17368
17440
  </ul>
17369
- ${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml23(name)}</code></li>`).join("")}</ul>` : ""}
17370
- ${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml23(warning)}</li>`).join("")}</ul>` : ""}
17441
+ ${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml24(name)}</code></li>`).join("")}</ul>` : ""}
17442
+ ${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml24(warning)}</li>`).join("")}</ul>` : ""}
17371
17443
  </main>`;
17372
17444
  var renderTelnyxSmokeHTML = (report, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
17373
17445
  <p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Telnyx smoke test</p>
17374
- <h1>${escapeHtml23(title)}</h1>
17446
+ <h1>${escapeHtml24(title)}</h1>
17375
17447
  <p><strong>Status:</strong> ${report.pass ? "Pass" : "Fail"}</p>
17376
- <ul>${report.checks.map((check) => `<li><strong>${escapeHtml23(check.name)}</strong>: ${escapeHtml23(check.status)}${check.message ? ` - ${escapeHtml23(check.message)}` : ""}</li>`).join("")}</ul>
17448
+ <ul>${report.checks.map((check) => `<li><strong>${escapeHtml24(check.name)}</strong>: ${escapeHtml24(check.status)}${check.message ? ` - ${escapeHtml24(check.message)}` : ""}</li>`).join("")}</ul>
17377
17449
  </main>`;
17378
17450
  var runTelnyxSmokeTest = async (input) => {
17379
17451
  const setup = await buildTelnyxVoiceSetupStatus(input.options, input);
@@ -17467,7 +17539,7 @@ var createTelnyxVoiceRoutes = (options = {}) => {
17467
17539
  publicKey: options.webhook?.publicKey,
17468
17540
  toleranceSeconds: options.webhook?.toleranceSeconds
17469
17541
  }) : undefined);
17470
- const app = new Elysia24({
17542
+ const app = new Elysia25({
17471
17543
  name: options.name ?? "absolutejs-voice-telnyx"
17472
17544
  }).get(texmlPath, async ({ query, request }) => {
17473
17545
  const streamUrl = await resolveTelnyxStreamUrl(options, {
@@ -17577,9 +17649,9 @@ var createTelnyxVoiceRoutes = (options = {}) => {
17577
17649
  };
17578
17650
  // src/telephony/plivo.ts
17579
17651
  import { Buffer as Buffer5 } from "buffer";
17580
- import { Elysia as Elysia25 } from "elysia";
17652
+ import { Elysia as Elysia26 } from "elysia";
17581
17653
  var escapeXml4 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
17582
- var escapeHtml24 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&#39;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
17654
+ var escapeHtml25 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&#39;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
17583
17655
  var joinUrlPath3 = (origin, path) => `${origin.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
17584
17656
  var resolveRequestOrigin3 = (request) => {
17585
17657
  const url = new URL(request.url);
@@ -17830,21 +17902,21 @@ var buildPlivoVoiceSetupStatus = async (options, input) => {
17830
17902
  };
17831
17903
  var renderPlivoSetupHTML = (status, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
17832
17904
  <p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Plivo setup</p>
17833
- <h1>${escapeHtml24(title)}</h1>
17905
+ <h1>${escapeHtml25(title)}</h1>
17834
17906
  <p><strong>Status:</strong> ${status.ready ? "Ready" : "Needs attention"}</p>
17835
17907
  <ul>
17836
- <li><strong>Answer XML:</strong> <code>${escapeHtml24(status.urls.answer)}</code></li>
17837
- <li><strong>Audio stream:</strong> <code>${escapeHtml24(status.urls.stream)}</code></li>
17838
- <li><strong>Status webhook:</strong> <code>${escapeHtml24(status.urls.webhook)}</code></li>
17908
+ <li><strong>Answer XML:</strong> <code>${escapeHtml25(status.urls.answer)}</code></li>
17909
+ <li><strong>Audio stream:</strong> <code>${escapeHtml25(status.urls.stream)}</code></li>
17910
+ <li><strong>Status webhook:</strong> <code>${escapeHtml25(status.urls.webhook)}</code></li>
17839
17911
  </ul>
17840
- ${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml24(name)}</code></li>`).join("")}</ul>` : ""}
17841
- ${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml24(warning)}</li>`).join("")}</ul>` : ""}
17912
+ ${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml25(name)}</code></li>`).join("")}</ul>` : ""}
17913
+ ${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml25(warning)}</li>`).join("")}</ul>` : ""}
17842
17914
  </main>`;
17843
17915
  var renderPlivoSmokeHTML = (report, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
17844
17916
  <p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Plivo smoke test</p>
17845
- <h1>${escapeHtml24(title)}</h1>
17917
+ <h1>${escapeHtml25(title)}</h1>
17846
17918
  <p><strong>Status:</strong> ${report.pass ? "Pass" : "Fail"}</p>
17847
- <ul>${report.checks.map((check) => `<li><strong>${escapeHtml24(check.name)}</strong>: ${escapeHtml24(check.status)}${check.message ? ` - ${escapeHtml24(check.message)}` : ""}</li>`).join("")}</ul>
17919
+ <ul>${report.checks.map((check) => `<li><strong>${escapeHtml25(check.name)}</strong>: ${escapeHtml25(check.status)}${check.message ? ` - ${escapeHtml25(check.message)}` : ""}</li>`).join("")}</ul>
17848
17920
  </main>`;
17849
17921
  var runPlivoSmokeTest = async (input) => {
17850
17922
  const setup = await buildPlivoVoiceSetupStatus(input.options, input);
@@ -17939,7 +18011,7 @@ var createPlivoVoiceRoutes = (options = {}) => {
17939
18011
  request: input.request
17940
18012
  }) : verificationUrl ?? input.request.url
17941
18013
  }) : undefined);
17942
- const app = new Elysia25({
18014
+ const app = new Elysia26({
17943
18015
  name: options.name ?? "absolutejs-voice-plivo"
17944
18016
  }).get(answerPath, async ({ query, request }) => {
17945
18017
  const streamUrl = await resolvePlivoStreamUrl(options, {
@@ -18125,6 +18197,7 @@ export {
18125
18197
  summarizeVoiceOpsTasks,
18126
18198
  summarizeVoiceOpsTaskQueue,
18127
18199
  summarizeVoiceOpsTaskAnalytics,
18200
+ summarizeVoiceLiveLatency,
18128
18201
  summarizeVoiceIntegrationEvents,
18129
18202
  summarizeVoiceHandoffHealth,
18130
18203
  summarizeVoiceHandoffDeliveries,
@@ -18177,6 +18250,7 @@ export {
18177
18250
  renderVoiceProductionReadinessHTML,
18178
18251
  renderVoiceOutcomeContractHTML,
18179
18252
  renderVoiceOpsConsoleHTML,
18253
+ renderVoiceLiveLatencyHTML,
18180
18254
  renderVoiceHandoffHealthHTML,
18181
18255
  renderVoiceEvalHTML,
18182
18256
  renderVoiceEvalBaselineHTML,
@@ -18314,6 +18388,7 @@ export {
18314
18388
  createVoiceMemoryStore,
18315
18389
  createVoiceMemoryHandoffDeliveryStore,
18316
18390
  createVoiceMemoryAssistantMemoryStore,
18391
+ createVoiceLiveLatencyRoutes,
18317
18392
  createVoiceLinearIssueUpdateSink,
18318
18393
  createVoiceLinearIssueSyncSinks,
18319
18394
  createVoiceLinearIssueSink,
@@ -0,0 +1,78 @@
1
+ import { Elysia } from 'elysia';
2
+ import type { VoiceTraceEventStore } from './trace';
3
+ export type VoiceLiveLatencyStatus = 'empty' | 'fail' | 'pass' | 'warn';
4
+ export type VoiceLiveLatencySample = {
5
+ at: number;
6
+ latencyMs: number;
7
+ sessionId: string;
8
+ status?: string;
9
+ traceId?: string;
10
+ };
11
+ export type VoiceLiveLatencyReport = {
12
+ averageLatencyMs?: number;
13
+ checkedAt: number;
14
+ failed: number;
15
+ p50LatencyMs?: number;
16
+ p95LatencyMs?: number;
17
+ recent: VoiceLiveLatencySample[];
18
+ status: VoiceLiveLatencyStatus;
19
+ total: number;
20
+ warnings: number;
21
+ };
22
+ export type VoiceLiveLatencyOptions = {
23
+ failAfterMs?: number;
24
+ limit?: number;
25
+ store: VoiceTraceEventStore;
26
+ warnAfterMs?: number;
27
+ };
28
+ export type VoiceLiveLatencyRoutesOptions = VoiceLiveLatencyOptions & {
29
+ headers?: HeadersInit;
30
+ htmlPath?: false | string;
31
+ name?: string;
32
+ path?: string;
33
+ title?: string;
34
+ };
35
+ export declare const summarizeVoiceLiveLatency: (options: VoiceLiveLatencyOptions) => Promise<VoiceLiveLatencyReport>;
36
+ export declare const renderVoiceLiveLatencyHTML: (report: VoiceLiveLatencyReport, options?: {
37
+ title?: string;
38
+ }) => string;
39
+ export declare const createVoiceLiveLatencyRoutes: (options: VoiceLiveLatencyRoutesOptions) => Elysia<"", {
40
+ decorator: {};
41
+ store: {};
42
+ derive: {};
43
+ resolve: {};
44
+ }, {
45
+ typebox: {};
46
+ error: {};
47
+ }, {
48
+ schema: {};
49
+ standaloneSchema: {};
50
+ macro: {};
51
+ macroFn: {};
52
+ parser: {};
53
+ response: {};
54
+ }, {
55
+ [x: string]: {
56
+ get: {
57
+ body: unknown;
58
+ params: {};
59
+ query: unknown;
60
+ headers: unknown;
61
+ response: {
62
+ 200: VoiceLiveLatencyReport;
63
+ };
64
+ };
65
+ };
66
+ }, {
67
+ derive: {};
68
+ resolve: {};
69
+ schema: {};
70
+ standaloneSchema: {};
71
+ response: {};
72
+ }, {
73
+ derive: {};
74
+ resolve: {};
75
+ schema: {};
76
+ standaloneSchema: {};
77
+ response: {};
78
+ }>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.102",
3
+ "version": "0.0.22-beta.103",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",