@absolutejs/voice 0.0.22-beta.201 → 0.0.22-beta.203

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.
@@ -5,6 +5,7 @@ export type VoiceEvalStatus = 'pass' | 'fail';
5
5
  export type VoiceEvalSessionReport = {
6
6
  endedAt?: number;
7
7
  eventCount: number;
8
+ operationsRecordHref?: string;
8
9
  quality: VoiceQualityReport;
9
10
  scenarioId?: string;
10
11
  sessionId: string;
@@ -81,6 +82,7 @@ export type VoiceScenarioEvalDefinition = {
81
82
  export type VoiceScenarioEvalSessionResult = {
82
83
  eventCount: number;
83
84
  issues: string[];
85
+ operationsRecordHref?: string;
84
86
  sessionId: string;
85
87
  status: VoiceEvalStatus;
86
88
  };
@@ -142,6 +144,7 @@ export type VoiceEvalRoutesOptions = {
142
144
  links?: VoiceEvalLink[];
143
145
  limit?: number;
144
146
  name?: string;
147
+ operationsRecordHref?: false | string | ((sessionId: string) => string);
145
148
  path?: string;
146
149
  scenarios?: VoiceScenarioEvalDefinition[];
147
150
  store?: VoiceTraceEventStore;
@@ -151,17 +154,20 @@ export type VoiceEvalRoutesOptions = {
151
154
  export declare const runVoiceSessionEvals: (options?: {
152
155
  events?: StoredVoiceTraceEvent[];
153
156
  limit?: number;
157
+ operationsRecordHref?: false | string | ((sessionId: string) => string);
154
158
  store?: VoiceTraceEventStore;
155
159
  thresholds?: VoiceQualityThresholds;
156
160
  }) => Promise<VoiceEvalReport>;
157
161
  export declare const runVoiceScenarioEvals: (options?: {
158
162
  events?: StoredVoiceTraceEvent[];
163
+ operationsRecordHref?: false | string | ((sessionId: string) => string);
159
164
  scenarios?: VoiceScenarioEvalDefinition[];
160
165
  store?: VoiceTraceEventStore;
161
166
  }) => Promise<VoiceScenarioEvalReport>;
162
167
  export declare const runVoiceScenarioFixtureEvals: (options?: {
163
168
  fixtures?: VoiceScenarioFixture[];
164
169
  fixtureStore?: VoiceScenarioFixtureStore;
170
+ operationsRecordHref?: false | string | ((sessionId: string) => string);
165
171
  scenarios?: VoiceScenarioEvalDefinition[];
166
172
  }) => Promise<VoiceScenarioFixtureEvalReport>;
167
173
  export declare const compareVoiceEvalBaseline: (currentReport: VoiceEvalReport, baselineReport: VoiceEvalReport, options?: VoiceEvalBaselineComparisonOptions) => VoiceEvalBaselineComparison;
package/dist/index.js CHANGED
@@ -13573,6 +13573,17 @@ var escapeHtml20 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&l
13573
13573
  var rate2 = (count, total) => count / Math.max(1, total);
13574
13574
  var normalizeSearchText = (value) => value.trim().toLowerCase();
13575
13575
  var getString9 = (value) => typeof value === "string" ? value : undefined;
13576
+ var resolveSessionHref = (value, sessionId) => {
13577
+ if (value === false) {
13578
+ return;
13579
+ }
13580
+ const href = value ?? "/voice-operations/:sessionId";
13581
+ if (typeof href === "function") {
13582
+ return href(sessionId);
13583
+ }
13584
+ const encoded = encodeURIComponent(sessionId);
13585
+ return href.includes(":sessionId") ? href.replace(":sessionId", encoded) : `${href.replace(/\/$/, "")}/${encoded}`;
13586
+ };
13576
13587
  var getObject = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
13577
13588
  var getPathValue = (value, path) => {
13578
13589
  let current = value;
@@ -13638,6 +13649,7 @@ var runVoiceSessionEvals = async (options = {}) => {
13638
13649
  return {
13639
13650
  endedAt,
13640
13651
  eventCount: sorted.length,
13652
+ operationsRecordHref: resolveSessionHref(options.operationsRecordHref, sessionId),
13641
13653
  quality,
13642
13654
  scenarioId,
13643
13655
  sessionId,
@@ -13662,7 +13674,7 @@ var runVoiceSessionEvals = async (options = {}) => {
13662
13674
  var getSessionText = (events, type) => events.filter((event) => event.type === type).map((event) => getString9(event.payload.text)).filter((text) => Boolean(text?.trim())).join(`
13663
13675
  `);
13664
13676
  var countProviderErrors = (events) => events.filter((event) => event.type === "session.error" && (event.payload.providerStatus === "error" || typeof event.payload.provider === "string")).length;
13665
- var evaluateScenarioSession = (scenario, sessionId, events) => {
13677
+ var evaluateScenarioSession = (scenario, sessionId, events, operationsRecordHref) => {
13666
13678
  const issues = [];
13667
13679
  const committedText = getSessionText(events, "turn.committed");
13668
13680
  const assistantText = getSessionText(events, "turn.assistant");
@@ -13729,6 +13741,7 @@ var evaluateScenarioSession = (scenario, sessionId, events) => {
13729
13741
  return {
13730
13742
  eventCount: events.length,
13731
13743
  issues,
13744
+ operationsRecordHref,
13732
13745
  sessionId,
13733
13746
  status: issues.length > 0 ? "fail" : "pass"
13734
13747
  };
@@ -13741,7 +13754,7 @@ var runVoiceScenarioEvals = async (options = {}) => {
13741
13754
  grouped.set(event.sessionId, [...grouped.get(event.sessionId) ?? [], event]);
13742
13755
  }
13743
13756
  const results = scenarios.map((scenario) => {
13744
- 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));
13757
+ const sessions = [...grouped.entries()].filter(([, sessionEvents]) => scenario.scenarioId ? sessionEvents.some((event) => event.scenarioId === scenario.scenarioId) : true).map(([sessionId, sessionEvents]) => evaluateScenarioSession(scenario, sessionId, filterVoiceTraceEvents(sessionEvents), resolveSessionHref(options.operationsRecordHref, sessionId))).sort((left, right) => left.sessionId.localeCompare(right.sessionId));
13745
13758
  const issues = [];
13746
13759
  const minSessions = scenario.minSessions ?? 1;
13747
13760
  if (sessions.length < minSessions) {
@@ -13778,6 +13791,7 @@ var runVoiceScenarioFixtureEvals = async (options = {}) => {
13778
13791
  const results = await Promise.all(fixtures.map(async (fixture) => {
13779
13792
  const report = await runVoiceScenarioEvals({
13780
13793
  events: fixture.events,
13794
+ operationsRecordHref: options.operationsRecordHref,
13781
13795
  scenarios: options.scenarios
13782
13796
  });
13783
13797
  return {
@@ -13902,7 +13916,8 @@ var renderVoiceEvalHTML = (report, options = {}) => {
13902
13916
  const trend = report.trend.length ? report.trend.map((bucket) => `<tr><td>${escapeHtml20(bucket.key)}</td><td>${bucket.total}</td><td>${bucket.passed}</td><td>${bucket.failed}</td></tr>`).join("") : '<tr><td colspan="4">No eval buckets yet.</td></tr>';
13903
13917
  const sessions = report.sessions.length ? report.sessions.map((session) => {
13904
13918
  const failedMetrics = Object.entries(session.quality.metrics).filter(([, metric]) => !metric.pass).map(([, metric]) => metric.label).join(", ");
13905
- return `<tr class="${session.status}"><td>${escapeHtml20(session.sessionId)}</td><td>${escapeHtml20(session.status)}</td><td>${session.eventCount}</td><td>${session.summary.turnCount}</td><td>${session.summary.errorCount}</td><td>${escapeHtml20(formatTime(session.endedAt))}</td><td>${escapeHtml20(failedMetrics || "none")}</td></tr>`;
13919
+ const sessionLabel = session.operationsRecordHref ? `<a href="${escapeHtml20(session.operationsRecordHref)}">${escapeHtml20(session.sessionId)}</a>` : escapeHtml20(session.sessionId);
13920
+ return `<tr class="${session.status}"><td>${sessionLabel}</td><td>${escapeHtml20(session.status)}</td><td>${session.eventCount}</td><td>${session.summary.turnCount}</td><td>${session.summary.errorCount}</td><td>${escapeHtml20(formatTime(session.endedAt))}</td><td>${escapeHtml20(failedMetrics || "none")}</td></tr>`;
13906
13921
  }).join("") : '<tr><td colspan="7">No sessions found.</td></tr>';
13907
13922
  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{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}.eyebrow{font-size:.78rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}.status{border-radius:999px;display:inline-flex;font-weight:800;padding:.35rem .75rem}.pass{color:#166534}.fail{color:#991b1b}.status.pass{background:#dcfce7}.status.fail{background:#fee2e2}.grid{display:grid;gap:1rem;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));margin:1rem 0}.card,.primitive{background:white;border:1px solid #e7e5e4;border-radius:1rem;padding:1rem}.primitive{background:#fffdf7;border-color:#d6c7a3;margin:1rem 0}.primitive p{line-height:1.55}.primitive pre{background:#181713;border-radius:.85rem;color:#fef3c7;overflow:auto;padding:1rem}.primitive code{color:#fef3c7}.card strong{display:block;font-size:2rem}table{border-collapse:collapse;background:white;width:100%;margin:1rem 0 2rem}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>${escapeHtml20(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>${renderVoiceEvalPrimitiveCopy()}<h2>Trend</h2><table><thead><tr><th>Day</th><th>Total</th><th>Passed</th><th>Failed</th></tr></thead><tbody>${trend}</tbody></table><h2>Session Eval Results</h2><table><thead><tr><th>Session</th><th>Status</th><th>Events</th><th>Turns</th><th>Errors</th><th>Last event</th><th>Failed metrics</th></tr></thead><tbody>${sessions}</tbody></table></main></body></html>`;
13908
13923
  };
@@ -13919,7 +13934,10 @@ var renderVoiceScenarioEvalHTML = (report, options = {}) => {
13919
13934
  const links = options.links?.length ? `<nav>${options.links.map((link) => `<a href="${escapeHtml20(link.href)}">${escapeHtml20(link.label)}</a>`).join("")}</nav>` : "";
13920
13935
  const scenarios = report.scenarios.length ? report.scenarios.map((scenario) => {
13921
13936
  const scenarioIssues = scenario.issues.length ? `<ul>${scenario.issues.map((issue) => `<li>${escapeHtml20(issue)}</li>`).join("")}</ul>` : "";
13922
- const sessions = scenario.sessions.length ? scenario.sessions.map((session) => `<tr class="${session.status}"><td>${escapeHtml20(session.sessionId)}</td><td>${escapeHtml20(session.status)}</td><td>${session.eventCount}</td><td>${escapeHtml20(session.issues.join(", ") || "none")}</td></tr>`).join("") : '<tr><td colspan="4">No matching sessions.</td></tr>';
13937
+ const sessions = scenario.sessions.length ? scenario.sessions.map((session) => {
13938
+ const sessionLabel = session.operationsRecordHref ? `<a href="${escapeHtml20(session.operationsRecordHref)}">${escapeHtml20(session.sessionId)}</a>` : escapeHtml20(session.sessionId);
13939
+ return `<tr class="${session.status}"><td>${sessionLabel}</td><td>${escapeHtml20(session.status)}</td><td>${session.eventCount}</td><td>${escapeHtml20(session.issues.join(", ") || "none")}</td></tr>`;
13940
+ }).join("") : '<tr><td colspan="4">No matching sessions.</td></tr>';
13923
13941
  return `<section class="scenario ${scenario.status}"><h2>${escapeHtml20(scenario.label)}</h2>${scenario.description ? `<p>${escapeHtml20(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>`;
13924
13942
  }).join("") : "<section><p>No scenarios configured.</p></section>";
13925
13943
  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{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}.eyebrow{font-size:.78rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}.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}.primitive{background:#fffdf7;border-color:#d6c7a3}.primitive p{line-height:1.55}.primitive pre{background:#181713;border-radius:.85rem;color:#fef3c7;overflow:auto;padding:1rem}.primitive code{color:#fef3c7}.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>${escapeHtml20(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>${renderVoiceEvalPrimitiveCopy()}${scenarios}</main></body></html>`;
@@ -13941,6 +13959,7 @@ var createVoiceEvalRoutes = (options) => {
13941
13959
  const getReport = () => runVoiceSessionEvals({
13942
13960
  events: options.events,
13943
13961
  limit: options.limit,
13962
+ operationsRecordHref: options.operationsRecordHref,
13944
13963
  store: options.store,
13945
13964
  thresholds: options.thresholds
13946
13965
  });
@@ -13951,12 +13970,14 @@ var createVoiceEvalRoutes = (options) => {
13951
13970
  };
13952
13971
  const getScenarioReport = () => runVoiceScenarioEvals({
13953
13972
  events: options.events,
13973
+ operationsRecordHref: options.operationsRecordHref,
13954
13974
  scenarios: options.scenarios,
13955
13975
  store: options.store
13956
13976
  });
13957
13977
  const getFixtureReport = () => runVoiceScenarioFixtureEvals({
13958
13978
  fixtures: options.fixtures,
13959
13979
  fixtureStore: options.fixtureStore,
13980
+ operationsRecordHref: options.operationsRecordHref,
13960
13981
  scenarios: options.scenarios
13961
13982
  });
13962
13983
  routes.get(path, async () => {
@@ -14684,7 +14705,7 @@ var collectSimulationActions = (input) => {
14684
14705
  const firstFailed = input.sessions.sessions.find((session) => session.status === "fail");
14685
14706
  actions.push({
14686
14707
  description: firstFailed ? `Inspect session ${firstFailed.sessionId}; at least one quality metric is outside threshold.` : "Inspect failing session quality reports.",
14687
- href: input.links?.sessions,
14708
+ href: firstFailed?.operationsRecordHref ?? input.links?.sessions,
14688
14709
  label: "Review failing session quality",
14689
14710
  section: "sessions",
14690
14711
  severity: "error"
@@ -14695,9 +14716,10 @@ var collectSimulationActions = (input) => {
14695
14716
  continue;
14696
14717
  }
14697
14718
  const issue = scenario.issues[0] ?? scenario.sessions.find((session) => session.issues.length > 0)?.issues[0] ?? "Scenario did not meet its expected trace conditions.";
14719
+ const failedSession = scenario.sessions.find((session) => session.status === "fail");
14698
14720
  actions.push({
14699
14721
  description: `${scenario.label}: ${issue}`,
14700
- href: input.links?.scenarios,
14722
+ href: failedSession?.operationsRecordHref ?? input.links?.scenarios,
14701
14723
  label: `Fix scenario ${scenario.label}`,
14702
14724
  section: "scenarios",
14703
14725
  severity: "error"
@@ -14708,9 +14730,10 @@ var collectSimulationActions = (input) => {
14708
14730
  continue;
14709
14731
  }
14710
14732
  const failedScenario = fixture.report.scenarios.find((scenario) => scenario.status === "fail");
14733
+ const failedSession = failedScenario?.sessions.find((session) => session.status === "fail");
14711
14734
  actions.push({
14712
14735
  description: failedScenario ? `${fixture.label}: ${failedScenario.label} failed.` : `${fixture.label}: fixture simulation failed.`,
14713
- href: input.links?.fixtures,
14736
+ href: failedSession?.operationsRecordHref ?? input.links?.fixtures,
14714
14737
  label: `Update fixture ${fixture.label}`,
14715
14738
  section: "fixtures",
14716
14739
  severity: "error"
@@ -14753,16 +14776,19 @@ var runVoiceSimulationSuite = async (options) => {
14753
14776
  const [sessions, scenarios, fixtures, tools, outcomes] = await Promise.all([
14754
14777
  shouldRunSessions ? runVoiceSessionEvals({
14755
14778
  limit: options.limit,
14779
+ operationsRecordHref: options.operationsRecordHref,
14756
14780
  store: options.store,
14757
14781
  thresholds: options.thresholds
14758
14782
  }) : undefined,
14759
14783
  shouldRunScenarios ? runVoiceScenarioEvals({
14784
+ operationsRecordHref: options.operationsRecordHref,
14760
14785
  scenarios: options.scenarios,
14761
14786
  store: options.store
14762
14787
  }) : undefined,
14763
14788
  shouldRunFixtures ? runVoiceScenarioFixtureEvals({
14764
14789
  fixtures: options.fixtures,
14765
14790
  fixtureStore: options.fixtureStore,
14791
+ operationsRecordHref: options.operationsRecordHref,
14766
14792
  scenarios: options.scenarios
14767
14793
  }) : undefined,
14768
14794
  shouldRunTools ? runVoiceToolContractSuite({
@@ -15170,7 +15196,7 @@ var escapeHtml24 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&l
15170
15196
  var increment4 = (record, key) => {
15171
15197
  record[key] = (record[key] ?? 0) + 1;
15172
15198
  };
15173
- var resolveSessionHref = (value, session) => {
15199
+ var resolveSessionHref2 = (value, session) => {
15174
15200
  if (value === false) {
15175
15201
  return;
15176
15202
  }
@@ -15334,7 +15360,7 @@ var summarizeVoiceSessions = async (options = {}) => {
15334
15360
  const replayHref = options.replayHref === false ? "" : typeof options.replayHref === "function" ? options.replayHref(item) : `${options.replayHref ?? "/api/voice-sessions"}/${encodeURIComponent(sessionId)}/replay/htmx`;
15335
15361
  return {
15336
15362
  ...item,
15337
- operationsRecordHref: resolveSessionHref(options.operationsRecordHref, item),
15363
+ operationsRecordHref: resolveSessionHref2(options.operationsRecordHref, item),
15338
15364
  replayHref
15339
15365
  };
15340
15366
  });
@@ -23959,7 +23985,7 @@ var eventStatus = (event) => firstString3(event.payload, [
23959
23985
  "reason"
23960
23986
  ]);
23961
23987
  var eventElapsedMs2 = (event) => firstNumber3(event.payload, ["elapsedMs", "latencyMs", "durationMs"]);
23962
- var resolveSessionHref2 = (value, sessionId) => {
23988
+ var resolveSessionHref3 = (value, sessionId) => {
23963
23989
  if (value === false) {
23964
23990
  return;
23965
23991
  }
@@ -24089,7 +24115,7 @@ var summarizeVoiceTraceTimeline = (events, options = {}) => {
24089
24115
  type: event.type
24090
24116
  })),
24091
24117
  lastEventAt: sorted.at(-1)?.at,
24092
- operationsRecordHref: resolveSessionHref2(options.operationsRecordHref, sessionId),
24118
+ operationsRecordHref: resolveSessionHref3(options.operationsRecordHref, sessionId),
24093
24119
  providers: summarizeProviders(sorted),
24094
24120
  sessionId,
24095
24121
  startedAt: summary.startedAt,
@@ -57,6 +57,7 @@ export type VoiceSimulationSuiteOptions<TSession extends VoiceSessionRecord = Vo
57
57
  tools?: boolean;
58
58
  };
59
59
  limit?: number;
60
+ operationsRecordHref?: false | string | ((sessionId: string) => string);
60
61
  outcomes?: Omit<VoiceOutcomeContractOptions<TSession>, 'contracts'> & {
61
62
  contracts: VoiceOutcomeContractDefinition[];
62
63
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.201",
3
+ "version": "0.0.22-beta.203",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",