@absolutejs/voice 0.0.22-beta.66 → 0.0.22-beta.68

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
@@ -10294,6 +10294,271 @@ var createVoiceToolContractRoutes = (options) => {
10294
10294
  }
10295
10295
  return routes;
10296
10296
  };
10297
+ // src/turnQuality.ts
10298
+ import { Elysia as Elysia14 } from "elysia";
10299
+ var DEFAULT_CONFIDENCE_WARN_THRESHOLD = 0.72;
10300
+ var escapeHtml15 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
10301
+ var getTurnLatencyMs = (turn) => {
10302
+ const firstTranscriptAt = turn.transcripts.map((transcript) => transcript.endedAtMs ?? transcript.startedAtMs).filter((value) => typeof value === "number").sort((left, right) => left - right)[0];
10303
+ if (firstTranscriptAt === undefined) {
10304
+ return;
10305
+ }
10306
+ return Math.max(0, turn.committedAt - firstTranscriptAt);
10307
+ };
10308
+ var summarizeTurn = (sessionId, turn, options) => {
10309
+ const quality = turn.quality;
10310
+ const correctionChanged = quality?.correction?.changed === true;
10311
+ const fallbackUsed = quality?.fallbackUsed === true;
10312
+ const lowConfidence = typeof quality?.averageConfidence === "number" && quality.averageConfidence < options.confidenceWarnThreshold;
10313
+ const hasNoQuality = !quality;
10314
+ const status = hasNoQuality ? "unknown" : quality.selectedTranscriptCount === 0 || turn.text.trim().length === 0 ? "fail" : fallbackUsed || correctionChanged || lowConfidence ? "warn" : "pass";
10315
+ return {
10316
+ averageConfidence: quality?.averageConfidence,
10317
+ committedAt: turn.committedAt,
10318
+ correctionChanged,
10319
+ correctionProvider: quality?.correction?.provider,
10320
+ correctionReason: quality?.correction?.reason,
10321
+ costUnits: quality?.cost?.estimatedRelativeCostUnits,
10322
+ fallbackSelectionReason: quality?.fallback?.selectionReason,
10323
+ fallbackUsed,
10324
+ finalTranscriptCount: quality?.finalTranscriptCount ?? 0,
10325
+ latencyMs: getTurnLatencyMs(turn),
10326
+ partialTranscriptCount: quality?.partialTranscriptCount ?? 0,
10327
+ selectedTranscriptCount: quality?.selectedTranscriptCount ?? 0,
10328
+ sessionId,
10329
+ source: quality?.source,
10330
+ status,
10331
+ text: turn.text,
10332
+ turnId: turn.id
10333
+ };
10334
+ };
10335
+ var resolveSessions = async (options) => {
10336
+ if (options.sessions) {
10337
+ return options.sessions;
10338
+ }
10339
+ if (!options.store) {
10340
+ return [];
10341
+ }
10342
+ const summaries = await options.store.list();
10343
+ const ids = options.sessionIds ?? summaries.map((summary) => summary.id);
10344
+ const hydrated = await Promise.all(ids.slice(0, options.limit ?? 25).map((id) => options.store?.get(id)));
10345
+ const sessions = [];
10346
+ for (const session of hydrated) {
10347
+ if (session) {
10348
+ sessions.push(session);
10349
+ }
10350
+ }
10351
+ return sessions;
10352
+ };
10353
+ var summarizeVoiceTurnQuality = async (options) => {
10354
+ const sessions = await resolveSessions(options);
10355
+ const confidenceWarnThreshold = options.confidenceWarnThreshold ?? DEFAULT_CONFIDENCE_WARN_THRESHOLD;
10356
+ const turns = sessions.flatMap((session) => session.turns.map((turn) => summarizeTurn(session.id, turn, { confidenceWarnThreshold }))).sort((left, right) => right.committedAt - left.committedAt);
10357
+ const failed = turns.filter((turn) => turn.status === "fail").length;
10358
+ const warnings = turns.filter((turn) => turn.status === "warn").length;
10359
+ return {
10360
+ checkedAt: Date.now(),
10361
+ failed,
10362
+ sessions: sessions.length,
10363
+ status: failed > 0 ? "fail" : warnings > 0 ? "warn" : "pass",
10364
+ total: turns.length,
10365
+ turns,
10366
+ warnings
10367
+ };
10368
+ };
10369
+ var renderVoiceTurnQualityHTML = (report, options = {}) => {
10370
+ const title = options.title ?? "Voice Turn Quality";
10371
+ const turns = report.turns.map((turn) => `<article class="turn ${escapeHtml15(turn.status)}">
10372
+ <div class="turn-header">
10373
+ <div>
10374
+ <p class="eyebrow">${escapeHtml15(turn.sessionId)} \xB7 ${escapeHtml15(turn.turnId)}</p>
10375
+ <h2>${escapeHtml15(turn.text || "Empty turn")}</h2>
10376
+ </div>
10377
+ <strong>${escapeHtml15(turn.status)}</strong>
10378
+ </div>
10379
+ <dl>
10380
+ <div><dt>Source</dt><dd>${escapeHtml15(turn.source ?? "unknown")}</dd></div>
10381
+ <div><dt>Confidence</dt><dd>${turn.averageConfidence === undefined ? "n/a" : `${Math.round(turn.averageConfidence * 100)}%`}</dd></div>
10382
+ <div><dt>Fallback</dt><dd>${turn.fallbackUsed ? `yes (${escapeHtml15(turn.fallbackSelectionReason ?? "selected")})` : "no"}</dd></div>
10383
+ <div><dt>Correction</dt><dd>${turn.correctionChanged ? `changed${turn.correctionProvider ? ` by ${escapeHtml15(turn.correctionProvider)}` : ""}` : "none"}</dd></div>
10384
+ <div><dt>Transcripts</dt><dd>${String(turn.selectedTranscriptCount)} selected \xB7 ${String(turn.finalTranscriptCount)} final \xB7 ${String(turn.partialTranscriptCount)} partial</dd></div>
10385
+ <div><dt>Cost</dt><dd>${turn.costUnits === undefined ? "n/a" : String(turn.costUnits)}</dd></div>
10386
+ </dl>
10387
+ </article>`).join("");
10388
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml15(title)}</title><style>body{background:#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>${escapeHtml15(title)}</h1><div class="summary"><span class="pill ${escapeHtml15(report.status)}">${escapeHtml15(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>`;
10389
+ };
10390
+ var createVoiceTurnQualityJSONHandler = (options) => async () => summarizeVoiceTurnQuality(options);
10391
+ var createVoiceTurnQualityHTMLHandler = (options) => async () => {
10392
+ const report = await summarizeVoiceTurnQuality(options);
10393
+ const render = options.render ?? ((input) => renderVoiceTurnQualityHTML(input, options));
10394
+ const body = await render(report);
10395
+ return new Response(body, {
10396
+ headers: {
10397
+ "Content-Type": "text/html; charset=utf-8",
10398
+ ...options.headers
10399
+ }
10400
+ });
10401
+ };
10402
+ var createVoiceTurnQualityRoutes = (options) => {
10403
+ const path = options.path ?? "/api/turn-quality";
10404
+ const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
10405
+ const routes = new Elysia14({
10406
+ name: options.name ?? "absolutejs-voice-turn-quality"
10407
+ }).get(path, createVoiceTurnQualityJSONHandler(options));
10408
+ if (htmlPath) {
10409
+ routes.get(htmlPath, createVoiceTurnQualityHTMLHandler(options));
10410
+ }
10411
+ return routes;
10412
+ };
10413
+ // src/outcomeContract.ts
10414
+ import { Elysia as Elysia15 } from "elysia";
10415
+ var escapeHtml16 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
10416
+ var getPayloadString = (event, key) => typeof event.payload[key] === "string" ? event.payload[key] : undefined;
10417
+ var toList = async (input) => Array.isArray(input) ? input : await input?.list() ?? [];
10418
+ var hydrateSessions = async (input) => {
10419
+ if (!input)
10420
+ return [];
10421
+ if (Array.isArray(input))
10422
+ return input;
10423
+ const summaries = await input.list();
10424
+ const sessions = await Promise.all(summaries.map((summary) => input.get(summary.id)));
10425
+ const hydrated = [];
10426
+ for (const session of sessions) {
10427
+ if (session) {
10428
+ hydrated.push(session);
10429
+ }
10430
+ }
10431
+ return hydrated;
10432
+ };
10433
+ var dispositionForSession = (session) => session.call?.disposition ?? (session.status === "completed" ? "completed" : undefined);
10434
+ var matchesDisposition = (disposition, expected) => expected === undefined || disposition === expected;
10435
+ var reportContract = (input) => {
10436
+ const { contract } = input;
10437
+ const sessions = input.sessions.filter((session) => (!contract.scenarioId || session.scenarioId === contract.scenarioId) && matchesDisposition(dispositionForSession(session), contract.expectedDisposition));
10438
+ const sessionIds = new Set(sessions.map((session) => session.id));
10439
+ const reviews = input.reviews.filter((review) => matchesDisposition(review.summary.outcome, contract.expectedDisposition));
10440
+ const tasks = input.tasks.filter((task) => matchesDisposition(task.outcome, contract.expectedDisposition));
10441
+ const handoffs = input.handoffs.filter((handoff) => (!contract.expectedDisposition || handoff.action === contract.expectedDisposition || contract.expectedDisposition === "transferred" && handoff.action === "transfer" || contract.expectedDisposition === "escalated" && handoff.action === "escalate") && (sessionIds.size === 0 || sessionIds.has(handoff.sessionId)));
10442
+ const events = input.events.filter((event) => {
10443
+ const eventSessionId = getPayloadString(event, "sessionId");
10444
+ const eventOutcome = getPayloadString(event, "outcome") ?? getPayloadString(event, "disposition");
10445
+ return (sessionIds.size === 0 || !eventSessionId || sessionIds.has(eventSessionId)) && (!contract.expectedDisposition || eventOutcome === contract.expectedDisposition);
10446
+ });
10447
+ const issues = [];
10448
+ const minSessions = contract.minSessions ?? 1;
10449
+ if (sessions.length < minSessions) {
10450
+ issues.push({
10451
+ code: "outcome.sessions_missing",
10452
+ message: `Expected at least ${minSessions} matching session(s), saw ${sessions.length}.`
10453
+ });
10454
+ }
10455
+ if (contract.requireReview !== false && reviews.length === 0) {
10456
+ issues.push({
10457
+ code: "outcome.review_missing",
10458
+ message: "Expected at least one matching review artifact."
10459
+ });
10460
+ }
10461
+ if (contract.requireTask && tasks.length < (contract.minTasks ?? 1)) {
10462
+ issues.push({
10463
+ code: "outcome.task_missing",
10464
+ message: `Expected at least ${contract.minTasks ?? 1} matching task(s), saw ${tasks.length}.`
10465
+ });
10466
+ }
10467
+ for (const action of contract.requireHandoffActions ?? []) {
10468
+ if (!handoffs.some((handoff) => handoff.action === action)) {
10469
+ issues.push({
10470
+ code: "outcome.handoff_missing",
10471
+ message: `Expected handoff action ${action}.`
10472
+ });
10473
+ }
10474
+ }
10475
+ for (const type of contract.requireIntegrationEvents ?? []) {
10476
+ if (!events.some((event) => event.type === type)) {
10477
+ issues.push({
10478
+ code: "outcome.integration_event_missing",
10479
+ message: `Expected integration event ${type}.`
10480
+ });
10481
+ }
10482
+ }
10483
+ return {
10484
+ contractId: contract.id,
10485
+ description: contract.description,
10486
+ issues,
10487
+ label: contract.label,
10488
+ matched: {
10489
+ handoffs: handoffs.length,
10490
+ integrationEvents: events.length,
10491
+ reviews: reviews.length,
10492
+ sessions: sessions.length,
10493
+ tasks: tasks.length
10494
+ },
10495
+ pass: issues.length === 0
10496
+ };
10497
+ };
10498
+ var runVoiceOutcomeContractSuite = async (options) => {
10499
+ const [sessions, reviews, tasks, events, handoffs] = await Promise.all([
10500
+ hydrateSessions(options.sessions),
10501
+ toList(options.reviews),
10502
+ toList(options.tasks),
10503
+ toList(options.events),
10504
+ toList(options.handoffs)
10505
+ ]);
10506
+ const contracts = options.contracts.map((contract) => reportContract({ contract, events, handoffs, reviews, sessions, tasks }));
10507
+ const passed = contracts.filter((contract) => contract.pass).length;
10508
+ const failed = contracts.length - passed;
10509
+ return {
10510
+ checkedAt: Date.now(),
10511
+ contracts,
10512
+ failed,
10513
+ passed,
10514
+ status: failed > 0 ? "fail" : "pass",
10515
+ total: contracts.length
10516
+ };
10517
+ };
10518
+ var renderVoiceOutcomeContractHTML = (report, options = {}) => {
10519
+ const title = options.title ?? "Voice Outcome Contracts";
10520
+ const contracts = report.contracts.map((contract) => `<section class="contract ${contract.pass ? "pass" : "fail"}">
10521
+ <div class="contract-header">
10522
+ <div>
10523
+ <p class="eyebrow">${escapeHtml16(contract.contractId)}</p>
10524
+ <h2>${escapeHtml16(contract.label ?? contract.contractId)}</h2>
10525
+ ${contract.description ? `<p>${escapeHtml16(contract.description)}</p>` : ""}
10526
+ </div>
10527
+ <strong>${contract.pass ? "pass" : "fail"}</strong>
10528
+ </div>
10529
+ <div class="grid">
10530
+ <span>sessions ${String(contract.matched.sessions)}</span>
10531
+ <span>reviews ${String(contract.matched.reviews)}</span>
10532
+ <span>tasks ${String(contract.matched.tasks)}</span>
10533
+ <span>handoffs ${String(contract.matched.handoffs)}</span>
10534
+ <span>events ${String(contract.matched.integrationEvents)}</span>
10535
+ </div>
10536
+ ${contract.issues.length ? `<ul>${contract.issues.map((issue) => `<li>${escapeHtml16(issue.message)}</li>`).join("")}</ul>` : ""}
10537
+ </section>`).join("");
10538
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml16(title)}</title><style>body{background:#101316;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}.hero,.contract{background:#181d22;border:1px solid #2a323a;border-radius:20px;margin-bottom:16px;padding:20px}.hero{background:linear-gradient(135deg,rgba(34,197,94,.14),rgba(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>${escapeHtml16(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>`;
10539
+ };
10540
+ var createVoiceOutcomeContractJSONHandler = (options) => async () => runVoiceOutcomeContractSuite(options);
10541
+ var createVoiceOutcomeContractHTMLHandler = (options) => async () => {
10542
+ const report = await runVoiceOutcomeContractSuite(options);
10543
+ const render = options.render ?? ((input) => renderVoiceOutcomeContractHTML(input, options));
10544
+ return new Response(await render(report), {
10545
+ headers: {
10546
+ "Content-Type": "text/html; charset=utf-8",
10547
+ ...options.headers
10548
+ }
10549
+ });
10550
+ };
10551
+ var createVoiceOutcomeContractRoutes = (options) => {
10552
+ const path = options.path ?? "/api/outcome-contracts";
10553
+ const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
10554
+ const routes = new Elysia15({
10555
+ name: options.name ?? "absolutejs-voice-outcome-contracts"
10556
+ }).get(path, createVoiceOutcomeContractJSONHandler(options));
10557
+ if (htmlPath) {
10558
+ routes.get(htmlPath, createVoiceOutcomeContractHTMLHandler(options));
10559
+ }
10560
+ return routes;
10561
+ };
10297
10562
  // src/fileStore.ts
10298
10563
  import { mkdir as mkdir2, readFile, readdir, rename, rm, writeFile } from "fs/promises";
10299
10564
  import { join } from "path";
@@ -12387,7 +12652,7 @@ var createVoiceMemoryStore = () => {
12387
12652
  return { get, getOrCreate, list, remove, set };
12388
12653
  };
12389
12654
  // src/opsWebhook.ts
12390
- import { Elysia as Elysia14 } from "elysia";
12655
+ import { Elysia as Elysia16 } from "elysia";
12391
12656
  var toHex5 = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
12392
12657
  var signVoiceOpsWebhookBody = async (input) => {
12393
12658
  const encoder = new TextEncoder;
@@ -12517,7 +12782,7 @@ var verifyVoiceOpsWebhookSignature = async (input) => {
12517
12782
  };
12518
12783
  var createVoiceOpsWebhookReceiverRoutes = (options = {}) => {
12519
12784
  const path = options.path ?? "/api/voice-ops/webhook";
12520
- return new Elysia14().post(path, async ({ body, request, set }) => {
12785
+ return new Elysia16().post(path, async ({ body, request, set }) => {
12521
12786
  const bodyText = typeof body === "string" ? body : JSON.stringify(body);
12522
12787
  if (options.signingSecret) {
12523
12788
  const verification = await verifyVoiceOpsWebhookSignature({
@@ -14664,6 +14929,7 @@ export {
14664
14929
  validateVoiceWorkflowRouteResult,
14665
14930
  transcodeTwilioInboundPayloadToPCM16,
14666
14931
  transcodePCMToTwilioOutboundPayload,
14932
+ summarizeVoiceTurnQuality,
14667
14933
  summarizeVoiceTraceSinkDeliveries,
14668
14934
  summarizeVoiceTrace,
14669
14935
  summarizeVoiceSessions,
@@ -14688,6 +14954,7 @@ export {
14688
14954
  runVoiceSessionEvals,
14689
14955
  runVoiceScenarioFixtureEvals,
14690
14956
  runVoiceScenarioEvals,
14957
+ runVoiceOutcomeContractSuite,
14691
14958
  resolveVoiceTraceRedactionOptions,
14692
14959
  resolveVoiceSTTRoutingStrategy,
14693
14960
  resolveVoiceRuntimePreset,
@@ -14703,6 +14970,7 @@ export {
14703
14970
  resolveAudioConditioningConfig,
14704
14971
  requeueVoiceOpsTask,
14705
14972
  reopenVoiceOpsTask,
14973
+ renderVoiceTurnQualityHTML,
14706
14974
  renderVoiceTraceMarkdown,
14707
14975
  renderVoiceTraceHTML,
14708
14976
  renderVoiceToolContractHTML,
@@ -14713,6 +14981,7 @@ export {
14713
14981
  renderVoiceQualityHTML,
14714
14982
  renderVoiceProviderHealthHTML,
14715
14983
  renderVoiceProviderCapabilityHTML,
14984
+ renderVoiceOutcomeContractHTML,
14716
14985
  renderVoiceOpsConsoleHTML,
14717
14986
  renderVoiceHandoffHealthHTML,
14718
14987
  renderVoiceEvalHTML,
@@ -14757,6 +15026,9 @@ export {
14757
15026
  createVoiceWebhookDeliveryWorkerLoop,
14758
15027
  createVoiceWebhookDeliveryWorker,
14759
15028
  createVoiceTwilioRedirectHandoffAdapter,
15029
+ createVoiceTurnQualityRoutes,
15030
+ createVoiceTurnQualityJSONHandler,
15031
+ createVoiceTurnQualityHTMLHandler,
14760
15032
  createVoiceTraceSinkStore,
14761
15033
  createVoiceTraceSinkDeliveryWorkerLoop,
14762
15034
  createVoiceTraceSinkDeliveryWorker,
@@ -14816,6 +15088,9 @@ export {
14816
15088
  createVoicePostgresReviewStore,
14817
15089
  createVoicePostgresIntegrationEventStore,
14818
15090
  createVoicePostgresExternalObjectMapStore,
15091
+ createVoiceOutcomeContractRoutes,
15092
+ createVoiceOutcomeContractJSONHandler,
15093
+ createVoiceOutcomeContractHTMLHandler,
14819
15094
  createVoiceOpsWebhookSink,
14820
15095
  createVoiceOpsWebhookReceiverRoutes,
14821
15096
  createVoiceOpsWebhookEnvelope,
@@ -0,0 +1,112 @@
1
+ import { Elysia } from 'elysia';
2
+ import type { StoredVoiceHandoffDelivery, VoiceCallDisposition, VoiceHandoffAction, VoiceHandoffDeliveryStore, VoiceSessionRecord, VoiceSessionStore } from './types';
3
+ import type { StoredVoiceIntegrationEvent, StoredVoiceOpsTask, VoiceIntegrationEventType } from './ops';
4
+ import type { StoredVoiceCallReviewArtifact } from './testing/review';
5
+ export type VoiceOutcomeContractStatus = 'pass' | 'fail';
6
+ export type VoiceOutcomeContractDefinition = {
7
+ description?: string;
8
+ expectedDisposition?: VoiceCallDisposition;
9
+ id: string;
10
+ label?: string;
11
+ minSessions?: number;
12
+ minTasks?: number;
13
+ requireHandoffActions?: VoiceHandoffAction[];
14
+ requireIntegrationEvents?: VoiceIntegrationEventType[];
15
+ requireReview?: boolean;
16
+ requireTask?: boolean;
17
+ scenarioId?: string;
18
+ };
19
+ export type VoiceOutcomeContractIssue = {
20
+ code: string;
21
+ message: string;
22
+ };
23
+ export type VoiceOutcomeContractReport = {
24
+ contractId: string;
25
+ description?: string;
26
+ issues: VoiceOutcomeContractIssue[];
27
+ label?: string;
28
+ matched: {
29
+ handoffs: number;
30
+ integrationEvents: number;
31
+ reviews: number;
32
+ sessions: number;
33
+ tasks: number;
34
+ };
35
+ pass: boolean;
36
+ };
37
+ export type VoiceOutcomeContractSuiteReport = {
38
+ checkedAt: number;
39
+ contracts: VoiceOutcomeContractReport[];
40
+ failed: number;
41
+ passed: number;
42
+ status: VoiceOutcomeContractStatus;
43
+ total: number;
44
+ };
45
+ type ListStore<T> = {
46
+ list: () => Promise<T[]> | T[];
47
+ };
48
+ export type VoiceOutcomeContractOptions<TSession extends VoiceSessionRecord = VoiceSessionRecord> = {
49
+ contracts: VoiceOutcomeContractDefinition[];
50
+ events?: StoredVoiceIntegrationEvent[] | ListStore<StoredVoiceIntegrationEvent>;
51
+ handoffs?: StoredVoiceHandoffDelivery[] | VoiceHandoffDeliveryStore;
52
+ reviews?: StoredVoiceCallReviewArtifact[] | ListStore<StoredVoiceCallReviewArtifact>;
53
+ sessions?: TSession[] | VoiceSessionStore<TSession>;
54
+ tasks?: StoredVoiceOpsTask[] | ListStore<StoredVoiceOpsTask>;
55
+ };
56
+ export type VoiceOutcomeContractHTMLHandlerOptions<TSession extends VoiceSessionRecord = VoiceSessionRecord> = VoiceOutcomeContractOptions<TSession> & {
57
+ headers?: HeadersInit;
58
+ render?: (report: VoiceOutcomeContractSuiteReport) => string | Promise<string>;
59
+ title?: string;
60
+ };
61
+ export type VoiceOutcomeContractRoutesOptions<TSession extends VoiceSessionRecord = VoiceSessionRecord> = VoiceOutcomeContractHTMLHandlerOptions<TSession> & {
62
+ htmlPath?: false | string;
63
+ name?: string;
64
+ path?: string;
65
+ };
66
+ export declare const runVoiceOutcomeContractSuite: <TSession extends VoiceSessionRecord = VoiceSessionRecord>(options: VoiceOutcomeContractOptions<TSession>) => Promise<VoiceOutcomeContractSuiteReport>;
67
+ export declare const renderVoiceOutcomeContractHTML: (report: VoiceOutcomeContractSuiteReport, options?: {
68
+ title?: string;
69
+ }) => string;
70
+ export declare const createVoiceOutcomeContractJSONHandler: <TSession extends VoiceSessionRecord = VoiceSessionRecord>(options: VoiceOutcomeContractOptions<TSession>) => () => Promise<VoiceOutcomeContractSuiteReport>;
71
+ export declare const createVoiceOutcomeContractHTMLHandler: <TSession extends VoiceSessionRecord = VoiceSessionRecord>(options: VoiceOutcomeContractHTMLHandlerOptions<TSession>) => () => Promise<Response>;
72
+ export declare const createVoiceOutcomeContractRoutes: <TSession extends VoiceSessionRecord = VoiceSessionRecord>(options: VoiceOutcomeContractRoutesOptions<TSession>) => Elysia<"", {
73
+ decorator: {};
74
+ store: {};
75
+ derive: {};
76
+ resolve: {};
77
+ }, {
78
+ typebox: {};
79
+ error: {};
80
+ }, {
81
+ schema: {};
82
+ standaloneSchema: {};
83
+ macro: {};
84
+ macroFn: {};
85
+ parser: {};
86
+ response: {};
87
+ }, {
88
+ [x: string]: {
89
+ get: {
90
+ body: unknown;
91
+ params: {};
92
+ query: unknown;
93
+ headers: unknown;
94
+ response: {
95
+ 200: VoiceOutcomeContractSuiteReport;
96
+ };
97
+ };
98
+ };
99
+ }, {
100
+ derive: {};
101
+ resolve: {};
102
+ schema: {};
103
+ standaloneSchema: {};
104
+ response: {};
105
+ }, {
106
+ derive: {};
107
+ resolve: {};
108
+ schema: {};
109
+ standaloneSchema: {};
110
+ response: {};
111
+ }>;
112
+ export {};
@@ -0,0 +1,6 @@
1
+ import { type VoiceTurnQualityWidgetOptions } from '../client/turnQualityWidget';
2
+ export type VoiceTurnQualityProps = VoiceTurnQualityWidgetOptions & {
3
+ className?: string;
4
+ path?: string;
5
+ };
6
+ export declare const VoiceTurnQuality: ({ className, path, ...options }: VoiceTurnQualityProps) => import("react/jsx-runtime").JSX.Element;
@@ -3,6 +3,7 @@ export { VoiceProviderSimulationControls } from './VoiceProviderSimulationContro
3
3
  export { VoiceProviderCapabilities } from './VoiceProviderCapabilities';
4
4
  export { VoiceProviderStatus } from './VoiceProviderStatus';
5
5
  export { VoiceRoutingStatus } from './VoiceRoutingStatus';
6
+ export { VoiceTurnQuality } from './VoiceTurnQuality';
6
7
  export { useVoiceAppKitStatus } from './useVoiceAppKitStatus';
7
8
  export { useVoiceStream } from './useVoiceStream';
8
9
  export { useVoiceController } from './useVoiceController';
@@ -10,4 +11,5 @@ export { useVoiceProviderStatus } from './useVoiceProviderStatus';
10
11
  export { useVoiceProviderCapabilities } from './useVoiceProviderCapabilities';
11
12
  export { useVoiceProviderSimulationControls } from './useVoiceProviderSimulationControls';
12
13
  export { useVoiceRoutingStatus } from './useVoiceRoutingStatus';
14
+ export { useVoiceTurnQuality } from './useVoiceTurnQuality';
13
15
  export { useVoiceWorkflowStatus } from './useVoiceWorkflowStatus';