@absolutejs/voice 0.0.22-beta.37 → 0.0.22-beta.38

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.
@@ -0,0 +1,44 @@
1
+ import { Elysia } from 'elysia';
2
+ import { evaluateVoiceTrace, type StoredVoiceTraceEvent, type VoiceTraceEventFilter, type VoiceTraceEventStore, type VoiceTraceRedactionConfig } from './trace';
3
+ export type VoiceDiagnosticsRoutesOptions = {
4
+ evaluation?: Parameters<typeof evaluateVoiceTrace>[1];
5
+ headers?: HeadersInit;
6
+ name?: string;
7
+ path?: string;
8
+ redact?: VoiceTraceRedactionConfig;
9
+ store: VoiceTraceEventStore;
10
+ title?: string;
11
+ };
12
+ export declare const resolveVoiceDiagnosticsTraceFilter: (query: Record<string, unknown>) => VoiceTraceEventFilter;
13
+ export declare const buildVoiceDiagnosticsMarkdown: (events: StoredVoiceTraceEvent[], options?: {
14
+ evaluation?: Parameters<typeof evaluateVoiceTrace>[1];
15
+ title?: string;
16
+ }) => string;
17
+ export declare const createVoiceDiagnosticsRoutes: (options: VoiceDiagnosticsRoutesOptions) => Elysia<"", {
18
+ decorator: {};
19
+ store: {};
20
+ derive: {};
21
+ resolve: {};
22
+ }, {
23
+ typebox: {};
24
+ error: {};
25
+ }, {
26
+ schema: {};
27
+ standaloneSchema: {};
28
+ macro: {};
29
+ macroFn: {};
30
+ parser: {};
31
+ response: {};
32
+ }, {}, {
33
+ derive: {};
34
+ resolve: {};
35
+ schema: {};
36
+ standaloneSchema: {};
37
+ response: {};
38
+ }, {
39
+ derive: {};
40
+ resolve: {};
41
+ schema: {};
42
+ standaloneSchema: {};
43
+ response: {};
44
+ }>;
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export { voice } from './plugin';
2
2
  export { createVoiceAssistant, createVoiceExperiment, summarizeVoiceAssistantRuns } from './assistant';
3
3
  export { createVoiceAssistantHealthHTMLHandler, createVoiceAssistantHealthJSONHandler, createVoiceAssistantHealthRoutes, renderVoiceAssistantHealthHTML, summarizeVoiceAssistantHealth } from './assistantHealth';
4
+ export { buildVoiceDiagnosticsMarkdown, createVoiceDiagnosticsRoutes, resolveVoiceDiagnosticsTraceFilter } from './diagnosticsRoutes';
4
5
  export { createVoiceSessionListRoutes, createVoiceSessionReplayHTMLHandler, createVoiceSessionReplayJSONHandler, createVoiceSessionReplayRoutes, createVoiceSessionsHTMLHandler, createVoiceSessionsJSONHandler, renderVoiceSessionsHTML, summarizeVoiceSessions, summarizeVoiceSessionReplay } from './sessionReplay';
5
6
  export { createVoiceAgent, createVoiceAgentSquad, createVoiceAgentTool } from './agent';
6
7
  export { createStoredVoiceCallReviewArtifact, createStoredVoiceExternalObjectMap, createStoredVoiceIntegrationEvent, createStoredVoiceOpsTask, createVoiceFileExternalObjectMapStore, createVoiceFileAssistantMemoryStore, createVoiceFileIntegrationEventStore, createVoiceFileReviewStore, createVoiceFileRuntimeStorage, createVoiceFileSessionStore, createVoiceFileTaskStore, createVoiceFileTraceSinkDeliveryStore, createVoiceFileTraceEventStore } from './fileStore';
@@ -35,6 +36,7 @@ export { createVoiceCallReviewFromLiveTelephonyReport, createVoiceCallReviewReco
35
36
  export type { VoiceAssistant, VoiceAssistantArtifactPlan, VoiceAssistantExperiment, VoiceAssistantExperimentOptions, VoiceAssistantGuardrailInput, VoiceAssistantGuardrails, VoiceAssistantMemoryLifecycle, VoiceAssistantMemoryLifecycleInput, VoiceAssistantOptions, VoiceAssistantOutputGuardrailInput, VoiceAssistantPreset, VoiceAssistantRunsSummary, VoiceAssistantRunSummary, VoiceAssistantVariant } from './assistant';
36
37
  export type { VoiceAssistantHealthFailure, VoiceAssistantHealthHTMLHandlerOptions, VoiceAssistantHealthRoutesOptions, VoiceAssistantHealthSummary, VoiceAssistantHealthSummaryOptions } from './assistantHealth';
37
38
  export type { VoiceAssistantMemoryBinding, VoiceAssistantMemoryHandle, VoiceAssistantMemoryOptions, VoiceAssistantMemoryRecord, VoiceAssistantMemoryStore } from './assistantMemory';
39
+ export type { VoiceDiagnosticsRoutesOptions } from './diagnosticsRoutes';
38
40
  export type { VoiceSessionListHTMLHandlerOptions, VoiceSessionListItem, VoiceSessionListOptions, VoiceSessionListRoutesOptions, VoiceSessionListStatus, VoiceSessionReplay, VoiceSessionReplayHTMLHandlerOptions, VoiceSessionReplayOptions, VoiceSessionReplayRoutesOptions, VoiceSessionReplayTurn } from './sessionReplay';
39
41
  export type { AnthropicVoiceAssistantModelOptions, GeminiVoiceAssistantModelOptions, OpenAIVoiceAssistantModelOptions, VoiceProviderRouterEvent, VoiceProviderRouterFallbackMode, VoiceProviderRouterHealthOptions, VoiceProviderRouterOptions, VoiceProviderRouterPolicy, VoiceProviderRouterProviderHealth, VoiceProviderRouterProviderProfile, VoiceJSONAssistantModelHandler, VoiceJSONAssistantModelOptions } from './modelAdapters';
40
42
  export type { VoiceProviderHealthStatus, VoiceProviderHealthSummary, VoiceProviderHealthSummaryOptions } from './providerHealth';
package/dist/index.js CHANGED
@@ -6822,7 +6822,7 @@ var createVoiceAssistantHealthRoutes = (options) => {
6822
6822
  }
6823
6823
  return routes;
6824
6824
  };
6825
- // src/sessionReplay.ts
6825
+ // src/diagnosticsRoutes.ts
6826
6826
  import { Elysia as Elysia4 } from "elysia";
6827
6827
 
6828
6828
  // src/trace.ts
@@ -7489,9 +7489,144 @@ var buildVoiceTraceReplay = (events, options = {}) => ({
7489
7489
  summary: summarizeVoiceTrace(options.redact ? redactVoiceTraceEvents(events, options.redact) : events)
7490
7490
  });
7491
7491
 
7492
- // src/sessionReplay.ts
7493
- var getString3 = (value) => typeof value === "string" ? value : undefined;
7492
+ // src/diagnosticsRoutes.ts
7494
7493
  var escapeHtml6 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
7494
+ var getString3 = (value) => typeof value === "string" && value.trim() ? value : undefined;
7495
+ var getNumber2 = (value) => {
7496
+ const parsed = typeof value === "number" ? value : typeof value === "string" ? Number(value) : undefined;
7497
+ return typeof parsed === "number" && Number.isFinite(parsed) ? parsed : undefined;
7498
+ };
7499
+ var getBoolean = (value) => value === true || value === "true" || value === "1";
7500
+ var parseTraceTypeFilter = (value) => {
7501
+ if (typeof value !== "string" || !value.trim()) {
7502
+ return;
7503
+ }
7504
+ const types = value.split(",").map((entry) => entry.trim()).filter(Boolean);
7505
+ return types.length <= 1 ? types[0] : types;
7506
+ };
7507
+ var resolveVoiceDiagnosticsTraceFilter = (query) => ({
7508
+ limit: getNumber2(query.limit),
7509
+ scenarioId: getString3(query.scenarioId),
7510
+ sessionId: getString3(query.sessionId),
7511
+ traceId: getString3(query.traceId),
7512
+ turnId: getString3(query.turnId),
7513
+ type: parseTraceTypeFilter(query.type)
7514
+ });
7515
+ var filterByDiagnosticsQuery = (events, query) => {
7516
+ const provider = getString3(query.provider);
7517
+ const status = getString3(query.status);
7518
+ const since = getNumber2(query.since);
7519
+ const until = getNumber2(query.until);
7520
+ return filterVoiceTraceEvents(events, resolveVoiceDiagnosticsTraceFilter(query)).filter((event) => (!provider || event.payload.provider === provider) && (!status || event.payload.providerStatus === status || event.payload.status === status) && (since === undefined || event.at >= since) && (until === undefined || event.at <= until));
7521
+ };
7522
+ var buildVoiceDiagnosticsMarkdown = (events, options = {}) => {
7523
+ const summary = summarizeVoiceTrace(events);
7524
+ const evaluation = evaluateVoiceTrace(events, options.evaluation);
7525
+ const trace = renderVoiceTraceMarkdown(events, {
7526
+ evaluation: options.evaluation,
7527
+ title: options.title ?? `Voice Diagnostics ${summary.sessionId ?? ""}`.trim()
7528
+ });
7529
+ return [
7530
+ `# ${options.title ?? "Voice Diagnostics Bug Report"}`,
7531
+ "",
7532
+ `Session: ${summary.sessionId ?? "unknown"}`,
7533
+ `Pass: ${evaluation.pass ? "yes" : "no"}`,
7534
+ `Events: ${summary.eventCount}`,
7535
+ `Turns: ${summary.turnCount}`,
7536
+ `Errors: ${summary.errorCount}`,
7537
+ `Tool errors: ${summary.toolErrorCount}`,
7538
+ `Estimated cost units: ${summary.cost.estimatedRelativeCostUnits}`,
7539
+ "",
7540
+ "## Issues",
7541
+ "",
7542
+ evaluation.issues.length ? evaluation.issues.map((issue) => `- [${issue.severity}] ${issue.code}: ${issue.message}`).join(`
7543
+ `) : "- none",
7544
+ "",
7545
+ "## Trace",
7546
+ "",
7547
+ trace
7548
+ ].join(`
7549
+ `);
7550
+ };
7551
+ var renderDiagnosticsIndex = (input) => {
7552
+ const sessions = new Map;
7553
+ for (const event of input.events) {
7554
+ sessions.set(event.sessionId, [...sessions.get(event.sessionId) ?? [], event]);
7555
+ }
7556
+ const rows = [...sessions.entries()].sort(([, left], [, right]) => (right.at(-1)?.at ?? 0) - (left.at(-1)?.at ?? 0)).slice(0, 50).map(([sessionId, events]) => {
7557
+ const summary = summarizeVoiceTrace(events);
7558
+ const encoded = encodeURIComponent(sessionId);
7559
+ return `<tr><td>${escapeHtml6(sessionId)}</td><td>${summary.eventCount}</td><td>${summary.turnCount}</td><td>${summary.errorCount}</td><td><a href="${input.basePath}/html?sessionId=${encoded}&redact=true">HTML</a> \xB7 <a href="${input.basePath}/markdown?sessionId=${encoded}&redact=true">Markdown</a> \xB7 <a href="${input.basePath}/json?sessionId=${encoded}&redact=true">JSON</a></td></tr>`;
7560
+ }).join("");
7561
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml6(input.title)}</title><style>body{font-family:ui-sans-serif,system-ui,sans-serif;margin:2rem;background:#f8f7f2;color:#181713}main{max-width:1100px;margin:auto}table{width:100%;border-collapse:collapse;background:white}td,th{border-bottom:1px solid #eee;padding:.7rem;text-align:left}a{color:#9a3412}</style></head><body><main><h1>${escapeHtml6(input.title)}</h1><p>Recent voice trace diagnostics. Exports support filters: sessionId, traceId, turnId, scenarioId, type, provider, status, since, until, limit, redact.</p><table><thead><tr><th>Session</th><th>Events</th><th>Turns</th><th>Errors</th><th>Exports</th></tr></thead><tbody>${rows}</tbody></table></main></body></html>`;
7562
+ };
7563
+ var withRedaction = (events, query, defaultRedact) => {
7564
+ const shouldRedact = query.redact === undefined ? defaultRedact : getBoolean(query.redact);
7565
+ return shouldRedact ? redactVoiceTraceEvents(events, shouldRedact) : events;
7566
+ };
7567
+ var createVoiceDiagnosticsRoutes = (options) => {
7568
+ const path = options.path ?? "/diagnostics";
7569
+ const title = options.title ?? "AbsoluteJS Voice Diagnostics";
7570
+ const routes = new Elysia4({
7571
+ name: options.name ?? "absolutejs-voice-diagnostics"
7572
+ });
7573
+ routes.get(path, async () => {
7574
+ const events = await options.store.list();
7575
+ return new Response(renderDiagnosticsIndex({ basePath: path, events, title }), {
7576
+ headers: {
7577
+ "Content-Type": "text/html; charset=utf-8",
7578
+ ...options.headers
7579
+ }
7580
+ });
7581
+ });
7582
+ routes.get(`${path}/json`, async ({ query }) => {
7583
+ const events = filterByDiagnosticsQuery(await options.store.list(), query);
7584
+ const redacted = withRedaction(events, query, options.redact);
7585
+ return Response.json({
7586
+ ...await exportVoiceTrace({
7587
+ filter: resolveVoiceDiagnosticsTraceFilter(query),
7588
+ redact: false,
7589
+ store: {
7590
+ ...options.store,
7591
+ list: async () => redacted
7592
+ }
7593
+ }),
7594
+ filteredCount: events.length,
7595
+ redacted: redacted !== events
7596
+ });
7597
+ });
7598
+ routes.get(`${path}/markdown`, async ({ query }) => {
7599
+ const events = withRedaction(filterByDiagnosticsQuery(await options.store.list(), query), query, options.redact ?? true);
7600
+ const body = buildVoiceDiagnosticsMarkdown(events, {
7601
+ evaluation: options.evaluation,
7602
+ title
7603
+ });
7604
+ return new Response(body, {
7605
+ headers: {
7606
+ "Content-Type": "text/markdown; charset=utf-8",
7607
+ ...options.headers
7608
+ }
7609
+ });
7610
+ });
7611
+ routes.get(`${path}/html`, async ({ query }) => {
7612
+ const events = withRedaction(filterByDiagnosticsQuery(await options.store.list(), query), query, options.redact ?? true);
7613
+ const body = renderVoiceTraceHTML(events, {
7614
+ evaluation: options.evaluation,
7615
+ title
7616
+ });
7617
+ return new Response(body, {
7618
+ headers: {
7619
+ "Content-Type": "text/html; charset=utf-8",
7620
+ ...options.headers
7621
+ }
7622
+ });
7623
+ });
7624
+ return routes;
7625
+ };
7626
+ // src/sessionReplay.ts
7627
+ import { Elysia as Elysia5 } from "elysia";
7628
+ var getString4 = (value) => typeof value === "string" ? value : undefined;
7629
+ var escapeHtml7 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
7495
7630
  var increment2 = (record, key) => {
7496
7631
  record[key] = (record[key] ?? 0) + 1;
7497
7632
  };
@@ -7520,14 +7655,14 @@ var buildReplayTurns = (events) => {
7520
7655
  case "turn.transcript":
7521
7656
  turn.transcripts.push({
7522
7657
  isFinal: event.payload.isFinal === true,
7523
- text: getString3(event.payload.text)
7658
+ text: getString4(event.payload.text)
7524
7659
  });
7525
7660
  break;
7526
7661
  case "turn.committed":
7527
- turn.committedText = getString3(event.payload.text);
7662
+ turn.committedText = getString4(event.payload.text);
7528
7663
  break;
7529
7664
  case "turn.assistant": {
7530
- const text = getString3(event.payload.text);
7665
+ const text = getString4(event.payload.text);
7531
7666
  if (text) {
7532
7667
  turn.assistantReplies.push(text);
7533
7668
  }
@@ -7596,7 +7731,7 @@ var summarizeVoiceSessions = async (options = {}) => {
7596
7731
  let latestOutcome;
7597
7732
  let errorCount = 0;
7598
7733
  for (const event of sorted) {
7599
- const provider = getString3(event.payload.provider);
7734
+ const provider = getString4(event.payload.provider);
7600
7735
  if (provider) {
7601
7736
  providers.add(provider);
7602
7737
  }
@@ -7604,7 +7739,7 @@ var summarizeVoiceSessions = async (options = {}) => {
7604
7739
  errorCount += 1;
7605
7740
  increment2(providerErrors, provider ?? "unknown");
7606
7741
  }
7607
- const outcome = getString3(event.payload.outcome);
7742
+ const outcome = getString4(event.payload.outcome);
7608
7743
  if (outcome) {
7609
7744
  latestOutcome = outcome;
7610
7745
  }
@@ -7650,10 +7785,10 @@ var summarizeVoiceSessions = async (options = {}) => {
7650
7785
  var renderVoiceSessionsHTML = (sessions) => sessions.length === 0 ? '<p class="voice-sessions-empty">No voice sessions found.</p>' : [
7651
7786
  '<div class="voice-sessions-list">',
7652
7787
  ...sessions.map((session) => [
7653
- `<article class="voice-session-card ${escapeHtml6(session.status)}">`,
7788
+ `<article class="voice-session-card ${escapeHtml7(session.status)}">`,
7654
7789
  '<div class="voice-session-card-header">',
7655
- `<strong>${escapeHtml6(session.sessionId)}</strong>`,
7656
- `<span>${escapeHtml6(session.status)}</span>`,
7790
+ `<strong>${escapeHtml7(session.sessionId)}</strong>`,
7791
+ `<span>${escapeHtml7(session.status)}</span>`,
7657
7792
  "</div>",
7658
7793
  "<dl>",
7659
7794
  `<div><dt>Events</dt><dd>${String(session.eventCount)}</dd></div>`,
@@ -7661,9 +7796,9 @@ var renderVoiceSessionsHTML = (sessions) => sessions.length === 0 ? '<p class="v
7661
7796
  `<div><dt>Transcripts</dt><dd>${String(session.transcriptCount)}</dd></div>`,
7662
7797
  `<div><dt>Errors</dt><dd>${String(session.errorCount)}</dd></div>`,
7663
7798
  "</dl>",
7664
- session.latestOutcome ? `<p>Outcome: ${escapeHtml6(session.latestOutcome)}</p>` : "",
7665
- session.providers.length ? `<p>Providers: ${session.providers.map(escapeHtml6).join(", ")}</p>` : "",
7666
- session.replayHref ? `<p><a href="${escapeHtml6(session.replayHref)}">Open replay</a></p>` : "",
7799
+ session.latestOutcome ? `<p>Outcome: ${escapeHtml7(session.latestOutcome)}</p>` : "",
7800
+ session.providers.length ? `<p>Providers: ${session.providers.map(escapeHtml7).join(", ")}</p>` : "",
7801
+ session.replayHref ? `<p><a href="${escapeHtml7(session.replayHref)}">Open replay</a></p>` : "",
7667
7802
  "</article>"
7668
7803
  ].join("")),
7669
7804
  "</div>"
@@ -7694,7 +7829,7 @@ var createVoiceSessionsHTMLHandler = (options = {}) => async ({ query }) => {
7694
7829
  var createVoiceSessionListRoutes = (options = {}) => {
7695
7830
  const path = options.path ?? "/api/voice-sessions";
7696
7831
  const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
7697
- const routes = new Elysia4({
7832
+ const routes = new Elysia5({
7698
7833
  name: options.name ?? "absolutejs-voice-session-list"
7699
7834
  }).get(path, createVoiceSessionsJSONHandler(options));
7700
7835
  if (htmlPath) {
@@ -7722,7 +7857,7 @@ var createVoiceSessionReplayHTMLHandler = (options) => async ({ params }) => {
7722
7857
  var createVoiceSessionReplayRoutes = (options) => {
7723
7858
  const path = options.path ?? "/api/voice-sessions/:sessionId/replay";
7724
7859
  const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
7725
- const routes = new Elysia4({
7860
+ const routes = new Elysia5({
7726
7861
  name: options.name ?? "absolutejs-voice-session-replay"
7727
7862
  }).get(path, createVoiceSessionReplayJSONHandler(options));
7728
7863
  if (htmlPath) {
@@ -8859,11 +8994,11 @@ var createGeminiVoiceAssistantModel = (options) => {
8859
8994
  };
8860
8995
  };
8861
8996
  // src/resilienceRoutes.ts
8862
- import { Elysia as Elysia5 } from "elysia";
8863
- var escapeHtml7 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
8864
- var getString4 = (value) => typeof value === "string" ? value : undefined;
8865
- var getNumber2 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
8866
- var getBoolean = (value) => value === true;
8997
+ import { Elysia as Elysia6 } from "elysia";
8998
+ var escapeHtml8 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
8999
+ var getString5 = (value) => typeof value === "string" ? value : undefined;
9000
+ var getNumber3 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
9001
+ var getBoolean2 = (value) => value === true;
8867
9002
  var isProviderStatus2 = (value) => value === "error" || value === "fallback" || value === "success";
8868
9003
  var listVoiceRoutingEvents = (events) => {
8869
9004
  const routingEvents = [];
@@ -8871,26 +9006,26 @@ var listVoiceRoutingEvents = (events) => {
8871
9006
  if (event.type !== "session.error") {
8872
9007
  continue;
8873
9008
  }
8874
- const provider = getString4(event.payload.provider);
9009
+ const provider = getString5(event.payload.provider);
8875
9010
  const providerStatus = isProviderStatus2(event.payload.providerStatus) ? event.payload.providerStatus : undefined;
8876
9011
  if (!provider || !providerStatus) {
8877
9012
  continue;
8878
9013
  }
8879
- const kind = getString4(event.payload.kind);
9014
+ const kind = getString5(event.payload.kind);
8880
9015
  routingEvents.push({
8881
9016
  at: event.at,
8882
- attempt: getNumber2(event.payload.attempt),
8883
- elapsedMs: getNumber2(event.payload.elapsedMs),
8884
- error: getString4(event.payload.error),
8885
- fallbackProvider: getString4(event.payload.fallbackProvider),
9017
+ attempt: getNumber3(event.payload.attempt),
9018
+ elapsedMs: getNumber3(event.payload.elapsedMs),
9019
+ error: getString5(event.payload.error),
9020
+ fallbackProvider: getString5(event.payload.fallbackProvider),
8886
9021
  kind: kind === "stt" || kind === "tts" ? kind : "llm",
8887
- latencyBudgetMs: getNumber2(event.payload.latencyBudgetMs),
8888
- operation: getString4(event.payload.operation),
9022
+ latencyBudgetMs: getNumber3(event.payload.latencyBudgetMs),
9023
+ operation: getString5(event.payload.operation),
8889
9024
  provider,
8890
- selectedProvider: getString4(event.payload.selectedProvider),
9025
+ selectedProvider: getString5(event.payload.selectedProvider),
8891
9026
  sessionId: event.sessionId,
8892
9027
  status: providerStatus,
8893
- timedOut: getBoolean(event.payload.timedOut),
9028
+ timedOut: getBoolean2(event.payload.timedOut),
8894
9029
  turnId: event.turnId
8895
9030
  });
8896
9031
  }
@@ -8923,13 +9058,13 @@ var summarizeRoutingEvents = (events) => {
8923
9058
  };
8924
9059
  var renderProviderCards = (title, providers) => {
8925
9060
  if (providers.length === 0) {
8926
- return `<p class="muted">No ${escapeHtml7(title)} provider health yet.</p>`;
9061
+ return `<p class="muted">No ${escapeHtml8(title)} provider health yet.</p>`;
8927
9062
  }
8928
9063
  return `<div class="provider-grid">${providers.map((provider) => `
8929
- <article class="card provider ${escapeHtml7(provider.status)}">
9064
+ <article class="card provider ${escapeHtml8(provider.status)}">
8930
9065
  <div class="card-header">
8931
- <strong>${escapeHtml7(provider.provider)}</strong>
8932
- <span>${escapeHtml7(provider.status)}${provider.recommended ? " \xB7 recommended" : ""}</span>
9066
+ <strong>${escapeHtml8(provider.provider)}</strong>
9067
+ <span>${escapeHtml8(provider.status)}${provider.recommended ? " \xB7 recommended" : ""}</span>
8933
9068
  </div>
8934
9069
  <dl>
8935
9070
  <div><dt>Runs</dt><dd>${provider.runCount}</dd></div>
@@ -8938,7 +9073,7 @@ var renderProviderCards = (title, providers) => {
8938
9073
  <div><dt>Timeouts</dt><dd>${provider.timeoutCount}</dd></div>
8939
9074
  <div><dt>Fallbacks</dt><dd>${provider.fallbackCount}</dd></div>
8940
9075
  </dl>
8941
- ${provider.lastError ? `<p class="muted">${escapeHtml7(provider.lastError)}</p>` : ""}
9076
+ ${provider.lastError ? `<p class="muted">${escapeHtml8(provider.lastError)}</p>` : ""}
8942
9077
  </article>
8943
9078
  `).join("")}</div>`;
8944
9079
  };
@@ -8947,24 +9082,24 @@ var renderTimeline2 = (events) => {
8947
9082
  return '<p class="muted">No provider routing events yet. Run the app or simulate provider failover.</p>';
8948
9083
  }
8949
9084
  return `<div class="timeline">${events.slice(0, 40).map((event) => `
8950
- <article class="card event ${escapeHtml7(event.status ?? "unknown")}">
9085
+ <article class="card event ${escapeHtml8(event.status ?? "unknown")}">
8951
9086
  <div class="card-header">
8952
- <strong>${escapeHtml7(event.kind.toUpperCase())} ${escapeHtml7(event.operation ?? "generate")}</strong>
9087
+ <strong>${escapeHtml8(event.kind.toUpperCase())} ${escapeHtml8(event.operation ?? "generate")}</strong>
8953
9088
  <span>${new Date(event.at).toLocaleString()}</span>
8954
9089
  </div>
8955
9090
  <p>
8956
- <span class="pill">${escapeHtml7(event.status ?? "unknown")}</span>
8957
- <span class="pill">provider: ${escapeHtml7(event.provider ?? "unknown")}</span>
8958
- ${event.fallbackProvider ? `<span class="pill">fallback: ${escapeHtml7(event.fallbackProvider)}</span>` : ""}
9091
+ <span class="pill">${escapeHtml8(event.status ?? "unknown")}</span>
9092
+ <span class="pill">provider: ${escapeHtml8(event.provider ?? "unknown")}</span>
9093
+ ${event.fallbackProvider ? `<span class="pill">fallback: ${escapeHtml8(event.fallbackProvider)}</span>` : ""}
8959
9094
  ${event.timedOut ? '<span class="pill danger">timed out</span>' : ""}
8960
9095
  </p>
8961
9096
  <dl>
8962
9097
  <div><dt>Attempt</dt><dd>${event.attempt ?? 0}</dd></div>
8963
9098
  <div><dt>Elapsed</dt><dd>${event.elapsedMs ?? 0}ms</dd></div>
8964
9099
  <div><dt>Budget</dt><dd>${event.latencyBudgetMs ?? 0}ms</dd></div>
8965
- <div><dt>Session</dt><dd>${escapeHtml7(event.sessionId)}</dd></div>
9100
+ <div><dt>Session</dt><dd>${escapeHtml8(event.sessionId)}</dd></div>
8966
9101
  </dl>
8967
- ${event.error ? `<p class="muted">${escapeHtml7(event.error)}</p>` : ""}
9102
+ ${event.error ? `<p class="muted">${escapeHtml8(event.error)}</p>` : ""}
8968
9103
  </article>
8969
9104
  `).join("")}</div>`;
8970
9105
  };
@@ -8979,26 +9114,26 @@ var renderSimulationControls = (kind, simulation) => {
8979
9114
  const pathPrefix = simulation.pathPrefix ?? `/api/${kind}-simulate`;
8980
9115
  const failureProviders = simulation.failureProviders ?? configuredProviders.map(({ provider }) => provider);
8981
9116
  const canFail = (provider) => configuredProviders.some((entry) => entry.provider === provider) && (!simulation.fallbackRequiredProvider || configuredProviders.some((entry) => entry.provider === simulation.fallbackRequiredProvider));
8982
- return `<div class="simulate-panel" data-sim-kind="${kind}" data-sim-prefix="${escapeHtml7(pathPrefix)}">
8983
- <p class="muted">${escapeHtml7(simulation.failureMessage ?? `Simulate ${kind.toUpperCase()} provider failure without changing provider credentials.`)}</p>
9117
+ return `<div class="simulate-panel" data-sim-kind="${kind}" data-sim-prefix="${escapeHtml8(pathPrefix)}">
9118
+ <p class="muted">${escapeHtml8(simulation.failureMessage ?? `Simulate ${kind.toUpperCase()} provider failure without changing provider credentials.`)}</p>
8984
9119
  <div class="simulate-actions">
8985
- ${failureProviders.map((provider) => `<button type="button" data-provider-fail="${escapeHtml7(provider)}"${canFail(provider) ? "" : " disabled"}>Simulate ${escapeHtml7(provider)} ${kind.toUpperCase()} failure</button>`).join("")}
8986
- ${configuredProviders.map((provider) => `<button type="button" data-provider-recover="${escapeHtml7(provider.provider)}">Mark ${escapeHtml7(provider.provider)} recovered</button>`).join("")}
9120
+ ${failureProviders.map((provider) => `<button type="button" data-provider-fail="${escapeHtml8(provider)}"${canFail(provider) ? "" : " disabled"}>Simulate ${escapeHtml8(provider)} ${kind.toUpperCase()} failure</button>`).join("")}
9121
+ ${configuredProviders.map((provider) => `<button type="button" data-provider-recover="${escapeHtml8(provider.provider)}">Mark ${escapeHtml8(provider.provider)} recovered</button>`).join("")}
8987
9122
  </div>
8988
- ${simulation.fallbackRequiredProvider && !configuredProviders.some((entry) => entry.provider === simulation.fallbackRequiredProvider) ? `<p class="muted">${escapeHtml7(simulation.fallbackRequiredMessage ?? `Configure ${simulation.fallbackRequiredProvider} to enable fallback simulation.`)}</p>` : ""}
9123
+ ${simulation.fallbackRequiredProvider && !configuredProviders.some((entry) => entry.provider === simulation.fallbackRequiredProvider) ? `<p class="muted">${escapeHtml8(simulation.fallbackRequiredMessage ?? `Configure ${simulation.fallbackRequiredProvider} to enable fallback simulation.`)}</p>` : ""}
8989
9124
  <pre class="simulate-output" hidden></pre>
8990
9125
  </div>`;
8991
9126
  };
8992
9127
  var renderVoiceResilienceHTML = (input) => {
8993
9128
  const summary = summarizeRoutingEvents(input.routingEvents);
8994
- const kindCounts = [...summary.byKind.entries()].map(([kind, count]) => `<span class="pill">${escapeHtml7(kind)}: ${String(count)}</span>`).join("");
8995
- const links = input.links?.length ? input.links.map((link) => `<a href="${escapeHtml7(link.href)}">${escapeHtml7(link.label)}</a>`).join(" \xB7 ") : "";
9129
+ const kindCounts = [...summary.byKind.entries()].map(([kind, count]) => `<span class="pill">${escapeHtml8(kind)}: ${String(count)}</span>`).join("");
9130
+ const links = input.links?.length ? input.links.map((link) => `<a href="${escapeHtml8(link.href)}">${escapeHtml8(link.label)}</a>`).join(" \xB7 ") : "";
8996
9131
  return `<!doctype html>
8997
9132
  <html lang="en">
8998
9133
  <head>
8999
9134
  <meta charset="utf-8" />
9000
9135
  <meta name="viewport" content="width=device-width, initial-scale=1" />
9001
- <title>${escapeHtml7(input.title ?? "AbsoluteJS Voice Resilience")}</title>
9136
+ <title>${escapeHtml8(input.title ?? "AbsoluteJS Voice Resilience")}</title>
9002
9137
  <style>
9003
9138
  :root { color-scheme: dark; }
9004
9139
  body { background: radial-gradient(circle at top left, #172554, #09090b 36%, #050505); color: #f4f4f5; font-family: ui-sans-serif, system-ui, sans-serif; margin: 0; padding: 24px; }
@@ -9134,7 +9269,7 @@ var registerSimulationRoutes = (routes, simulation, defaultPathPrefix) => {
9134
9269
  };
9135
9270
  var createVoiceResilienceRoutes = (options) => {
9136
9271
  const path = options.path ?? "/resilience";
9137
- const routes = new Elysia5({
9272
+ const routes = new Elysia6({
9138
9273
  name: options.name ?? "absolutejs-voice-resilience"
9139
9274
  }).get(path, async () => {
9140
9275
  const events = await options.store.list();
@@ -10015,7 +10150,7 @@ var createVoiceMemoryStore = () => {
10015
10150
  return { get, getOrCreate, list, remove, set };
10016
10151
  };
10017
10152
  // src/opsWebhook.ts
10018
- import { Elysia as Elysia6 } from "elysia";
10153
+ import { Elysia as Elysia7 } from "elysia";
10019
10154
  var toHex5 = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
10020
10155
  var signVoiceOpsWebhookBody = async (input) => {
10021
10156
  const encoder = new TextEncoder;
@@ -10145,7 +10280,7 @@ var verifyVoiceOpsWebhookSignature = async (input) => {
10145
10280
  };
10146
10281
  var createVoiceOpsWebhookReceiverRoutes = (options = {}) => {
10147
10282
  const path = options.path ?? "/api/voice-ops/webhook";
10148
- return new Elysia6().post(path, async ({ body, request, set }) => {
10283
+ return new Elysia7().post(path, async ({ body, request, set }) => {
10149
10284
  const bodyText = typeof body === "string" ? body : JSON.stringify(body);
10150
10285
  if (options.signingSecret) {
10151
10286
  const verification = await verifyVoiceOpsWebhookSignature({
@@ -10178,9 +10313,9 @@ var createVoiceOpsWebhookReceiverRoutes = (options = {}) => {
10178
10313
  });
10179
10314
  };
10180
10315
  // src/handoffHealth.ts
10181
- import { Elysia as Elysia7 } from "elysia";
10182
- var escapeHtml8 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
10183
- var getString5 = (value) => typeof value === "string" && value.length > 0 ? value : undefined;
10316
+ import { Elysia as Elysia8 } from "elysia";
10317
+ var escapeHtml9 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
10318
+ var getString6 = (value) => typeof value === "string" && value.length > 0 ? value : undefined;
10184
10319
  var isStatus = (value) => value === "delivered" || value === "failed" || value === "skipped";
10185
10320
  var increment3 = (record, key) => {
10186
10321
  record[key] = (record[key] ?? 0) + 1;
@@ -10188,11 +10323,11 @@ var increment3 = (record, key) => {
10188
10323
  var normalizeDelivery = (adapterId, value) => {
10189
10324
  const record = value && typeof value === "object" ? value : {};
10190
10325
  return {
10191
- adapterId: getString5(record.adapterId) ?? adapterId,
10192
- adapterKind: getString5(record.adapterKind),
10326
+ adapterId: getString6(record.adapterId) ?? adapterId,
10327
+ adapterKind: getString6(record.adapterKind),
10193
10328
  deliveredAt: typeof record.deliveredAt === "number" ? record.deliveredAt : undefined,
10194
- deliveredTo: getString5(record.deliveredTo),
10195
- error: getString5(record.error),
10329
+ deliveredTo: getString6(record.deliveredTo),
10330
+ error: getString6(record.error),
10196
10331
  status: isStatus(record.status) ? record.status : "failed"
10197
10332
  };
10198
10333
  };
@@ -10226,13 +10361,13 @@ var summarizeVoiceHandoffHealth = async (options = {}) => {
10226
10361
  const status = isStatus(event.payload.status) ? event.payload.status : "failed";
10227
10362
  const deliveries = normalizeDeliveries(event.payload);
10228
10363
  const item = {
10229
- action: getString5(event.payload.action),
10364
+ action: getString6(event.payload.action),
10230
10365
  at: event.at,
10231
10366
  deliveries,
10232
- reason: getString5(event.payload.reason),
10367
+ reason: getString6(event.payload.reason),
10233
10368
  sessionId: event.sessionId,
10234
10369
  status,
10235
- target: getString5(event.payload.target)
10370
+ target: getString6(event.payload.target)
10236
10371
  };
10237
10372
  return {
10238
10373
  ...item,
@@ -10297,10 +10432,10 @@ var renderActionSummary = (summary) => {
10297
10432
  return [
10298
10433
  '<section class="voice-handoff-health-columns">',
10299
10434
  "<article><h3>Actions</h3>",
10300
- actions.length === 0 ? "<p>No handoff actions yet.</p>" : `<ul>${actions.map(([action, count]) => `<li>${escapeHtml8(action)}: ${String(count)}</li>`).join("")}</ul>`,
10435
+ actions.length === 0 ? "<p>No handoff actions yet.</p>" : `<ul>${actions.map(([action, count]) => `<li>${escapeHtml9(action)}: ${String(count)}</li>`).join("")}</ul>`,
10301
10436
  "</article>",
10302
10437
  "<article><h3>Adapters</h3>",
10303
- adapters.length === 0 ? "<p>No adapter deliveries yet.</p>" : `<ul>${adapters.map(([adapterId, counts]) => `<li>${escapeHtml8(adapterId)}: ${String(counts.delivered)} delivered / ${String(counts.failed)} failed / ${String(counts.skipped)} skipped</li>`).join("")}</ul>`,
10438
+ adapters.length === 0 ? "<p>No adapter deliveries yet.</p>" : `<ul>${adapters.map(([adapterId, counts]) => `<li>${escapeHtml9(adapterId)}: ${String(counts.delivered)} delivered / ${String(counts.failed)} failed / ${String(counts.skipped)} skipped</li>`).join("")}</ul>`,
10304
10439
  "</article>",
10305
10440
  "</section>"
10306
10441
  ].join("");
@@ -10314,22 +10449,22 @@ var renderVoiceHandoffHealthHTML = (summary) => [
10314
10449
  summary.events.length === 0 ? '<p class="voice-handoff-health-empty">No handoffs found.</p>' : [
10315
10450
  '<div class="voice-handoff-health-events">',
10316
10451
  ...summary.events.map((event) => [
10317
- `<article class="${escapeHtml8(event.status)}">`,
10452
+ `<article class="${escapeHtml9(event.status)}">`,
10318
10453
  '<div class="voice-handoff-health-event-header">',
10319
- `<strong>${escapeHtml8(event.action ?? "handoff")}</strong>`,
10320
- `<span>${escapeHtml8(event.status)}</span>`,
10454
+ `<strong>${escapeHtml9(event.action ?? "handoff")}</strong>`,
10455
+ `<span>${escapeHtml9(event.status)}</span>`,
10321
10456
  "</div>",
10322
- `<p><small>${escapeHtml8(event.sessionId)}</small></p>`,
10323
- event.target ? `<p>Target: ${escapeHtml8(event.target)}</p>` : "",
10324
- event.reason ? `<p>Reason: ${escapeHtml8(event.reason)}</p>` : "",
10457
+ `<p><small>${escapeHtml9(event.sessionId)}</small></p>`,
10458
+ event.target ? `<p>Target: ${escapeHtml9(event.target)}</p>` : "",
10459
+ event.reason ? `<p>Reason: ${escapeHtml9(event.reason)}</p>` : "",
10325
10460
  event.deliveries.length ? `<ul>${event.deliveries.map((delivery) => [
10326
10461
  "<li>",
10327
- `${escapeHtml8(delivery.adapterId)}: ${escapeHtml8(delivery.status)}`,
10328
- delivery.deliveredTo ? ` to ${escapeHtml8(delivery.deliveredTo)}` : "",
10329
- delivery.error ? ` (${escapeHtml8(delivery.error)})` : "",
10462
+ `${escapeHtml9(delivery.adapterId)}: ${escapeHtml9(delivery.status)}`,
10463
+ delivery.deliveredTo ? ` to ${escapeHtml9(delivery.deliveredTo)}` : "",
10464
+ delivery.error ? ` (${escapeHtml9(delivery.error)})` : "",
10330
10465
  "</li>"
10331
10466
  ].join("")).join("")}</ul>` : "",
10332
- event.replayHref ? `<p><a href="${escapeHtml8(event.replayHref)}">Open replay</a></p>` : "",
10467
+ event.replayHref ? `<p><a href="${escapeHtml9(event.replayHref)}">Open replay</a></p>` : "",
10333
10468
  "</article>"
10334
10469
  ].join("")),
10335
10470
  "</div>"
@@ -10361,7 +10496,7 @@ var createVoiceHandoffHealthHTMLHandler = (options = {}) => async ({ query }) =>
10361
10496
  var createVoiceHandoffHealthRoutes = (options = {}) => {
10362
10497
  const path = options.path ?? "/api/voice-handoffs";
10363
10498
  const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
10364
- const routes = new Elysia7({
10499
+ const routes = new Elysia8({
10365
10500
  name: options.name ?? "absolutejs-voice-handoff-health"
10366
10501
  }).get(path, createVoiceHandoffHealthJSONHandler(options));
10367
10502
  if (htmlPath) {
@@ -12507,6 +12642,7 @@ export {
12507
12642
  resolveVoiceOpsTaskAssignment,
12508
12643
  resolveVoiceOpsTaskAgeBucket,
12509
12644
  resolveVoiceOpsPreset,
12645
+ resolveVoiceDiagnosticsTraceFilter,
12510
12646
  resolveVoiceAssistantMemoryNamespace,
12511
12647
  resolveTurnDetectionConfig,
12512
12648
  resolveAudioConditioningConfig,
@@ -12640,6 +12776,7 @@ export {
12640
12776
  createVoiceExternalObjectMapId,
12641
12777
  createVoiceExternalObjectMap,
12642
12778
  createVoiceExperiment,
12779
+ createVoiceDiagnosticsRoutes,
12643
12780
  createVoiceCallReviewRecorder,
12644
12781
  createVoiceCallReviewFromSession,
12645
12782
  createVoiceCallReviewFromLiveTelephonyReport,
@@ -12675,6 +12812,7 @@ export {
12675
12812
  buildVoiceTraceReplay,
12676
12813
  buildVoiceOpsTaskFromSLABreach,
12677
12814
  buildVoiceOpsTaskFromReview,
12815
+ buildVoiceDiagnosticsMarkdown,
12678
12816
  assignVoiceOpsTask,
12679
12817
  applyVoiceOpsTaskPolicy,
12680
12818
  applyVoiceOpsTaskAssignmentRule,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.37",
3
+ "version": "0.0.22-beta.38",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",