@absolutejs/voice 0.0.22-beta.43 → 0.0.22-beta.44

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.
@@ -59,6 +59,49 @@ export type VoiceEvalBaselineStore = {
59
59
  get: () => Promise<VoiceEvalReport | undefined>;
60
60
  set: (report: VoiceEvalReport) => Promise<void>;
61
61
  };
62
+ export type VoiceScenarioEvalDefinition = {
63
+ description?: string;
64
+ forbiddenHandoffActions?: string[];
65
+ forbiddenLifecycleTypes?: string[];
66
+ id: string;
67
+ label?: string;
68
+ maxProviderErrors?: number;
69
+ maxSessionErrors?: number;
70
+ minSessions?: number;
71
+ minTurns?: number;
72
+ requiredAssistantIncludes?: string[];
73
+ requiredDisposition?: string;
74
+ requiredHandoffActions?: string[];
75
+ requiredLifecycleTypes?: string[];
76
+ requiredPayloadPaths?: string[];
77
+ requiredTranscriptIncludes?: string[];
78
+ scenarioId?: string;
79
+ };
80
+ export type VoiceScenarioEvalSessionResult = {
81
+ eventCount: number;
82
+ issues: string[];
83
+ sessionId: string;
84
+ status: VoiceEvalStatus;
85
+ };
86
+ export type VoiceScenarioEvalResult = {
87
+ description?: string;
88
+ failed: number;
89
+ id: string;
90
+ issues: string[];
91
+ label: string;
92
+ matchedSessions: number;
93
+ passed: number;
94
+ sessions: VoiceScenarioEvalSessionResult[];
95
+ status: VoiceEvalStatus;
96
+ };
97
+ export type VoiceScenarioEvalReport = {
98
+ checkedAt: number;
99
+ failed: number;
100
+ passed: number;
101
+ scenarios: VoiceScenarioEvalResult[];
102
+ status: VoiceEvalStatus;
103
+ total: number;
104
+ };
62
105
  export type VoiceEvalLink = {
63
106
  href: string;
64
107
  label: string;
@@ -73,6 +116,7 @@ export type VoiceEvalRoutesOptions = {
73
116
  limit?: number;
74
117
  name?: string;
75
118
  path?: string;
119
+ scenarios?: VoiceScenarioEvalDefinition[];
76
120
  store?: VoiceTraceEventStore;
77
121
  thresholds?: VoiceQualityThresholds;
78
122
  title?: string;
@@ -83,6 +127,11 @@ export declare const runVoiceSessionEvals: (options?: {
83
127
  store?: VoiceTraceEventStore;
84
128
  thresholds?: VoiceQualityThresholds;
85
129
  }) => Promise<VoiceEvalReport>;
130
+ export declare const runVoiceScenarioEvals: (options?: {
131
+ events?: StoredVoiceTraceEvent[];
132
+ scenarios?: VoiceScenarioEvalDefinition[];
133
+ store?: VoiceTraceEventStore;
134
+ }) => Promise<VoiceScenarioEvalReport>;
86
135
  export declare const compareVoiceEvalBaseline: (currentReport: VoiceEvalReport, baselineReport: VoiceEvalReport, options?: VoiceEvalBaselineComparisonOptions) => VoiceEvalBaselineComparison;
87
136
  export declare const createVoiceFileEvalBaselineStore: (filePath: string) => VoiceEvalBaselineStore;
88
137
  export declare const renderVoiceEvalHTML: (report: VoiceEvalReport, options?: {
@@ -93,6 +142,10 @@ export declare const renderVoiceEvalBaselineHTML: (comparison: VoiceEvalBaseline
93
142
  links?: VoiceEvalLink[];
94
143
  title?: string;
95
144
  }) => string;
145
+ export declare const renderVoiceScenarioEvalHTML: (report: VoiceScenarioEvalReport, options?: {
146
+ links?: VoiceEvalLink[];
147
+ title?: string;
148
+ }) => string;
96
149
  export declare const createVoiceEvalRoutes: (options: VoiceEvalRoutesOptions) => Elysia<"", {
97
150
  decorator: {};
98
151
  store: {};
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ export { voice } from './plugin';
2
2
  export { createVoiceAssistant, createVoiceExperiment, summarizeVoiceAssistantRuns } from './assistant';
3
3
  export { createVoiceAssistantHealthHTMLHandler, createVoiceAssistantHealthJSONHandler, createVoiceAssistantHealthRoutes, renderVoiceAssistantHealthHTML, summarizeVoiceAssistantHealth } from './assistantHealth';
4
4
  export { buildVoiceDiagnosticsMarkdown, createVoiceDiagnosticsRoutes, resolveVoiceDiagnosticsTraceFilter } from './diagnosticsRoutes';
5
- export { compareVoiceEvalBaseline, createVoiceFileEvalBaselineStore, createVoiceEvalRoutes, renderVoiceEvalBaselineHTML, renderVoiceEvalHTML, runVoiceSessionEvals } from './evalRoutes';
5
+ export { compareVoiceEvalBaseline, createVoiceFileEvalBaselineStore, createVoiceEvalRoutes, renderVoiceEvalBaselineHTML, renderVoiceEvalHTML, renderVoiceScenarioEvalHTML, runVoiceScenarioEvals, runVoiceSessionEvals } from './evalRoutes';
6
6
  export { createVoiceSessionListRoutes, createVoiceSessionReplayHTMLHandler, createVoiceSessionReplayJSONHandler, createVoiceSessionReplayRoutes, createVoiceSessionsHTMLHandler, createVoiceSessionsJSONHandler, renderVoiceSessionsHTML, summarizeVoiceSessions, summarizeVoiceSessionReplay } from './sessionReplay';
7
7
  export { createVoiceAgent, createVoiceAgentSquad, createVoiceAgentTool } from './agent';
8
8
  export { createStoredVoiceCallReviewArtifact, createStoredVoiceExternalObjectMap, createStoredVoiceIntegrationEvent, createStoredVoiceOpsTask, createVoiceFileExternalObjectMapStore, createVoiceFileAssistantMemoryStore, createVoiceFileIntegrationEventStore, createVoiceFileReviewStore, createVoiceFileRuntimeStorage, createVoiceFileSessionStore, createVoiceFileTaskStore, createVoiceFileTraceSinkDeliveryStore, createVoiceFileTraceEventStore } from './fileStore';
@@ -40,7 +40,7 @@ export type { VoiceAssistant, VoiceAssistantArtifactPlan, VoiceAssistantExperime
40
40
  export type { VoiceAssistantHealthFailure, VoiceAssistantHealthHTMLHandlerOptions, VoiceAssistantHealthRoutesOptions, VoiceAssistantHealthSummary, VoiceAssistantHealthSummaryOptions } from './assistantHealth';
41
41
  export type { VoiceAssistantMemoryBinding, VoiceAssistantMemoryHandle, VoiceAssistantMemoryOptions, VoiceAssistantMemoryRecord, VoiceAssistantMemoryStore } from './assistantMemory';
42
42
  export type { VoiceDiagnosticsRoutesOptions } from './diagnosticsRoutes';
43
- export type { VoiceEvalBaselineComparison, VoiceEvalBaselineComparisonOptions, VoiceEvalBaselineStore, VoiceEvalBaselineSummary, VoiceEvalLink, VoiceEvalReport, VoiceEvalRoutesOptions, VoiceEvalSessionReport, VoiceEvalStatus, VoiceEvalTrendBucket } from './evalRoutes';
43
+ export type { VoiceEvalBaselineComparison, VoiceEvalBaselineComparisonOptions, VoiceEvalBaselineStore, VoiceEvalBaselineSummary, VoiceEvalLink, VoiceEvalReport, VoiceEvalRoutesOptions, VoiceEvalSessionReport, VoiceEvalStatus, VoiceEvalTrendBucket, VoiceScenarioEvalDefinition, VoiceScenarioEvalReport, VoiceScenarioEvalResult, VoiceScenarioEvalSessionResult } from './evalRoutes';
44
44
  export type { VoiceSessionListHTMLHandlerOptions, VoiceSessionListItem, VoiceSessionListOptions, VoiceSessionListRoutesOptions, VoiceSessionListStatus, VoiceSessionReplay, VoiceSessionReplayHTMLHandlerOptions, VoiceSessionReplayOptions, VoiceSessionReplayRoutesOptions, VoiceSessionReplayTurn } from './sessionReplay';
45
45
  export type { AnthropicVoiceAssistantModelOptions, GeminiVoiceAssistantModelOptions, OpenAIVoiceAssistantModelOptions, VoiceProviderRouterEvent, VoiceProviderRouterFallbackMode, VoiceProviderRouterHealthOptions, VoiceProviderRouterOptions, VoiceProviderRouterPolicy, VoiceProviderRouterProviderHealth, VoiceProviderRouterProviderProfile, VoiceJSONAssistantModelHandler, VoiceJSONAssistantModelOptions } from './modelAdapters';
46
46
  export type { VoiceProviderHealthStatus, VoiceProviderHealthSummary, VoiceProviderHealthSummaryOptions } from './providerHealth';
package/dist/index.js CHANGED
@@ -7977,6 +7977,24 @@ var createVoiceQualityRoutes = (options) => {
7977
7977
  // src/evalRoutes.ts
7978
7978
  var escapeHtml9 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
7979
7979
  var rate2 = (count, total) => count / Math.max(1, total);
7980
+ var normalizeSearchText = (value) => value.trim().toLowerCase();
7981
+ var getString6 = (value) => typeof value === "string" ? value : undefined;
7982
+ var getObject = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
7983
+ var getPathValue = (value, path) => {
7984
+ let current = value;
7985
+ for (const part of path.split(".").filter(Boolean)) {
7986
+ const record = getObject(current);
7987
+ if (!record || !(part in record)) {
7988
+ return;
7989
+ }
7990
+ current = record[part];
7991
+ }
7992
+ return current;
7993
+ };
7994
+ var includesAll = (haystack, needles) => {
7995
+ const normalized = normalizeSearchText(haystack);
7996
+ return needles.filter((needle) => !normalized.includes(normalizeSearchText(needle)));
7997
+ };
7980
7998
  var sessionTime = (events) => {
7981
7999
  const sorted = filterVoiceTraceEvents(events);
7982
8000
  return {
@@ -8047,6 +8065,108 @@ var runVoiceSessionEvals = async (options = {}) => {
8047
8065
  trend: buildTrend(limitedSessions)
8048
8066
  };
8049
8067
  };
8068
+ var getSessionText = (events, type) => events.filter((event) => event.type === type).map((event) => getString6(event.payload.text)).filter((text) => Boolean(text?.trim())).join(`
8069
+ `);
8070
+ var countProviderErrors = (events) => events.filter((event) => event.type === "session.error" && (event.payload.providerStatus === "error" || typeof event.payload.provider === "string")).length;
8071
+ var evaluateScenarioSession = (scenario, sessionId, events) => {
8072
+ const issues = [];
8073
+ const committedText = getSessionText(events, "turn.committed");
8074
+ const assistantText = getSessionText(events, "turn.assistant");
8075
+ const lifecycleTypes = events.filter((event) => event.type === "call.lifecycle").map((event) => getString6(event.payload.type)).filter((type) => Boolean(type));
8076
+ const dispositions = events.filter((event) => event.type === "call.lifecycle").map((event) => getString6(event.payload.disposition)).filter((disposition) => Boolean(disposition));
8077
+ const handoffActions = events.filter((event) => event.type === "call.handoff").map((event) => getString6(event.payload.action)).filter((action) => Boolean(action));
8078
+ const turnCount = events.filter((event) => event.type === "turn.committed").length;
8079
+ const sessionErrorCount = events.filter((event) => event.type === "session.error").length;
8080
+ const providerErrorCount = countProviderErrors(events);
8081
+ for (const missing of includesAll(committedText, scenario.requiredTranscriptIncludes ?? [])) {
8082
+ issues.push(`Missing transcript text: ${missing}`);
8083
+ }
8084
+ for (const missing of includesAll(assistantText, scenario.requiredAssistantIncludes ?? [])) {
8085
+ issues.push(`Missing assistant text: ${missing}`);
8086
+ }
8087
+ for (const type of scenario.requiredLifecycleTypes ?? []) {
8088
+ if (!lifecycleTypes.includes(type)) {
8089
+ issues.push(`Missing lifecycle event: ${type}`);
8090
+ }
8091
+ }
8092
+ for (const type of scenario.forbiddenLifecycleTypes ?? []) {
8093
+ if (lifecycleTypes.includes(type)) {
8094
+ issues.push(`Forbidden lifecycle event occurred: ${type}`);
8095
+ }
8096
+ }
8097
+ for (const action of scenario.requiredHandoffActions ?? []) {
8098
+ if (!handoffActions.includes(action)) {
8099
+ issues.push(`Missing handoff action: ${action}`);
8100
+ }
8101
+ }
8102
+ for (const action of scenario.forbiddenHandoffActions ?? []) {
8103
+ if (handoffActions.includes(action)) {
8104
+ issues.push(`Forbidden handoff action occurred: ${action}`);
8105
+ }
8106
+ }
8107
+ if (scenario.requiredDisposition && !dispositions.includes(scenario.requiredDisposition)) {
8108
+ issues.push(`Missing disposition: ${scenario.requiredDisposition}`);
8109
+ }
8110
+ if (scenario.minTurns !== undefined && turnCount < scenario.minTurns) {
8111
+ issues.push(`Expected at least ${scenario.minTurns} turn(s), saw ${turnCount}.`);
8112
+ }
8113
+ if (scenario.maxSessionErrors !== undefined && sessionErrorCount > scenario.maxSessionErrors) {
8114
+ issues.push(`Expected at most ${scenario.maxSessionErrors} session error(s), saw ${sessionErrorCount}.`);
8115
+ }
8116
+ if (scenario.maxProviderErrors !== undefined && providerErrorCount > scenario.maxProviderErrors) {
8117
+ issues.push(`Expected at most ${scenario.maxProviderErrors} provider error(s), saw ${providerErrorCount}.`);
8118
+ }
8119
+ for (const path of scenario.requiredPayloadPaths ?? []) {
8120
+ if (events.every((event) => getPathValue(event.payload, path) === undefined)) {
8121
+ issues.push(`Missing payload path: ${path}`);
8122
+ }
8123
+ }
8124
+ return {
8125
+ eventCount: events.length,
8126
+ issues,
8127
+ sessionId,
8128
+ status: issues.length > 0 ? "fail" : "pass"
8129
+ };
8130
+ };
8131
+ var runVoiceScenarioEvals = async (options = {}) => {
8132
+ const scenarios = options.scenarios ?? [];
8133
+ const events = filterVoiceTraceEvents(options.events ?? await options.store?.list() ?? []);
8134
+ const grouped = new Map;
8135
+ for (const event of events) {
8136
+ grouped.set(event.sessionId, [...grouped.get(event.sessionId) ?? [], event]);
8137
+ }
8138
+ const results = scenarios.map((scenario) => {
8139
+ const sessions = [...grouped.entries()].filter(([, sessionEvents]) => scenario.scenarioId ? sessionEvents.some((event) => event.scenarioId === scenario.scenarioId) : true).map(([sessionId, sessionEvents]) => evaluateScenarioSession(scenario, sessionId, filterVoiceTraceEvents(sessionEvents))).sort((left, right) => left.sessionId.localeCompare(right.sessionId));
8140
+ const issues = [];
8141
+ const minSessions = scenario.minSessions ?? 1;
8142
+ if (sessions.length < minSessions) {
8143
+ issues.push(`Expected at least ${minSessions} matching session(s), saw ${sessions.length}.`);
8144
+ }
8145
+ const failed2 = sessions.filter((session) => session.status === "fail").length;
8146
+ const passed2 = sessions.length - failed2;
8147
+ return {
8148
+ description: scenario.description,
8149
+ failed: failed2,
8150
+ id: scenario.id,
8151
+ issues,
8152
+ label: scenario.label ?? scenario.id,
8153
+ matchedSessions: sessions.length,
8154
+ passed: passed2,
8155
+ sessions,
8156
+ status: issues.length > 0 || failed2 > 0 ? "fail" : "pass"
8157
+ };
8158
+ });
8159
+ const failed = results.filter((scenario) => scenario.status === "fail").length;
8160
+ const passed = results.length - failed;
8161
+ return {
8162
+ checkedAt: Date.now(),
8163
+ failed,
8164
+ passed,
8165
+ scenarios: results,
8166
+ status: failed > 0 ? "fail" : "pass",
8167
+ total: results.length
8168
+ };
8169
+ };
8050
8170
  var summarizeEvalBaseline = (report) => {
8051
8171
  const failedSessionIds = report.sessions.filter((session) => session.status === "fail").map((session) => session.sessionId).sort();
8052
8172
  return {
@@ -8128,6 +8248,16 @@ var renderVoiceEvalBaselineHTML = (comparison, options = {}) => {
8128
8248
  const recovered = comparison.recoveredSessionIds.length ? comparison.recoveredSessionIds.map((id) => `<li>${escapeHtml9(id)}</li>`).join("") : "<li>none</li>";
8129
8249
  return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml9(title)}</title><style>body{font-family:ui-sans-serif,system-ui,sans-serif;margin:2rem;background:#f8f7f2;color:#181713}main{max-width:1000px;margin:auto}nav{display:flex;gap:.5rem;flex-wrap:wrap;margin-bottom:1rem}nav a{background:#181713;border-radius:999px;color:white;padding:.35rem .7rem;text-decoration:none}.status{border-radius:999px;display:inline-flex;font-weight:800;padding:.35rem .75rem}.pass{background:#dcfce7;color:#166534}.fail{background:#fee2e2;color:#991b1b}.grid{display:grid;gap:1rem;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));margin:1rem 0}.card{background:white;border:1px solid #e7e5e4;border-radius:1rem;padding:1rem}.card strong{display:block;font-size:2rem}section{background:white;border:1px solid #e7e5e4;border-radius:1rem;margin:1rem 0;padding:1rem}</style></head><body><main>${links}<h1>${escapeHtml9(title)}</h1><p class="status ${comparison.status}">${comparison.status}</p><div class="grid"><article class="card"><span>Baseline pass rate</span><strong>${escapeHtml9(formatPercent(comparison.baseline.passRate))}</strong></article><article class="card"><span>Current pass rate</span><strong>${escapeHtml9(formatPercent(comparison.current.passRate))}</strong></article><article class="card"><span>Failed delta</span><strong>${comparison.deltas.failed}</strong></article><article class="card"><span>Pass rate delta</span><strong>${escapeHtml9(formatPercent(comparison.deltas.passRate))}</strong></article></div><section><h2>Regression Reasons</h2><ul>${reasons}</ul></section><section><h2>New Failed Sessions</h2><ul>${newFailures}</ul></section><section><h2>Recovered Sessions</h2><ul>${recovered}</ul></section></main></body></html>`;
8130
8250
  };
8251
+ var renderVoiceScenarioEvalHTML = (report, options = {}) => {
8252
+ const title = options.title ?? "AbsoluteJS Voice Scenario Evals";
8253
+ const links = options.links?.length ? `<nav>${options.links.map((link) => `<a href="${escapeHtml9(link.href)}">${escapeHtml9(link.label)}</a>`).join("")}</nav>` : "";
8254
+ const scenarios = report.scenarios.length ? report.scenarios.map((scenario) => {
8255
+ const scenarioIssues = scenario.issues.length ? `<ul>${scenario.issues.map((issue) => `<li>${escapeHtml9(issue)}</li>`).join("")}</ul>` : "";
8256
+ const sessions = scenario.sessions.length ? scenario.sessions.map((session) => `<tr class="${session.status}"><td>${escapeHtml9(session.sessionId)}</td><td>${escapeHtml9(session.status)}</td><td>${session.eventCount}</td><td>${escapeHtml9(session.issues.join(", ") || "none")}</td></tr>`).join("") : '<tr><td colspan="4">No matching sessions.</td></tr>';
8257
+ return `<section class="scenario ${scenario.status}"><h2>${escapeHtml9(scenario.label)}</h2>${scenario.description ? `<p>${escapeHtml9(scenario.description)}</p>` : ""}<p class="status ${scenario.status}">${scenario.status}</p><p>${scenario.passed} passed, ${scenario.failed} failed, ${scenario.matchedSessions} matched.</p>${scenarioIssues}<table><thead><tr><th>Session</th><th>Status</th><th>Events</th><th>Issues</th></tr></thead><tbody>${sessions}</tbody></table></section>`;
8258
+ }).join("") : "<section><p>No scenarios configured.</p></section>";
8259
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml9(title)}</title><style>body{font-family:ui-sans-serif,system-ui,sans-serif;margin:2rem;background:#f8f7f2;color:#181713}main{max-width:1180px;margin:auto}nav{display:flex;gap:.5rem;flex-wrap:wrap;margin-bottom:1rem}nav a{background:#181713;border-radius:999px;color:white;padding:.35rem .7rem;text-decoration:none}.status{border-radius:999px;display:inline-flex;font-weight:800;padding:.35rem .75rem}.status.pass{background:#dcfce7;color:#166534}.status.fail{background:#fee2e2;color:#991b1b}.grid{display:grid;gap:1rem;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));margin:1rem 0}.card,section{background:white;border:1px solid #e7e5e4;border-radius:1rem;padding:1rem}.card strong{display:block;font-size:2rem}section{margin:1rem 0}table{border-collapse:collapse;width:100%;margin-top:1rem}td,th{border-bottom:1px solid #eee;padding:.75rem;text-align:left}tr.fail td{border-left:4px solid #dc2626}tr.pass td{border-left:4px solid #16a34a}</style></head><body><main>${links}<h1>${escapeHtml9(title)}</h1><p class="status ${report.status}">${report.status}</p><div class="grid"><article class="card"><span>Total</span><strong>${report.total}</strong></article><article class="card"><span>Passed</span><strong>${report.passed}</strong></article><article class="card"><span>Failed</span><strong>${report.failed}</strong></article></div>${scenarios}</main></body></html>`;
8260
+ };
8131
8261
  var createVoiceEvalRoutes = (options) => {
8132
8262
  const path = options.path ?? "/evals";
8133
8263
  const routes = new Elysia7({
@@ -8144,6 +8274,11 @@ var createVoiceEvalRoutes = (options) => {
8144
8274
  const [current, baseline] = await Promise.all([getReport(), getBaseline()]);
8145
8275
  return baseline ? compareVoiceEvalBaseline(current, baseline, options.baselineComparison) : undefined;
8146
8276
  };
8277
+ const getScenarioReport = () => runVoiceScenarioEvals({
8278
+ events: options.events,
8279
+ scenarios: options.scenarios,
8280
+ store: options.store
8281
+ });
8147
8282
  routes.get(path, async () => {
8148
8283
  const report = await getReport();
8149
8284
  return new Response(renderVoiceEvalHTML(report, {
@@ -8211,11 +8346,31 @@ var createVoiceEvalRoutes = (options) => {
8211
8346
  status: "saved"
8212
8347
  };
8213
8348
  });
8349
+ routes.get(`${path}/scenarios`, async () => {
8350
+ const report = await getScenarioReport();
8351
+ return new Response(renderVoiceScenarioEvalHTML(report, {
8352
+ links: options.links,
8353
+ title: `${options.title ?? "AbsoluteJS Voice Evals"} Scenarios`
8354
+ }), {
8355
+ headers: {
8356
+ "Content-Type": "text/html; charset=utf-8",
8357
+ ...options.headers
8358
+ }
8359
+ });
8360
+ });
8361
+ routes.get(`${path}/scenarios/json`, async () => getScenarioReport());
8362
+ routes.get(`${path}/scenarios/status`, async ({ set }) => {
8363
+ const report = await getScenarioReport();
8364
+ if (report.status === "fail") {
8365
+ set.status = 503;
8366
+ }
8367
+ return report;
8368
+ });
8214
8369
  return routes;
8215
8370
  };
8216
8371
  // src/sessionReplay.ts
8217
8372
  import { Elysia as Elysia8 } from "elysia";
8218
- var getString6 = (value) => typeof value === "string" ? value : undefined;
8373
+ var getString7 = (value) => typeof value === "string" ? value : undefined;
8219
8374
  var escapeHtml10 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
8220
8375
  var increment3 = (record, key) => {
8221
8376
  record[key] = (record[key] ?? 0) + 1;
@@ -8245,14 +8400,14 @@ var buildReplayTurns = (events) => {
8245
8400
  case "turn.transcript":
8246
8401
  turn.transcripts.push({
8247
8402
  isFinal: event.payload.isFinal === true,
8248
- text: getString6(event.payload.text)
8403
+ text: getString7(event.payload.text)
8249
8404
  });
8250
8405
  break;
8251
8406
  case "turn.committed":
8252
- turn.committedText = getString6(event.payload.text);
8407
+ turn.committedText = getString7(event.payload.text);
8253
8408
  break;
8254
8409
  case "turn.assistant": {
8255
- const text = getString6(event.payload.text);
8410
+ const text = getString7(event.payload.text);
8256
8411
  if (text) {
8257
8412
  turn.assistantReplies.push(text);
8258
8413
  }
@@ -8321,7 +8476,7 @@ var summarizeVoiceSessions = async (options = {}) => {
8321
8476
  let latestOutcome;
8322
8477
  let errorCount = 0;
8323
8478
  for (const event of sorted) {
8324
- const provider = getString6(event.payload.provider);
8479
+ const provider = getString7(event.payload.provider);
8325
8480
  if (provider) {
8326
8481
  providers.add(provider);
8327
8482
  }
@@ -8329,7 +8484,7 @@ var summarizeVoiceSessions = async (options = {}) => {
8329
8484
  errorCount += 1;
8330
8485
  increment3(providerErrors, provider ?? "unknown");
8331
8486
  }
8332
- const outcome = getString6(event.payload.outcome);
8487
+ const outcome = getString7(event.payload.outcome);
8333
8488
  if (outcome) {
8334
8489
  latestOutcome = outcome;
8335
8490
  }
@@ -9589,7 +9744,7 @@ import { Elysia as Elysia10 } from "elysia";
9589
9744
  // src/resilienceRoutes.ts
9590
9745
  import { Elysia as Elysia9 } from "elysia";
9591
9746
  var escapeHtml11 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
9592
- var getString7 = (value) => typeof value === "string" ? value : undefined;
9747
+ var getString8 = (value) => typeof value === "string" ? value : undefined;
9593
9748
  var getNumber4 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
9594
9749
  var getBoolean2 = (value) => value === true;
9595
9750
  var isProviderStatus2 = (value) => value === "error" || value === "fallback" || value === "success";
@@ -9599,23 +9754,23 @@ var listVoiceRoutingEvents = (events) => {
9599
9754
  if (event.type !== "session.error") {
9600
9755
  continue;
9601
9756
  }
9602
- const provider = getString7(event.payload.provider);
9757
+ const provider = getString8(event.payload.provider);
9603
9758
  const providerStatus = isProviderStatus2(event.payload.providerStatus) ? event.payload.providerStatus : undefined;
9604
9759
  if (!provider || !providerStatus) {
9605
9760
  continue;
9606
9761
  }
9607
- const kind = getString7(event.payload.kind);
9762
+ const kind = getString8(event.payload.kind);
9608
9763
  routingEvents.push({
9609
9764
  at: event.at,
9610
9765
  attempt: getNumber4(event.payload.attempt),
9611
9766
  elapsedMs: getNumber4(event.payload.elapsedMs),
9612
- error: getString7(event.payload.error),
9613
- fallbackProvider: getString7(event.payload.fallbackProvider),
9767
+ error: getString8(event.payload.error),
9768
+ fallbackProvider: getString8(event.payload.fallbackProvider),
9614
9769
  kind: kind === "stt" || kind === "tts" ? kind : "llm",
9615
9770
  latencyBudgetMs: getNumber4(event.payload.latencyBudgetMs),
9616
- operation: getString7(event.payload.operation),
9771
+ operation: getString8(event.payload.operation),
9617
9772
  provider,
9618
- selectedProvider: getString7(event.payload.selectedProvider),
9773
+ selectedProvider: getString8(event.payload.selectedProvider),
9619
9774
  sessionId: event.sessionId,
9620
9775
  status: providerStatus,
9621
9776
  timedOut: getBoolean2(event.payload.timedOut),
@@ -13157,6 +13312,7 @@ export {
13157
13312
  shapeTelephonyAssistantText,
13158
13313
  selectVoiceTraceEventsForPrune,
13159
13314
  runVoiceSessionEvals,
13315
+ runVoiceScenarioEvals,
13160
13316
  resolveVoiceTraceRedactionOptions,
13161
13317
  resolveVoiceSTTRoutingStrategy,
13162
13318
  resolveVoiceRuntimePreset,
@@ -13174,6 +13330,7 @@ export {
13174
13330
  renderVoiceTraceMarkdown,
13175
13331
  renderVoiceTraceHTML,
13176
13332
  renderVoiceSessionsHTML,
13333
+ renderVoiceScenarioEvalHTML,
13177
13334
  renderVoiceResilienceHTML,
13178
13335
  renderVoiceQualityHTML,
13179
13336
  renderVoiceProviderHealthHTML,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.43",
3
+ "version": "0.0.22-beta.44",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",