@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/angular/index.d.ts +1 -0
- package/dist/angular/index.js +133 -10
- package/dist/angular/voice-turn-quality.service.d.ts +12 -0
- package/dist/client/index.d.ts +4 -0
- package/dist/client/index.js +196 -0
- package/dist/client/turnQuality.d.ts +19 -0
- package/dist/client/turnQualityWidget.d.ts +32 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +277 -2
- package/dist/outcomeContract.d.ts +112 -0
- package/dist/react/VoiceTurnQuality.d.ts +6 -0
- package/dist/react/index.d.ts +2 -0
- package/dist/react/index.js +304 -12
- package/dist/react/useVoiceTurnQuality.d.ts +8 -0
- package/dist/svelte/createVoiceTurnQuality.d.ts +10 -0
- package/dist/svelte/index.d.ts +1 -0
- package/dist/svelte/index.js +201 -0
- package/dist/turnQuality.d.ts +94 -0
- package/dist/vue/VoiceTurnQuality.d.ts +51 -0
- package/dist/vue/index.d.ts +2 -0
- package/dist/vue/index.js +291 -14
- package/dist/vue/useVoiceTurnQuality.d.ts +9 -0
- package/package.json +1 -1
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("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
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("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
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
|
|
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
|
|
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;
|
package/dist/react/index.d.ts
CHANGED
|
@@ -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';
|