@absolutejs/voice 0.0.22-beta.94 → 0.0.22-beta.95

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.js CHANGED
@@ -11168,10 +11168,113 @@ var createVoiceToolContractRoutes = (options) => {
11168
11168
  }
11169
11169
  return routes;
11170
11170
  };
11171
- // src/turnQuality.ts
11171
+ // src/turnLatency.ts
11172
11172
  import { Elysia as Elysia18 } from "elysia";
11173
- var DEFAULT_CONFIDENCE_WARN_THRESHOLD = 0.72;
11173
+ var DEFAULT_WARN_AFTER_MS = 1800;
11174
+ var DEFAULT_FAIL_AFTER_MS = 3200;
11174
11175
  var escapeHtml19 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
11176
+ var firstNumber2 = (values) => values.filter((value) => typeof value === "number").sort((left, right) => left - right)[0];
11177
+ var summarizeTurn = (sessionId, turn, options) => {
11178
+ const firstTranscriptAt = firstNumber2(turn.transcripts.map((transcript) => transcript.endedAtMs ?? transcript.startedAtMs));
11179
+ const finalTranscriptAt = firstNumber2(turn.transcripts.filter((transcript) => transcript.isFinal).map((transcript) => transcript.endedAtMs ?? transcript.startedAtMs));
11180
+ const commitAfterFirstMs = firstTranscriptAt === undefined ? undefined : Math.max(0, turn.committedAt - firstTranscriptAt);
11181
+ const commitAfterFinalMs = finalTranscriptAt === undefined ? undefined : Math.max(0, turn.committedAt - finalTranscriptAt);
11182
+ const assistantTextStartedAt = turn.assistantText ? turn.committedAt : undefined;
11183
+ const totalMs = commitAfterFirstMs;
11184
+ const status = totalMs === undefined ? "warn" : totalMs > options.failAfterMs ? "fail" : totalMs > options.warnAfterMs ? "warn" : "pass";
11185
+ return {
11186
+ assistantTextStartedAt,
11187
+ committedAt: turn.committedAt,
11188
+ finalTranscriptAt,
11189
+ firstTranscriptAt,
11190
+ sessionId,
11191
+ stages: [
11192
+ { label: "Speech to commit", valueMs: commitAfterFirstMs },
11193
+ { label: "Final transcript to commit", valueMs: commitAfterFinalMs },
11194
+ {
11195
+ label: "Commit to assistant text",
11196
+ valueMs: assistantTextStartedAt === undefined ? undefined : 0
11197
+ }
11198
+ ],
11199
+ status,
11200
+ text: turn.text,
11201
+ totalMs,
11202
+ turnId: turn.id
11203
+ };
11204
+ };
11205
+ var resolveSessions = async (options) => {
11206
+ if (options.sessions) {
11207
+ return options.sessions;
11208
+ }
11209
+ if (!options.store) {
11210
+ return [];
11211
+ }
11212
+ const summaries = await options.store.list();
11213
+ const ids = options.sessionIds ?? summaries.map((summary) => summary.id);
11214
+ const hydrated = await Promise.all(ids.slice(0, options.limit ?? 25).map((id) => options.store?.get(id)));
11215
+ const sessions = [];
11216
+ for (const session of hydrated) {
11217
+ if (session) {
11218
+ sessions.push(session);
11219
+ }
11220
+ }
11221
+ return sessions;
11222
+ };
11223
+ var summarizeVoiceTurnLatency = async (options) => {
11224
+ const sessions = await resolveSessions(options);
11225
+ const warnAfterMs = options.warnAfterMs ?? DEFAULT_WARN_AFTER_MS;
11226
+ const failAfterMs = options.failAfterMs ?? DEFAULT_FAIL_AFTER_MS;
11227
+ const turns = sessions.flatMap((session) => session.turns.map((turn) => summarizeTurn(session.id, turn, { failAfterMs, warnAfterMs }))).sort((left, right) => right.committedAt - left.committedAt);
11228
+ const totals = turns.map((turn) => turn.totalMs).filter((value) => typeof value === "number");
11229
+ const failed = turns.filter((turn) => turn.status === "fail").length;
11230
+ const warnings = turns.filter((turn) => turn.status === "warn").length;
11231
+ return {
11232
+ averageTotalMs: totals.length > 0 ? Math.round(totals.reduce((total, value) => total + value, 0) / totals.length) : undefined,
11233
+ checkedAt: Date.now(),
11234
+ failed,
11235
+ sessions: sessions.length,
11236
+ status: turns.length === 0 ? "empty" : failed > 0 ? "fail" : warnings > 0 ? "warn" : "pass",
11237
+ total: turns.length,
11238
+ turns,
11239
+ warnings
11240
+ };
11241
+ };
11242
+ var formatMs2 = (value) => typeof value === "number" ? `${Math.round(value)}ms` : "n/a";
11243
+ var renderVoiceTurnLatencyHTML = (report, options = {}) => {
11244
+ const title = options.title ?? "Voice Turn Latency";
11245
+ const turns = report.turns.map((turn) => `<article class="turn ${escapeHtml19(turn.status)}">
11246
+ <header><div><p class="eyebrow">${escapeHtml19(turn.sessionId)} \xB7 ${escapeHtml19(turn.turnId)}</p><h2>${escapeHtml19(turn.text || "Empty turn")}</h2></div><strong>${escapeHtml19(turn.status)}</strong></header>
11247
+ <dl>${turn.stages.map((stage) => `<div><dt>${escapeHtml19(stage.label)}</dt><dd>${escapeHtml19(formatMs2(stage.valueMs))}</dd></div>`).join("")}</dl>
11248
+ </article>`).join("");
11249
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml19(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(94,234,212,.16),rgba(251,191,36,.1))}.eyebrow{color:#5eead4;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,.empty{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{font-weight:900;margin:0}@media(max-width:800px){main{padding:18px}.turn header{display:block}}</style></head><body><main><section class="hero"><p class="eyebrow">End-to-end responsiveness</p><h1>${escapeHtml19(title)}</h1><div class="summary"><span class="pill ${escapeHtml19(report.status)}">${escapeHtml19(report.status)}</span><span class="pill">${String(report.total)} turns</span><span class="pill">avg ${escapeHtml19(formatMs2(report.averageTotalMs))}</span><span class="pill">${String(report.warnings)} warnings</span><span class="pill">${String(report.failed)} failed</span></div></section>${turns || '<section class="turn"><p>No committed turns found.</p></section>'}</main></body></html>`;
11250
+ };
11251
+ var createVoiceTurnLatencyJSONHandler = (options) => async () => summarizeVoiceTurnLatency(options);
11252
+ var createVoiceTurnLatencyHTMLHandler = (options) => async () => {
11253
+ const report = await summarizeVoiceTurnLatency(options);
11254
+ const render = options.render ?? ((input) => renderVoiceTurnLatencyHTML(input, options));
11255
+ const body = await render(report);
11256
+ return new Response(body, {
11257
+ headers: {
11258
+ "Content-Type": "text/html; charset=utf-8",
11259
+ ...options.headers
11260
+ }
11261
+ });
11262
+ };
11263
+ var createVoiceTurnLatencyRoutes = (options) => {
11264
+ const path = options.path ?? "/api/turn-latency";
11265
+ const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
11266
+ const routes = new Elysia18({
11267
+ name: options.name ?? "absolutejs-voice-turn-latency"
11268
+ }).get(path, createVoiceTurnLatencyJSONHandler(options));
11269
+ if (htmlPath) {
11270
+ routes.get(htmlPath, createVoiceTurnLatencyHTMLHandler(options));
11271
+ }
11272
+ return routes;
11273
+ };
11274
+ // src/turnQuality.ts
11275
+ import { Elysia as Elysia19 } from "elysia";
11276
+ var DEFAULT_CONFIDENCE_WARN_THRESHOLD = 0.72;
11277
+ var escapeHtml20 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
11175
11278
  var getTurnLatencyMs = (turn) => {
11176
11279
  const firstTranscriptAt = turn.transcripts.map((transcript) => transcript.endedAtMs ?? transcript.startedAtMs).filter((value) => typeof value === "number").sort((left, right) => left - right)[0];
11177
11280
  if (firstTranscriptAt === undefined) {
@@ -11179,7 +11282,7 @@ var getTurnLatencyMs = (turn) => {
11179
11282
  }
11180
11283
  return Math.max(0, turn.committedAt - firstTranscriptAt);
11181
11284
  };
11182
- var summarizeTurn = (sessionId, turn, options) => {
11285
+ var summarizeTurn2 = (sessionId, turn, options) => {
11183
11286
  const quality = turn.quality;
11184
11287
  const correctionChanged = quality?.correction?.changed === true;
11185
11288
  const fallbackUsed = quality?.fallbackUsed === true;
@@ -11206,7 +11309,7 @@ var summarizeTurn = (sessionId, turn, options) => {
11206
11309
  turnId: turn.id
11207
11310
  };
11208
11311
  };
11209
- var resolveSessions = async (options) => {
11312
+ var resolveSessions2 = async (options) => {
11210
11313
  if (options.sessions) {
11211
11314
  return options.sessions;
11212
11315
  }
@@ -11225,9 +11328,9 @@ var resolveSessions = async (options) => {
11225
11328
  return sessions;
11226
11329
  };
11227
11330
  var summarizeVoiceTurnQuality = async (options) => {
11228
- const sessions = await resolveSessions(options);
11331
+ const sessions = await resolveSessions2(options);
11229
11332
  const confidenceWarnThreshold = options.confidenceWarnThreshold ?? DEFAULT_CONFIDENCE_WARN_THRESHOLD;
11230
- const turns = sessions.flatMap((session) => session.turns.map((turn) => summarizeTurn(session.id, turn, { confidenceWarnThreshold }))).sort((left, right) => right.committedAt - left.committedAt);
11333
+ const turns = sessions.flatMap((session) => session.turns.map((turn) => summarizeTurn2(session.id, turn, { confidenceWarnThreshold }))).sort((left, right) => right.committedAt - left.committedAt);
11231
11334
  const failed = turns.filter((turn) => turn.status === "fail").length;
11232
11335
  const warnings = turns.filter((turn) => turn.status === "warn").length;
11233
11336
  return {
@@ -11242,24 +11345,24 @@ var summarizeVoiceTurnQuality = async (options) => {
11242
11345
  };
11243
11346
  var renderVoiceTurnQualityHTML = (report, options = {}) => {
11244
11347
  const title = options.title ?? "Voice Turn Quality";
11245
- const turns = report.turns.map((turn) => `<article class="turn ${escapeHtml19(turn.status)}">
11348
+ const turns = report.turns.map((turn) => `<article class="turn ${escapeHtml20(turn.status)}">
11246
11349
  <div class="turn-header">
11247
11350
  <div>
11248
- <p class="eyebrow">${escapeHtml19(turn.sessionId)} \xB7 ${escapeHtml19(turn.turnId)}</p>
11249
- <h2>${escapeHtml19(turn.text || "Empty turn")}</h2>
11351
+ <p class="eyebrow">${escapeHtml20(turn.sessionId)} \xB7 ${escapeHtml20(turn.turnId)}</p>
11352
+ <h2>${escapeHtml20(turn.text || "Empty turn")}</h2>
11250
11353
  </div>
11251
- <strong>${escapeHtml19(turn.status)}</strong>
11354
+ <strong>${escapeHtml20(turn.status)}</strong>
11252
11355
  </div>
11253
11356
  <dl>
11254
- <div><dt>Source</dt><dd>${escapeHtml19(turn.source ?? "unknown")}</dd></div>
11357
+ <div><dt>Source</dt><dd>${escapeHtml20(turn.source ?? "unknown")}</dd></div>
11255
11358
  <div><dt>Confidence</dt><dd>${turn.averageConfidence === undefined ? "n/a" : `${Math.round(turn.averageConfidence * 100)}%`}</dd></div>
11256
- <div><dt>Fallback</dt><dd>${turn.fallbackUsed ? `yes (${escapeHtml19(turn.fallbackSelectionReason ?? "selected")})` : "no"}</dd></div>
11257
- <div><dt>Correction</dt><dd>${turn.correctionChanged ? `changed${turn.correctionProvider ? ` by ${escapeHtml19(turn.correctionProvider)}` : ""}` : "none"}</dd></div>
11359
+ <div><dt>Fallback</dt><dd>${turn.fallbackUsed ? `yes (${escapeHtml20(turn.fallbackSelectionReason ?? "selected")})` : "no"}</dd></div>
11360
+ <div><dt>Correction</dt><dd>${turn.correctionChanged ? `changed${turn.correctionProvider ? ` by ${escapeHtml20(turn.correctionProvider)}` : ""}` : "none"}</dd></div>
11258
11361
  <div><dt>Transcripts</dt><dd>${String(turn.selectedTranscriptCount)} selected \xB7 ${String(turn.finalTranscriptCount)} final \xB7 ${String(turn.partialTranscriptCount)} partial</dd></div>
11259
11362
  <div><dt>Cost</dt><dd>${turn.costUnits === undefined ? "n/a" : String(turn.costUnits)}</dd></div>
11260
11363
  </dl>
11261
11364
  </article>`).join("");
11262
- return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml19(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>${escapeHtml19(title)}</h1><div class="summary"><span class="pill ${escapeHtml19(report.status)}">${escapeHtml19(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>`;
11365
+ 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>`;
11263
11366
  };
11264
11367
  var createVoiceTurnQualityJSONHandler = (options) => async () => summarizeVoiceTurnQuality(options);
11265
11368
  var createVoiceTurnQualityHTMLHandler = (options) => async () => {
@@ -11276,7 +11379,7 @@ var createVoiceTurnQualityHTMLHandler = (options) => async () => {
11276
11379
  var createVoiceTurnQualityRoutes = (options) => {
11277
11380
  const path = options.path ?? "/api/turn-quality";
11278
11381
  const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
11279
- const routes = new Elysia18({
11382
+ const routes = new Elysia19({
11280
11383
  name: options.name ?? "absolutejs-voice-turn-quality"
11281
11384
  }).get(path, createVoiceTurnQualityJSONHandler(options));
11282
11385
  if (htmlPath) {
@@ -11285,8 +11388,8 @@ var createVoiceTurnQualityRoutes = (options) => {
11285
11388
  return routes;
11286
11389
  };
11287
11390
  // src/outcomeContract.ts
11288
- import { Elysia as Elysia19 } from "elysia";
11289
- var escapeHtml20 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
11391
+ import { Elysia as Elysia20 } from "elysia";
11392
+ var escapeHtml21 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
11290
11393
  var getPayloadString = (event, key) => typeof event.payload[key] === "string" ? event.payload[key] : undefined;
11291
11394
  var toList = async (input) => Array.isArray(input) ? input : await input?.list() ?? [];
11292
11395
  var hydrateSessions = async (input) => {
@@ -11394,9 +11497,9 @@ var renderVoiceOutcomeContractHTML = (report, options = {}) => {
11394
11497
  const contracts = report.contracts.map((contract) => `<section class="contract ${contract.pass ? "pass" : "fail"}">
11395
11498
  <div class="contract-header">
11396
11499
  <div>
11397
- <p class="eyebrow">${escapeHtml20(contract.contractId)}</p>
11398
- <h2>${escapeHtml20(contract.label ?? contract.contractId)}</h2>
11399
- ${contract.description ? `<p>${escapeHtml20(contract.description)}</p>` : ""}
11500
+ <p class="eyebrow">${escapeHtml21(contract.contractId)}</p>
11501
+ <h2>${escapeHtml21(contract.label ?? contract.contractId)}</h2>
11502
+ ${contract.description ? `<p>${escapeHtml21(contract.description)}</p>` : ""}
11400
11503
  </div>
11401
11504
  <strong>${contract.pass ? "pass" : "fail"}</strong>
11402
11505
  </div>
@@ -11407,9 +11510,9 @@ var renderVoiceOutcomeContractHTML = (report, options = {}) => {
11407
11510
  <span>handoffs ${String(contract.matched.handoffs)}</span>
11408
11511
  <span>events ${String(contract.matched.integrationEvents)}</span>
11409
11512
  </div>
11410
- ${contract.issues.length ? `<ul>${contract.issues.map((issue) => `<li>${escapeHtml20(issue.message)}</li>`).join("")}</ul>` : ""}
11513
+ ${contract.issues.length ? `<ul>${contract.issues.map((issue) => `<li>${escapeHtml21(issue.message)}</li>`).join("")}</ul>` : ""}
11411
11514
  </section>`).join("");
11412
- 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,.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>${escapeHtml20(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>`;
11515
+ 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>`;
11413
11516
  };
11414
11517
  var createVoiceOutcomeContractJSONHandler = (options) => async () => runVoiceOutcomeContractSuite(options);
11415
11518
  var createVoiceOutcomeContractHTMLHandler = (options) => async () => {
@@ -11425,7 +11528,7 @@ var createVoiceOutcomeContractHTMLHandler = (options) => async () => {
11425
11528
  var createVoiceOutcomeContractRoutes = (options) => {
11426
11529
  const path = options.path ?? "/api/outcome-contracts";
11427
11530
  const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
11428
- const routes = new Elysia19({
11531
+ const routes = new Elysia20({
11429
11532
  name: options.name ?? "absolutejs-voice-outcome-contracts"
11430
11533
  }).get(path, createVoiceOutcomeContractJSONHandler(options));
11431
11534
  if (htmlPath) {
@@ -11434,7 +11537,7 @@ var createVoiceOutcomeContractRoutes = (options) => {
11434
11537
  return routes;
11435
11538
  };
11436
11539
  // src/telephonyOutcome.ts
11437
- import { Elysia as Elysia20 } from "elysia";
11540
+ import { Elysia as Elysia21 } from "elysia";
11438
11541
  var DEFAULT_COMPLETED_STATUSES = [
11439
11542
  "answered",
11440
11543
  "completed",
@@ -11505,7 +11608,7 @@ var firstString2 = (source, keys) => {
11505
11608
  }
11506
11609
  }
11507
11610
  };
11508
- var firstNumber2 = (source, keys) => {
11611
+ var firstNumber3 = (source, keys) => {
11509
11612
  for (const key of keys) {
11510
11613
  const value = source[key];
11511
11614
  if (typeof value === "number" && Number.isFinite(value)) {
@@ -11877,7 +11980,7 @@ var parseVoiceTelephonyWebhookEvent = (input) => {
11877
11980
  "event_type",
11878
11981
  "type"
11879
11982
  ]);
11880
- const durationMs = firstNumber2(payload, ["durationMs", "duration_ms"]) ?? durationMsFromSeconds(firstNumber2(payload, [
11983
+ const durationMs = firstNumber3(payload, ["durationMs", "duration_ms"]) ?? durationMsFromSeconds(firstNumber3(payload, [
11881
11984
  "CallDuration",
11882
11985
  "call_duration",
11883
11986
  "callDuration",
@@ -11885,7 +11988,7 @@ var parseVoiceTelephonyWebhookEvent = (input) => {
11885
11988
  "dial_call_duration",
11886
11989
  "duration"
11887
11990
  ]));
11888
- const sipCode = firstNumber2(payload, [
11991
+ const sipCode = firstNumber3(payload, [
11889
11992
  "SipResponseCode",
11890
11993
  "sip_response_code",
11891
11994
  "sipCode",
@@ -12081,7 +12184,7 @@ var createVoiceTelephonyWebhookHandler = (options = {}) => async (input) => {
12081
12184
  var createVoiceTelephonyWebhookRoutes = (options = {}) => {
12082
12185
  const path = options.path ?? "/api/voice/telephony/webhook";
12083
12186
  const handler = createVoiceTelephonyWebhookHandler(options);
12084
- return new Elysia20({
12187
+ return new Elysia21({
12085
12188
  name: options.name ?? "absolutejs-voice-telephony-webhooks"
12086
12189
  }).post(path, async ({ query, request }) => {
12087
12190
  try {
@@ -14349,7 +14452,7 @@ var createVoiceMemoryStore = () => {
14349
14452
  return { get, getOrCreate, list, remove, set };
14350
14453
  };
14351
14454
  // src/opsWebhook.ts
14352
- import { Elysia as Elysia21 } from "elysia";
14455
+ import { Elysia as Elysia22 } from "elysia";
14353
14456
  var toHex5 = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
14354
14457
  var signVoiceOpsWebhookBody = async (input) => {
14355
14458
  const encoder = new TextEncoder;
@@ -14479,7 +14582,7 @@ var verifyVoiceOpsWebhookSignature = async (input) => {
14479
14582
  };
14480
14583
  var createVoiceOpsWebhookReceiverRoutes = (options = {}) => {
14481
14584
  const path = options.path ?? "/api/voice-ops/webhook";
14482
- return new Elysia21().post(path, async ({ body, request, set }) => {
14585
+ return new Elysia22().post(path, async ({ body, request, set }) => {
14483
14586
  const bodyText = typeof body === "string" ? body : JSON.stringify(body);
14484
14587
  if (options.signingSecret) {
14485
14588
  const verification = await verifyVoiceOpsWebhookSignature({
@@ -16208,7 +16311,7 @@ var createVoiceSTTRoutingCorrectionHandler = (mode = "generic") => {
16208
16311
  };
16209
16312
  // src/telephony/twilio.ts
16210
16313
  import { Buffer as Buffer3 } from "buffer";
16211
- import { Elysia as Elysia22 } from "elysia";
16314
+ import { Elysia as Elysia23 } from "elysia";
16212
16315
  var TWILIO_MULAW_SAMPLE_RATE = 8000;
16213
16316
  var VOICE_PCM_SAMPLE_RATE = 16000;
16214
16317
  var escapeXml2 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
@@ -16238,7 +16341,7 @@ var resolveTwilioStreamParameters = async (parameters, input) => {
16238
16341
  return parameters;
16239
16342
  };
16240
16343
  var joinUrlPath = (origin, path) => `${origin.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
16241
- var escapeHtml21 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&#39;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
16344
+ var escapeHtml22 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&#39;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
16242
16345
  var getWebhookVerificationUrl = (webhook, input) => {
16243
16346
  if (!webhook?.verificationUrl) {
16244
16347
  return;
@@ -16281,23 +16384,23 @@ var buildTwilioVoiceSetupStatus = async (options, input) => {
16281
16384
  };
16282
16385
  var renderTwilioVoiceSetupHTML = (status, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
16283
16386
  <p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Twilio setup</p>
16284
- <h1>${escapeHtml21(title)}</h1>
16387
+ <h1>${escapeHtml22(title)}</h1>
16285
16388
  <p><strong>Status:</strong> ${status.ready ? "Ready" : "Needs attention"}</p>
16286
16389
  <section>
16287
16390
  <h2>URLs</h2>
16288
16391
  <ul>
16289
- <li><strong>TwiML:</strong> <code>${escapeHtml21(status.urls.twiml)}</code></li>
16290
- <li><strong>Media stream:</strong> <code>${escapeHtml21(status.urls.stream)}</code></li>
16291
- <li><strong>Status webhook:</strong> <code>${escapeHtml21(status.urls.webhook)}</code></li>
16392
+ <li><strong>TwiML:</strong> <code>${escapeHtml22(status.urls.twiml)}</code></li>
16393
+ <li><strong>Media stream:</strong> <code>${escapeHtml22(status.urls.stream)}</code></li>
16394
+ <li><strong>Status webhook:</strong> <code>${escapeHtml22(status.urls.webhook)}</code></li>
16292
16395
  </ul>
16293
16396
  </section>
16294
16397
  <section>
16295
16398
  <h2>Signing</h2>
16296
16399
  <p>Mode: <code>${status.signing.mode}</code></p>
16297
- ${status.signing.verificationUrl ? `<p>Verification URL: <code>${escapeHtml21(status.signing.verificationUrl)}</code></p>` : ""}
16400
+ ${status.signing.verificationUrl ? `<p>Verification URL: <code>${escapeHtml22(status.signing.verificationUrl)}</code></p>` : ""}
16298
16401
  </section>
16299
- ${status.missing.length ? `<section><h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml21(name)}</code></li>`).join("")}</ul></section>` : ""}
16300
- ${status.warnings.length ? `<section><h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml21(warning)}</li>`).join("")}</ul></section>` : ""}
16402
+ ${status.missing.length ? `<section><h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml22(name)}</code></li>`).join("")}</ul></section>` : ""}
16403
+ ${status.warnings.length ? `<section><h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml22(warning)}</li>`).join("")}</ul></section>` : ""}
16301
16404
  </main>`;
16302
16405
  var extractTwilioStreamUrl = (twiml) => twiml.match(/<Stream\b[^>]*\surl="([^"]+)"/i)?.[1]?.replaceAll("&amp;", "&");
16303
16406
  var createSmokeCheck = (name, status, message, details) => ({
@@ -16308,20 +16411,20 @@ var createSmokeCheck = (name, status, message, details) => ({
16308
16411
  });
16309
16412
  var renderTwilioVoiceSmokeHTML = (report, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
16310
16413
  <p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Twilio smoke test</p>
16311
- <h1>${escapeHtml21(title)}</h1>
16414
+ <h1>${escapeHtml22(title)}</h1>
16312
16415
  <p><strong>Status:</strong> ${report.pass ? "Pass" : "Fail"}</p>
16313
16416
  <section>
16314
16417
  <h2>Checks</h2>
16315
16418
  <ul>
16316
- ${report.checks.map((check) => `<li><strong>${escapeHtml21(check.name)}</strong>: ${escapeHtml21(check.status)}${check.message ? ` - ${escapeHtml21(check.message)}` : ""}</li>`).join("")}
16419
+ ${report.checks.map((check) => `<li><strong>${escapeHtml22(check.name)}</strong>: ${escapeHtml22(check.status)}${check.message ? ` - ${escapeHtml22(check.message)}` : ""}</li>`).join("")}
16317
16420
  </ul>
16318
16421
  </section>
16319
16422
  <section>
16320
16423
  <h2>Observed URLs</h2>
16321
16424
  <ul>
16322
- <li><strong>TwiML:</strong> <code>${escapeHtml21(report.setup.urls.twiml)}</code></li>
16323
- <li><strong>Stream:</strong> <code>${escapeHtml21(report.twiml?.streamUrl ?? report.setup.urls.stream)}</code></li>
16324
- <li><strong>Webhook:</strong> <code>${escapeHtml21(report.setup.urls.webhook)}</code></li>
16425
+ <li><strong>TwiML:</strong> <code>${escapeHtml22(report.setup.urls.twiml)}</code></li>
16426
+ <li><strong>Stream:</strong> <code>${escapeHtml22(report.twiml?.streamUrl ?? report.setup.urls.stream)}</code></li>
16427
+ <li><strong>Webhook:</strong> <code>${escapeHtml22(report.setup.urls.webhook)}</code></li>
16325
16428
  </ul>
16326
16429
  </section>
16327
16430
  </main>`;
@@ -16781,7 +16884,7 @@ var createTwilioVoiceRoutes = (options) => {
16781
16884
  const smokePath = options.smoke?.path === false ? false : options.smoke?.path ?? "/api/voice/twilio/smoke";
16782
16885
  const bridges = new WeakMap;
16783
16886
  const webhookPolicy = options.webhook?.policy ?? options.outcomePolicy ?? createVoiceTelephonyOutcomePolicy();
16784
- const app = new Elysia22({
16887
+ const app = new Elysia23({
16785
16888
  name: options.name ?? "absolutejs-voice-twilio"
16786
16889
  }).get(twimlPath, async ({ query, request }) => {
16787
16890
  const streamUrl = await resolveTwilioStreamUrl(options, {
@@ -16917,9 +17020,9 @@ var createTwilioVoiceRoutes = (options) => {
16917
17020
  };
16918
17021
  // src/telephony/telnyx.ts
16919
17022
  import { Buffer as Buffer4 } from "buffer";
16920
- import { Elysia as Elysia23 } from "elysia";
17023
+ import { Elysia as Elysia24 } from "elysia";
16921
17024
  var escapeXml3 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
16922
- var escapeHtml22 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&#39;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
17025
+ var escapeHtml23 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&#39;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
16923
17026
  var joinUrlPath2 = (origin, path) => `${origin.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
16924
17027
  var resolveRequestOrigin2 = (request) => {
16925
17028
  const url = new URL(request.url);
@@ -17120,21 +17223,21 @@ var buildTelnyxVoiceSetupStatus = async (options, input) => {
17120
17223
  };
17121
17224
  var renderTelnyxSetupHTML = (status, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
17122
17225
  <p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Telnyx setup</p>
17123
- <h1>${escapeHtml22(title)}</h1>
17226
+ <h1>${escapeHtml23(title)}</h1>
17124
17227
  <p><strong>Status:</strong> ${status.ready ? "Ready" : "Needs attention"}</p>
17125
17228
  <ul>
17126
- <li><strong>TeXML:</strong> <code>${escapeHtml22(status.urls.texml)}</code></li>
17127
- <li><strong>Media stream:</strong> <code>${escapeHtml22(status.urls.stream)}</code></li>
17128
- <li><strong>Status webhook:</strong> <code>${escapeHtml22(status.urls.webhook)}</code></li>
17229
+ <li><strong>TeXML:</strong> <code>${escapeHtml23(status.urls.texml)}</code></li>
17230
+ <li><strong>Media stream:</strong> <code>${escapeHtml23(status.urls.stream)}</code></li>
17231
+ <li><strong>Status webhook:</strong> <code>${escapeHtml23(status.urls.webhook)}</code></li>
17129
17232
  </ul>
17130
- ${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml22(name)}</code></li>`).join("")}</ul>` : ""}
17131
- ${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml22(warning)}</li>`).join("")}</ul>` : ""}
17233
+ ${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml23(name)}</code></li>`).join("")}</ul>` : ""}
17234
+ ${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml23(warning)}</li>`).join("")}</ul>` : ""}
17132
17235
  </main>`;
17133
17236
  var renderTelnyxSmokeHTML = (report, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
17134
17237
  <p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Telnyx smoke test</p>
17135
- <h1>${escapeHtml22(title)}</h1>
17238
+ <h1>${escapeHtml23(title)}</h1>
17136
17239
  <p><strong>Status:</strong> ${report.pass ? "Pass" : "Fail"}</p>
17137
- <ul>${report.checks.map((check) => `<li><strong>${escapeHtml22(check.name)}</strong>: ${escapeHtml22(check.status)}${check.message ? ` - ${escapeHtml22(check.message)}` : ""}</li>`).join("")}</ul>
17240
+ <ul>${report.checks.map((check) => `<li><strong>${escapeHtml23(check.name)}</strong>: ${escapeHtml23(check.status)}${check.message ? ` - ${escapeHtml23(check.message)}` : ""}</li>`).join("")}</ul>
17138
17241
  </main>`;
17139
17242
  var runTelnyxSmokeTest = async (input) => {
17140
17243
  const setup = await buildTelnyxVoiceSetupStatus(input.options, input);
@@ -17228,7 +17331,7 @@ var createTelnyxVoiceRoutes = (options = {}) => {
17228
17331
  publicKey: options.webhook?.publicKey,
17229
17332
  toleranceSeconds: options.webhook?.toleranceSeconds
17230
17333
  }) : undefined);
17231
- const app = new Elysia23({
17334
+ const app = new Elysia24({
17232
17335
  name: options.name ?? "absolutejs-voice-telnyx"
17233
17336
  }).get(texmlPath, async ({ query, request }) => {
17234
17337
  const streamUrl = await resolveTelnyxStreamUrl(options, {
@@ -17338,9 +17441,9 @@ var createTelnyxVoiceRoutes = (options = {}) => {
17338
17441
  };
17339
17442
  // src/telephony/plivo.ts
17340
17443
  import { Buffer as Buffer5 } from "buffer";
17341
- import { Elysia as Elysia24 } from "elysia";
17444
+ import { Elysia as Elysia25 } from "elysia";
17342
17445
  var escapeXml4 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
17343
- var escapeHtml23 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&#39;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
17446
+ var escapeHtml24 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&#39;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
17344
17447
  var joinUrlPath3 = (origin, path) => `${origin.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
17345
17448
  var resolveRequestOrigin3 = (request) => {
17346
17449
  const url = new URL(request.url);
@@ -17591,21 +17694,21 @@ var buildPlivoVoiceSetupStatus = async (options, input) => {
17591
17694
  };
17592
17695
  var renderPlivoSetupHTML = (status, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
17593
17696
  <p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Plivo setup</p>
17594
- <h1>${escapeHtml23(title)}</h1>
17697
+ <h1>${escapeHtml24(title)}</h1>
17595
17698
  <p><strong>Status:</strong> ${status.ready ? "Ready" : "Needs attention"}</p>
17596
17699
  <ul>
17597
- <li><strong>Answer XML:</strong> <code>${escapeHtml23(status.urls.answer)}</code></li>
17598
- <li><strong>Audio stream:</strong> <code>${escapeHtml23(status.urls.stream)}</code></li>
17599
- <li><strong>Status webhook:</strong> <code>${escapeHtml23(status.urls.webhook)}</code></li>
17700
+ <li><strong>Answer XML:</strong> <code>${escapeHtml24(status.urls.answer)}</code></li>
17701
+ <li><strong>Audio stream:</strong> <code>${escapeHtml24(status.urls.stream)}</code></li>
17702
+ <li><strong>Status webhook:</strong> <code>${escapeHtml24(status.urls.webhook)}</code></li>
17600
17703
  </ul>
17601
- ${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml23(name)}</code></li>`).join("")}</ul>` : ""}
17602
- ${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml23(warning)}</li>`).join("")}</ul>` : ""}
17704
+ ${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml24(name)}</code></li>`).join("")}</ul>` : ""}
17705
+ ${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml24(warning)}</li>`).join("")}</ul>` : ""}
17603
17706
  </main>`;
17604
17707
  var renderPlivoSmokeHTML = (report, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
17605
17708
  <p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Plivo smoke test</p>
17606
- <h1>${escapeHtml23(title)}</h1>
17709
+ <h1>${escapeHtml24(title)}</h1>
17607
17710
  <p><strong>Status:</strong> ${report.pass ? "Pass" : "Fail"}</p>
17608
- <ul>${report.checks.map((check) => `<li><strong>${escapeHtml23(check.name)}</strong>: ${escapeHtml23(check.status)}${check.message ? ` - ${escapeHtml23(check.message)}` : ""}</li>`).join("")}</ul>
17711
+ <ul>${report.checks.map((check) => `<li><strong>${escapeHtml24(check.name)}</strong>: ${escapeHtml24(check.status)}${check.message ? ` - ${escapeHtml24(check.message)}` : ""}</li>`).join("")}</ul>
17609
17712
  </main>`;
17610
17713
  var runPlivoSmokeTest = async (input) => {
17611
17714
  const setup = await buildPlivoVoiceSetupStatus(input.options, input);
@@ -17700,7 +17803,7 @@ var createPlivoVoiceRoutes = (options = {}) => {
17700
17803
  request: input.request
17701
17804
  }) : verificationUrl ?? input.request.url
17702
17805
  }) : undefined);
17703
- const app = new Elysia24({
17806
+ const app = new Elysia25({
17704
17807
  name: options.name ?? "absolutejs-voice-plivo"
17705
17808
  }).get(answerPath, async ({ query, request }) => {
17706
17809
  const streamUrl = await resolvePlivoStreamUrl(options, {
@@ -17873,6 +17976,7 @@ export {
17873
17976
  transcodeTwilioInboundPayloadToPCM16,
17874
17977
  transcodePCMToTwilioOutboundPayload,
17875
17978
  summarizeVoiceTurnQuality,
17979
+ summarizeVoiceTurnLatency,
17876
17980
  summarizeVoiceTraceTimeline,
17877
17981
  summarizeVoiceTraceSinkDeliveries,
17878
17982
  summarizeVoiceTrace,
@@ -17920,6 +18024,7 @@ export {
17920
18024
  requeueVoiceOpsTask,
17921
18025
  reopenVoiceOpsTask,
17922
18026
  renderVoiceTurnQualityHTML,
18027
+ renderVoiceTurnLatencyHTML,
17923
18028
  renderVoiceTraceTimelineSessionHTML,
17924
18029
  renderVoiceTraceTimelineHTML,
17925
18030
  renderVoiceTraceMarkdown,
@@ -17985,6 +18090,9 @@ export {
17985
18090
  createVoiceTurnQualityRoutes,
17986
18091
  createVoiceTurnQualityJSONHandler,
17987
18092
  createVoiceTurnQualityHTMLHandler,
18093
+ createVoiceTurnLatencyRoutes,
18094
+ createVoiceTurnLatencyJSONHandler,
18095
+ createVoiceTurnLatencyHTMLHandler,
17988
18096
  createVoiceTraceTimelineRoutes,
17989
18097
  createVoiceTraceSinkStore,
17990
18098
  createVoiceTraceSinkDeliveryWorkerLoop,
@@ -0,0 +1,6 @@
1
+ import { type VoiceTurnLatencyWidgetOptions } from '../client/turnLatencyWidget';
2
+ export type VoiceTurnLatencyProps = VoiceTurnLatencyWidgetOptions & {
3
+ className?: string;
4
+ path?: string;
5
+ };
6
+ export declare const VoiceTurnLatency: ({ className, path, ...options }: VoiceTurnLatencyProps) => import("react/jsx-runtime").JSX.Element;
@@ -4,6 +4,7 @@ export { VoiceProviderCapabilities } from './VoiceProviderCapabilities';
4
4
  export { VoiceProviderStatus } from './VoiceProviderStatus';
5
5
  export { VoiceRoutingStatus } from './VoiceRoutingStatus';
6
6
  export { VoiceTraceTimeline } from './VoiceTraceTimeline';
7
+ export { VoiceTurnLatency } from './VoiceTurnLatency';
7
8
  export { VoiceTurnQuality } from './VoiceTurnQuality';
8
9
  export { useVoiceAppKitStatus } from './useVoiceAppKitStatus';
9
10
  export { useVoiceStream } from './useVoiceStream';
@@ -13,5 +14,6 @@ export { useVoiceProviderCapabilities } from './useVoiceProviderCapabilities';
13
14
  export { useVoiceProviderSimulationControls } from './useVoiceProviderSimulationControls';
14
15
  export { useVoiceRoutingStatus } from './useVoiceRoutingStatus';
15
16
  export { useVoiceTraceTimeline } from './useVoiceTraceTimeline';
17
+ export { useVoiceTurnLatency } from './useVoiceTurnLatency';
16
18
  export { useVoiceTurnQuality } from './useVoiceTurnQuality';
17
19
  export { useVoiceWorkflowStatus } from './useVoiceWorkflowStatus';