@absolutejs/voice 0.0.22-beta.197 → 0.0.22-beta.199

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.
@@ -241,6 +241,12 @@ export type VoiceCampaignRoutesOptions = VoiceCampaignRuntimeOptions & {
241
241
  headers?: HeadersInit;
242
242
  htmlPath?: false | string;
243
243
  observability?: VoiceCampaignObservabilityOptions;
244
+ operationsRecordHref?: false | string | ((input: {
245
+ attempt: VoiceCampaignAttempt;
246
+ campaign: VoiceCampaign;
247
+ recipient?: VoiceCampaignRecipient;
248
+ sessionId?: string;
249
+ }) => string | undefined);
244
250
  name?: string;
245
251
  path?: string;
246
252
  title?: string;
@@ -375,9 +381,7 @@ export declare const applyVoiceCampaignTelephonyOutcome: <TResult = unknown>(inp
375
381
  export declare const createVoiceCampaignTelephonyOutcomeHandler: <TResult = unknown>(options: VoiceCampaignTelephonyOutcomeOptions<TResult>) => (input: VoiceTelephonyWebhookDecision<TResult>) => Promise<VoiceCampaignTelephonyOutcomeResult>;
376
382
  export declare const runVoiceCampaignProof: (options?: VoiceCampaignProofOptions) => Promise<VoiceCampaignProofReport>;
377
383
  export declare const runVoiceCampaignReadinessProof: (options?: VoiceCampaignReadinessProofOptions) => Promise<VoiceCampaignReadinessProofReport>;
378
- export declare const renderVoiceCampaignsHTML: (records: VoiceCampaignRecord[], options?: {
379
- title?: string;
380
- }) => string;
384
+ export declare const renderVoiceCampaignsHTML: (records: VoiceCampaignRecord[], options?: Pick<VoiceCampaignRoutesOptions, "operationsRecordHref" | "title">) => string;
381
385
  export declare const renderVoiceCampaignObservabilityHTML: (report: VoiceCampaignObservabilityReport, options?: {
382
386
  title?: string;
383
387
  }) => string;
package/dist/index.js CHANGED
@@ -6662,11 +6662,37 @@ recipient-no-consent,Barbara,+15550001004,no,delta`,
6662
6662
  };
6663
6663
  };
6664
6664
  var escapeHtml3 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
6665
+ var getString = (value) => typeof value === "string" && value.length > 0 ? value : undefined;
6666
+ var campaignAttemptSessionId = (attempt) => getString(attempt.metadata?.sessionId) ?? getString(attempt.metadata?.voiceSessionId) ?? getString(attempt.metadata?.callSessionId);
6667
+ var resolveCampaignOperationsRecordHref = (value, input) => {
6668
+ if (value === false || !input.sessionId) {
6669
+ return;
6670
+ }
6671
+ if (typeof value === "function") {
6672
+ return value(input);
6673
+ }
6674
+ if (typeof value === "string") {
6675
+ const encoded = encodeURIComponent(input.sessionId);
6676
+ return value.includes(":sessionId") ? value.replace(":sessionId", encoded) : `${value.replace(/\/$/, "")}/${encoded}`;
6677
+ }
6678
+ return;
6679
+ };
6665
6680
  var renderVoiceCampaignsHTML = (records, options = {}) => {
6666
6681
  const title = options.title ?? "Voice Campaigns";
6667
6682
  const rows = records.map((record) => `<tr><td>${escapeHtml3(record.campaign.name)}</td><td>${escapeHtml3(record.campaign.status)}</td><td>${String(record.recipients.length)}</td><td>${String(record.attempts.length)}</td><td>${new Date(record.campaign.updatedAt).toLocaleString()}</td></tr>`).join("");
6668
6683
  const summary = summarizeVoiceCampaigns(records);
6669
- return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml3(title)}</title><style>body{background:#111827;color:#f9fafb;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1080px;padding:32px}.hero{background:linear-gradient(135deg,rgba(251,146,60,.18),rgba(45,212,191,.12));border:1px solid #334155;border-radius:28px;margin-bottom:18px;padding:28px}.eyebrow{color:#fdba74;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,5rem);line-height:.9;margin:.2rem 0 1rem}.grid{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));margin:18px 0}.grid article,table{background:#172033;border:1px solid #334155;border-radius:18px}.grid article{padding:16px}.grid span{color:#aab5c0}.grid strong{display:block;font-size:2rem;margin:.25rem 0}table{border-collapse:collapse;overflow:hidden;width:100%}td,th{border-bottom:1px solid #334155;padding:12px;text-align:left}</style></head><body><main><section class="hero"><p class="eyebrow">Self-hosted outbound</p><h1>${escapeHtml3(title)}</h1><p>Campaign orchestration, recipients, attempts, retries, and outcomes without a hosted dialer dashboard.</p><section class="grid"><article><span>Campaigns</span><strong>${String(summary.campaigns.total)}</strong></article><article><span>Recipients</span><strong>${String(summary.recipients.total)}</strong></article><article><span>Attempts</span><strong>${String(summary.attempts.total)}</strong></article><article><span>Running</span><strong>${String(summary.campaigns.running)}</strong></article></section></section><table><thead><tr><th>Name</th><th>Status</th><th>Recipients</th><th>Attempts</th><th>Updated</th></tr></thead><tbody>${rows || '<tr><td colspan="5">No campaigns yet.</td></tr>'}</tbody></table></main></body></html>`;
6684
+ const attemptRows = records.flatMap((record) => record.attempts.slice(-8).reverse().map((attempt) => {
6685
+ const recipient = record.recipients.find((item) => item.id === attempt.recipientId);
6686
+ const sessionId = campaignAttemptSessionId(attempt);
6687
+ const href = resolveCampaignOperationsRecordHref(options.operationsRecordHref, {
6688
+ attempt,
6689
+ campaign: record.campaign,
6690
+ recipient,
6691
+ sessionId
6692
+ });
6693
+ return `<tr><td>${escapeHtml3(record.campaign.name)}</td><td>${escapeHtml3(attempt.status)}</td><td>${escapeHtml3(recipient?.phone ?? attempt.recipientId)}</td><td>${escapeHtml3(sessionId ?? "")}</td><td>${href ? `<a href="${escapeHtml3(href)}">Open operations record</a>` : ""}</td></tr>`;
6694
+ })).slice(0, 20).join("");
6695
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml3(title)}</title><style>body{background:#111827;color:#f9fafb;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1080px;padding:32px}.hero{background:linear-gradient(135deg,rgba(251,146,60,.18),rgba(45,212,191,.12));border:1px solid #334155;border-radius:28px;margin-bottom:18px;padding:28px}.eyebrow{color:#fdba74;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,5rem);line-height:.9;margin:.2rem 0 1rem}.grid{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));margin:18px 0}.grid article,table{background:#172033;border:1px solid #334155;border-radius:18px}.grid article{padding:16px}.grid span{color:#aab5c0}.grid strong{display:block;font-size:2rem;margin:.25rem 0}table{border-collapse:collapse;margin-top:18px;overflow:hidden;width:100%}td,th{border-bottom:1px solid #334155;padding:12px;text-align:left}a{color:#fdba74}</style></head><body><main><section class="hero"><p class="eyebrow">Self-hosted outbound</p><h1>${escapeHtml3(title)}</h1><p>Campaign orchestration, recipients, attempts, retries, and outcomes without a hosted dialer dashboard.</p><section class="grid"><article><span>Campaigns</span><strong>${String(summary.campaigns.total)}</strong></article><article><span>Recipients</span><strong>${String(summary.recipients.total)}</strong></article><article><span>Attempts</span><strong>${String(summary.attempts.total)}</strong></article><article><span>Running</span><strong>${String(summary.campaigns.running)}</strong></article></section></section><table><thead><tr><th>Name</th><th>Status</th><th>Recipients</th><th>Attempts</th><th>Updated</th></tr></thead><tbody>${rows || '<tr><td colspan="5">No campaigns yet.</td></tr>'}</tbody></table><h2>Recent attempts</h2><table><thead><tr><th>Campaign</th><th>Status</th><th>Recipient</th><th>Session</th><th>Debug</th></tr></thead><tbody>${attemptRows || '<tr><td colspan="5">No attempts yet.</td></tr>'}</tbody></table></main></body></html>`;
6670
6696
  };
6671
6697
  var renderVoiceCampaignObservabilityHTML = (report, options = {}) => {
6672
6698
  const title = options.title ?? "Voice Campaign Observability";
@@ -8634,7 +8660,7 @@ import { Elysia as Elysia4 } from "elysia";
8634
8660
 
8635
8661
  // src/providerHealth.ts
8636
8662
  import { Elysia as Elysia3 } from "elysia";
8637
- var getString = (value) => typeof value === "string" ? value : undefined;
8663
+ var getString2 = (value) => typeof value === "string" ? value : undefined;
8638
8664
  var getNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
8639
8665
  var isProviderStatus = (value) => value === "success" || value === "fallback" || value === "error";
8640
8666
  var summarizeVoiceProviderHealth = async (input) => {
@@ -8740,7 +8766,7 @@ var summarizeVoiceProviderHealth = async (input) => {
8740
8766
  if (event.payload.timedOut === true) {
8741
8767
  entry.timeoutCount += 1;
8742
8768
  }
8743
- entry.lastError = getString(event.payload.error);
8769
+ entry.lastError = getString2(event.payload.error);
8744
8770
  entry.lastErrorAt = event.at;
8745
8771
  entry.rateLimited ||= event.payload.rateLimited === true;
8746
8772
  }
@@ -8836,16 +8862,16 @@ var renderCountMap = (values) => {
8836
8862
  "</div>"
8837
8863
  ].join("");
8838
8864
  };
8839
- var getString2 = (value) => typeof value === "string" ? value : undefined;
8865
+ var getString3 = (value) => typeof value === "string" ? value : undefined;
8840
8866
  var getRecentFailures = (events, maxFailures, replayHref) => events.filter((event) => event.type === "session.error" && (event.payload.providerStatus === "error" || typeof event.payload.error === "string") || event.type === "assistant.guardrail" && event.payload.action === "blocked").toReversed().slice(0, maxFailures).map((event) => {
8841
8867
  const failure = {
8842
8868
  at: event.at,
8843
- assistantId: getString2(event.payload.assistantId),
8844
- error: getString2(event.payload.error),
8845
- provider: getString2(event.payload.provider),
8869
+ assistantId: getString3(event.payload.assistantId),
8870
+ error: getString3(event.payload.error),
8871
+ provider: getString3(event.payload.provider),
8846
8872
  rateLimited: event.payload.rateLimited === true ? true : undefined,
8847
8873
  sessionId: event.sessionId,
8848
- status: getString2(event.payload.providerStatus),
8874
+ status: getString3(event.payload.providerStatus),
8849
8875
  turnId: event.turnId,
8850
8876
  type: event.type
8851
8877
  };
@@ -9783,7 +9809,7 @@ var buildVoiceAuditExport = (events, options = {}) => {
9783
9809
 
9784
9810
  // src/auditRoutes.ts
9785
9811
  var escapeHtml8 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
9786
- var getString3 = (value) => typeof value === "string" && value.trim() ? value.trim() : undefined;
9812
+ var getString4 = (value) => typeof value === "string" && value.trim() ? value.trim() : undefined;
9787
9813
  var getNumber2 = (value) => {
9788
9814
  if (typeof value === "number" && Number.isFinite(value)) {
9789
9815
  return value;
@@ -9795,7 +9821,7 @@ var getNumber2 = (value) => {
9795
9821
  return;
9796
9822
  };
9797
9823
  var parseType = (value) => {
9798
- const text = getString3(value);
9824
+ const text = getString4(value);
9799
9825
  return text && [
9800
9826
  "handoff",
9801
9827
  "operator.action",
@@ -9805,11 +9831,11 @@ var parseType = (value) => {
9805
9831
  ].includes(text) ? text : undefined;
9806
9832
  };
9807
9833
  var parseOutcome = (value) => {
9808
- const text = getString3(value);
9834
+ const text = getString4(value);
9809
9835
  return text && ["error", "skipped", "success"].includes(text) ? text : undefined;
9810
9836
  };
9811
9837
  var parseBoolean = (value, fallback = false) => {
9812
- const text = getString3(value)?.toLowerCase();
9838
+ const text = getString4(value)?.toLowerCase();
9813
9839
  if (["1", "true", "yes"].includes(text ?? "")) {
9814
9840
  return true;
9815
9841
  }
@@ -9819,7 +9845,7 @@ var parseBoolean = (value, fallback = false) => {
9819
9845
  return fallback;
9820
9846
  };
9821
9847
  var parseExportFormat = (value) => {
9822
- const text = getString3(value)?.toLowerCase();
9848
+ const text = getString4(value)?.toLowerCase();
9823
9849
  return text === "markdown" || text === "md" ? "markdown" : text === "html" ? "html" : "json";
9824
9850
  };
9825
9851
  var increment2 = (counts, key) => {
@@ -9831,17 +9857,17 @@ var increment2 = (counts, key) => {
9831
9857
  var sortedCounts = (counts) => [...counts.entries()].sort(([leftKey, leftCount], [rightKey, rightCount]) => rightCount - leftCount || leftKey.localeCompare(rightKey));
9832
9858
  var resolveVoiceAuditTrailFilter = (query = {}, base = {}) => ({
9833
9859
  ...base,
9834
- actorId: getString3(query.actorId) ?? base.actorId,
9860
+ actorId: getString4(query.actorId) ?? base.actorId,
9835
9861
  after: getNumber2(query.after) ?? base.after,
9836
9862
  afterOrAt: getNumber2(query.afterOrAt) ?? base.afterOrAt,
9837
9863
  before: getNumber2(query.before) ?? base.before,
9838
9864
  beforeOrAt: getNumber2(query.beforeOrAt) ?? base.beforeOrAt,
9839
9865
  limit: getNumber2(query.limit) ?? base.limit,
9840
9866
  outcome: parseOutcome(query.outcome) ?? base.outcome,
9841
- resourceId: getString3(query.resourceId) ?? base.resourceId,
9842
- resourceType: getString3(query.resourceType) ?? base.resourceType,
9843
- sessionId: getString3(query.sessionId) ?? base.sessionId,
9844
- traceId: getString3(query.traceId) ?? base.traceId,
9867
+ resourceId: getString4(query.resourceId) ?? base.resourceId,
9868
+ resourceType: getString4(query.resourceType) ?? base.resourceType,
9869
+ sessionId: getString4(query.sessionId) ?? base.sessionId,
9870
+ traceId: getString4(query.traceId) ?? base.traceId,
9845
9871
  type: parseType(query.type) ?? base.type
9846
9872
  });
9847
9873
  var summarizeVoiceAuditTrail = (events) => {
@@ -10416,7 +10442,7 @@ var createVoiceAuditSinkDeliveryWorkerLoop = (options) => {
10416
10442
  // src/auditDeliveryRoutes.ts
10417
10443
  import { Elysia as Elysia6 } from "elysia";
10418
10444
  var escapeHtml9 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
10419
- var getString4 = (value) => typeof value === "string" && value.trim() ? value.trim() : undefined;
10445
+ var getString5 = (value) => typeof value === "string" && value.trim() ? value.trim() : undefined;
10420
10446
  var getNumber3 = (value) => {
10421
10447
  if (typeof value === "number" && Number.isFinite(value)) {
10422
10448
  return value;
@@ -10428,13 +10454,13 @@ var getNumber3 = (value) => {
10428
10454
  return;
10429
10455
  };
10430
10456
  var parseStatus = (value) => {
10431
- const text = getString4(value);
10457
+ const text = getString5(value);
10432
10458
  return text === "pending" || text === "delivered" || text === "failed" || text === "skipped" || text === "all" ? text : undefined;
10433
10459
  };
10434
10460
  var resolveVoiceAuditDeliveryFilter = (query = {}, base = {}) => ({
10435
10461
  ...base,
10436
10462
  limit: getNumber3(query.limit) ?? base.limit,
10437
- q: getString4(query.q) ?? base.q,
10463
+ q: getString5(query.q) ?? base.q,
10438
10464
  status: parseStatus(query.status) ?? base.status
10439
10465
  });
10440
10466
  var deliverySearchText = (delivery) => [
@@ -10779,7 +10805,7 @@ var createVoiceReconnectContractRoutes = (options) => {
10779
10805
  // src/diagnosticsRoutes.ts
10780
10806
  import { Elysia as Elysia9 } from "elysia";
10781
10807
  var escapeHtml12 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
10782
- var getString5 = (value) => typeof value === "string" && value.trim() ? value : undefined;
10808
+ var getString6 = (value) => typeof value === "string" && value.trim() ? value : undefined;
10783
10809
  var getNumber4 = (value) => {
10784
10810
  const parsed = typeof value === "number" ? value : typeof value === "string" ? Number(value) : undefined;
10785
10811
  return typeof parsed === "number" && Number.isFinite(parsed) ? parsed : undefined;
@@ -10794,15 +10820,15 @@ var parseTraceTypeFilter = (value) => {
10794
10820
  };
10795
10821
  var resolveVoiceDiagnosticsTraceFilter = (query) => ({
10796
10822
  limit: getNumber4(query.limit),
10797
- scenarioId: getString5(query.scenarioId),
10798
- sessionId: getString5(query.sessionId),
10799
- traceId: getString5(query.traceId),
10800
- turnId: getString5(query.turnId),
10823
+ scenarioId: getString6(query.scenarioId),
10824
+ sessionId: getString6(query.sessionId),
10825
+ traceId: getString6(query.traceId),
10826
+ turnId: getString6(query.turnId),
10801
10827
  type: parseTraceTypeFilter(query.type)
10802
10828
  });
10803
10829
  var filterByDiagnosticsQuery = (events, query) => {
10804
- const provider = getString5(query.provider);
10805
- const status = getString5(query.status);
10830
+ const provider = getString6(query.provider);
10831
+ const status = getString6(query.status);
10806
10832
  const since = getNumber4(query.since);
10807
10833
  const until = getNumber4(query.until);
10808
10834
  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));
@@ -13202,7 +13228,7 @@ import { Elysia as Elysia17 } from "elysia";
13202
13228
  // src/handoffHealth.ts
13203
13229
  import { Elysia as Elysia16 } from "elysia";
13204
13230
  var escapeHtml18 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
13205
- var getString6 = (value) => typeof value === "string" && value.length > 0 ? value : undefined;
13231
+ var getString7 = (value) => typeof value === "string" && value.length > 0 ? value : undefined;
13206
13232
  var isStatus = (value) => value === "delivered" || value === "failed" || value === "skipped";
13207
13233
  var increment3 = (record, key) => {
13208
13234
  record[key] = (record[key] ?? 0) + 1;
@@ -13210,11 +13236,11 @@ var increment3 = (record, key) => {
13210
13236
  var normalizeDelivery = (adapterId, value) => {
13211
13237
  const record = value && typeof value === "object" ? value : {};
13212
13238
  return {
13213
- adapterId: getString6(record.adapterId) ?? adapterId,
13214
- adapterKind: getString6(record.adapterKind),
13239
+ adapterId: getString7(record.adapterId) ?? adapterId,
13240
+ adapterKind: getString7(record.adapterKind),
13215
13241
  deliveredAt: typeof record.deliveredAt === "number" ? record.deliveredAt : undefined,
13216
- deliveredTo: getString6(record.deliveredTo),
13217
- error: getString6(record.error),
13242
+ deliveredTo: getString7(record.deliveredTo),
13243
+ error: getString7(record.error),
13218
13244
  status: isStatus(record.status) ? record.status : "failed"
13219
13245
  };
13220
13246
  };
@@ -13248,13 +13274,13 @@ var summarizeVoiceHandoffHealth = async (options = {}) => {
13248
13274
  const status = isStatus(event.payload.status) ? event.payload.status : "failed";
13249
13275
  const deliveries = normalizeDeliveries(event.payload);
13250
13276
  const item = {
13251
- action: getString6(event.payload.action),
13277
+ action: getString7(event.payload.action),
13252
13278
  at: event.at,
13253
13279
  deliveries,
13254
- reason: getString6(event.payload.reason),
13280
+ reason: getString7(event.payload.reason),
13255
13281
  sessionId: event.sessionId,
13256
13282
  status,
13257
- target: getString6(event.payload.target)
13283
+ target: getString7(event.payload.target)
13258
13284
  };
13259
13285
  return {
13260
13286
  ...item,
@@ -13403,7 +13429,7 @@ var DEFAULT_THRESHOLDS = {
13403
13429
  maxProviderFallbackRate: 0.25,
13404
13430
  maxProviderTimeoutRate: 0.03
13405
13431
  };
13406
- var getString7 = (value) => typeof value === "string" ? value : undefined;
13432
+ var getString8 = (value) => typeof value === "string" ? value : undefined;
13407
13433
  var getNumber5 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
13408
13434
  var rate = (count, total) => count / Math.max(1, total);
13409
13435
  var roundMetric2 = (value) => Math.round(value * 1e4) / 1e4;
@@ -13422,11 +13448,11 @@ var evaluateVoiceQuality = async (input) => {
13422
13448
  const assistantReplies = events.filter((event) => event.type === "turn.assistant");
13423
13449
  const sessionIdsWithAssistantReply = new Set(assistantReplies.map((event) => event.sessionId));
13424
13450
  const sessionsWithTurns = new Set(committedTurns.map((event) => event.sessionId));
13425
- const emptyTurns = committedTurns.filter((event) => !getString7(event.payload.text)?.trim());
13451
+ const emptyTurns = committedTurns.filter((event) => !getString8(event.payload.text)?.trim());
13426
13452
  const turnTextsBySession = new Map;
13427
13453
  let duplicateTurns = 0;
13428
13454
  for (const turn of committedTurns) {
13429
- const normalized = getString7(turn.payload.text)?.trim().toLowerCase();
13455
+ const normalized = getString8(turn.payload.text)?.trim().toLowerCase();
13430
13456
  if (!normalized) {
13431
13457
  continue;
13432
13458
  }
@@ -13546,7 +13572,7 @@ var createVoiceQualityRoutes = (options) => {
13546
13572
  var escapeHtml20 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
13547
13573
  var rate2 = (count, total) => count / Math.max(1, total);
13548
13574
  var normalizeSearchText = (value) => value.trim().toLowerCase();
13549
- var getString8 = (value) => typeof value === "string" ? value : undefined;
13575
+ var getString9 = (value) => typeof value === "string" ? value : undefined;
13550
13576
  var getObject = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
13551
13577
  var getPathValue = (value, path) => {
13552
13578
  let current = value;
@@ -13633,16 +13659,16 @@ var runVoiceSessionEvals = async (options = {}) => {
13633
13659
  trend: buildTrend(limitedSessions)
13634
13660
  };
13635
13661
  };
13636
- var getSessionText = (events, type) => events.filter((event) => event.type === type).map((event) => getString8(event.payload.text)).filter((text) => Boolean(text?.trim())).join(`
13662
+ var getSessionText = (events, type) => events.filter((event) => event.type === type).map((event) => getString9(event.payload.text)).filter((text) => Boolean(text?.trim())).join(`
13637
13663
  `);
13638
13664
  var countProviderErrors = (events) => events.filter((event) => event.type === "session.error" && (event.payload.providerStatus === "error" || typeof event.payload.provider === "string")).length;
13639
13665
  var evaluateScenarioSession = (scenario, sessionId, events) => {
13640
13666
  const issues = [];
13641
13667
  const committedText = getSessionText(events, "turn.committed");
13642
13668
  const assistantText = getSessionText(events, "turn.assistant");
13643
- const lifecycleTypes = events.filter((event) => event.type === "call.lifecycle").map((event) => getString8(event.payload.type)).filter((type) => Boolean(type));
13644
- const dispositions = events.filter((event) => event.type === "call.lifecycle").map((event) => getString8(event.payload.disposition)).filter((disposition) => Boolean(disposition));
13645
- const handoffActions = events.filter((event) => event.type === "call.handoff").map((event) => getString8(event.payload.action)).filter((action) => Boolean(action));
13669
+ const lifecycleTypes = events.filter((event) => event.type === "call.lifecycle").map((event) => getString9(event.payload.type)).filter((type) => Boolean(type));
13670
+ const dispositions = events.filter((event) => event.type === "call.lifecycle").map((event) => getString9(event.payload.disposition)).filter((disposition) => Boolean(disposition));
13671
+ const handoffActions = events.filter((event) => event.type === "call.handoff").map((event) => getString9(event.payload.action)).filter((action) => Boolean(action));
13646
13672
  const turnCount = events.filter((event) => event.type === "turn.committed").length;
13647
13673
  const sessionErrorCount = events.filter((event) => event.type === "session.error").length;
13648
13674
  const providerErrorCount = countProviderErrors(events);
@@ -13691,12 +13717,12 @@ var evaluateScenarioSession = (scenario, sessionId, events) => {
13691
13717
  }
13692
13718
  }
13693
13719
  for (const contractId of scenario.requiredWorkflowContracts ?? []) {
13694
- const matching = workflowContractEvents.filter((event) => getString8(event.payload.contractId) === contractId);
13720
+ const matching = workflowContractEvents.filter((event) => getString9(event.payload.contractId) === contractId);
13695
13721
  if (matching.length === 0) {
13696
13722
  issues.push(`Missing workflow contract: ${contractId}`);
13697
13723
  continue;
13698
13724
  }
13699
- if (matching.some((event) => getString8(event.payload.status) !== "pass")) {
13725
+ if (matching.some((event) => getString9(event.payload.status) !== "pass")) {
13700
13726
  issues.push(`Workflow contract failed: ${contractId}`);
13701
13727
  }
13702
13728
  }
@@ -15139,11 +15165,24 @@ var createVoiceWorkflowContractHandler = (input) => {
15139
15165
  };
15140
15166
  // src/sessionReplay.ts
15141
15167
  import { Elysia as Elysia22 } from "elysia";
15142
- var getString9 = (value) => typeof value === "string" ? value : undefined;
15168
+ var getString10 = (value) => typeof value === "string" ? value : undefined;
15143
15169
  var escapeHtml24 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
15144
15170
  var increment4 = (record, key) => {
15145
15171
  record[key] = (record[key] ?? 0) + 1;
15146
15172
  };
15173
+ var resolveSessionHref = (value, session) => {
15174
+ if (value === false) {
15175
+ return;
15176
+ }
15177
+ if (typeof value === "function") {
15178
+ return value(session);
15179
+ }
15180
+ if (typeof value === "string") {
15181
+ const encoded = encodeURIComponent(session.sessionId);
15182
+ return value.includes(":sessionId") ? value.replace(":sessionId", encoded) : `${value.replace(/\/$/, "")}/${encoded}`;
15183
+ }
15184
+ return;
15185
+ };
15147
15186
  var isProviderErrorEvent = (event) => event.type === "session.error" && (event.payload.providerStatus === "error" || typeof event.payload.error === "string");
15148
15187
  var isRecoveredProviderFallbackEvent = (event) => event.type === "session.error" && (event.payload.providerStatus === "fallback" || event.payload.status === "fallback") && event.payload.recovered === true;
15149
15188
  var turnRecoveryKey = (event) => event.turnId ?? `session:${event.sessionId}`;
@@ -15189,14 +15228,14 @@ var buildReplayTurns = (events) => {
15189
15228
  case "turn.transcript":
15190
15229
  turn.transcripts.push({
15191
15230
  isFinal: event.payload.isFinal === true,
15192
- text: getString9(event.payload.text)
15231
+ text: getString10(event.payload.text)
15193
15232
  });
15194
15233
  break;
15195
15234
  case "turn.committed":
15196
- turn.committedText = getString9(event.payload.text);
15235
+ turn.committedText = getString10(event.payload.text);
15197
15236
  break;
15198
15237
  case "turn.assistant": {
15199
- const text = getString9(event.payload.text);
15238
+ const text = getString10(event.payload.text);
15200
15239
  if (text) {
15201
15240
  turn.assistantReplies.push(text);
15202
15241
  }
@@ -15266,7 +15305,7 @@ var summarizeVoiceSessions = async (options = {}) => {
15266
15305
  let errorCount = 0;
15267
15306
  const recoveredTurns = new Set(sorted.filter(isRecoveredProviderFallbackEvent).map((event) => turnRecoveryKey(event)));
15268
15307
  for (const event of sorted) {
15269
- const provider = getString9(event.payload.provider);
15308
+ const provider = getString10(event.payload.provider);
15270
15309
  if (provider) {
15271
15310
  providers.add(provider);
15272
15311
  }
@@ -15274,7 +15313,7 @@ var summarizeVoiceSessions = async (options = {}) => {
15274
15313
  errorCount += 1;
15275
15314
  increment4(providerErrors, provider ?? "unknown");
15276
15315
  }
15277
- const outcome = getString9(event.payload.outcome);
15316
+ const outcome = getString10(event.payload.outcome);
15278
15317
  if (outcome) {
15279
15318
  latestOutcome = outcome;
15280
15319
  }
@@ -15295,6 +15334,7 @@ var summarizeVoiceSessions = async (options = {}) => {
15295
15334
  const replayHref = options.replayHref === false ? "" : typeof options.replayHref === "function" ? options.replayHref(item) : `${options.replayHref ?? "/api/voice-sessions"}/${encodeURIComponent(sessionId)}/replay/htmx`;
15296
15335
  return {
15297
15336
  ...item,
15337
+ operationsRecordHref: resolveSessionHref(options.operationsRecordHref, item),
15298
15338
  replayHref
15299
15339
  };
15300
15340
  });
@@ -15333,7 +15373,7 @@ var renderVoiceSessionsHTML = (sessions) => sessions.length === 0 ? '<p class="v
15333
15373
  "</dl>",
15334
15374
  session.latestOutcome ? `<p>Outcome: ${escapeHtml24(session.latestOutcome)}</p>` : "",
15335
15375
  session.providers.length ? `<p>Providers: ${session.providers.map(escapeHtml24).join(", ")}</p>` : "",
15336
- session.replayHref ? `<p><a href="${escapeHtml24(session.replayHref)}">Open replay</a></p>` : "",
15376
+ session.replayHref ? `<p>${session.operationsRecordHref ? `<a href="${escapeHtml24(session.operationsRecordHref)}">Open operations record</a> \xB7 ` : ""}<a href="${escapeHtml24(session.replayHref)}">Open replay</a></p>` : "",
15337
15377
  "</article>"
15338
15378
  ].join("")),
15339
15379
  "</div>"
@@ -15626,14 +15666,14 @@ var DEFAULT_WARN_AFTER_MS = 1800;
15626
15666
  var DEFAULT_FAIL_AFTER_MS = 3200;
15627
15667
  var escapeHtml25 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
15628
15668
  var firstNumber = (values) => values.filter((value) => typeof value === "number").sort((left, right) => left - right)[0];
15629
- var getString10 = (value) => typeof value === "string" && value.trim() ? value : undefined;
15669
+ var getString11 = (value) => typeof value === "string" && value.trim() ? value : undefined;
15630
15670
  var createTraceStageIndex = (events) => {
15631
15671
  const index = new Map;
15632
15672
  for (const event of events) {
15633
15673
  if (event.type !== "turn_latency.stage" || !event.turnId) {
15634
15674
  continue;
15635
15675
  }
15636
- const stage = getString10(event.payload.stage);
15676
+ const stage = getString11(event.payload.stage);
15637
15677
  if (!stage) {
15638
15678
  continue;
15639
15679
  }
@@ -15900,7 +15940,7 @@ var TRACE_TYPES = [
15900
15940
  "turn_latency.stage"
15901
15941
  ];
15902
15942
  var getNumber6 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
15903
- var getString11 = (value) => typeof value === "string" && value.trim() ? value : undefined;
15943
+ var getString12 = (value) => typeof value === "string" && value.trim() ? value : undefined;
15904
15944
  var percentile2 = (values, percentileValue) => {
15905
15945
  if (values.length === 0) {
15906
15946
  return;
@@ -15937,7 +15977,7 @@ var providerStageForEvent = (event) => {
15937
15977
  if (event.type === "turn.transcript") {
15938
15978
  return "provider_stt";
15939
15979
  }
15940
- const kind = getString11(event.payload.providerKind) ?? getString11(event.payload.kind) ?? getString11(event.payload.lane);
15980
+ const kind = getString12(event.payload.providerKind) ?? getString12(event.payload.kind) ?? getString12(event.payload.lane);
15941
15981
  if (kind === "llm" || kind === "model") {
15942
15982
  return "provider_llm";
15943
15983
  }
@@ -15956,7 +15996,7 @@ var collectTraceStageMeasurements = (events, options) => {
15956
15996
  if (event.type !== "turn_latency.stage" || !event.turnId) {
15957
15997
  continue;
15958
15998
  }
15959
- const stage = getString11(event.payload.stage);
15999
+ const stage = getString12(event.payload.stage);
15960
16000
  if (!stage) {
15961
16001
  continue;
15962
16002
  }
@@ -16085,7 +16125,7 @@ var collectDirectMeasurements = (events, options) => {
16085
16125
  at: event.at,
16086
16126
  budget: resolveBudget(providerStage, options),
16087
16127
  latencyMs: eventElapsedMs(event),
16088
- provider: getString11(event.payload.provider),
16128
+ provider: getString12(event.payload.provider),
16089
16129
  sessionId: event.sessionId,
16090
16130
  stage: providerStage,
16091
16131
  turnId: event.turnId
@@ -21226,7 +21266,7 @@ var createVoiceProviderCapabilityRoutes = (options) => {
21226
21266
  // src/resilienceRoutes.ts
21227
21267
  import { Elysia as Elysia34 } from "elysia";
21228
21268
  var escapeHtml35 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
21229
- var getString12 = (value) => typeof value === "string" ? value : undefined;
21269
+ var getString13 = (value) => typeof value === "string" ? value : undefined;
21230
21270
  var getNumber7 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
21231
21271
  var getBoolean2 = (value) => value === true;
21232
21272
  var isProviderStatus2 = (value) => value === "error" || value === "fallback" || value === "success";
@@ -21236,24 +21276,24 @@ var listVoiceRoutingEvents = (events) => {
21236
21276
  if (event.type !== "session.error") {
21237
21277
  continue;
21238
21278
  }
21239
- const provider = getString12(event.payload.provider);
21279
+ const provider = getString13(event.payload.provider);
21240
21280
  const providerStatus = isProviderStatus2(event.payload.providerStatus) ? event.payload.providerStatus : undefined;
21241
21281
  if (!provider || !providerStatus) {
21242
21282
  continue;
21243
21283
  }
21244
- const kind = getString12(event.payload.kind);
21284
+ const kind = getString13(event.payload.kind);
21245
21285
  routingEvents.push({
21246
21286
  at: event.at,
21247
21287
  attempt: getNumber7(event.payload.attempt),
21248
21288
  elapsedMs: getNumber7(event.payload.elapsedMs),
21249
- error: getString12(event.payload.error),
21250
- fallbackProvider: getString12(event.payload.fallbackProvider),
21289
+ error: getString13(event.payload.error),
21290
+ fallbackProvider: getString13(event.payload.fallbackProvider),
21251
21291
  kind: kind === "stt" || kind === "tts" ? kind : "llm",
21252
21292
  latencyBudgetMs: getNumber7(event.payload.latencyBudgetMs),
21253
- operation: getString12(event.payload.operation),
21293
+ operation: getString13(event.payload.operation),
21254
21294
  provider,
21255
- routing: getString12(event.payload.routing),
21256
- selectedProvider: getString12(event.payload.selectedProvider),
21295
+ routing: getString13(event.payload.routing),
21296
+ selectedProvider: getString13(event.payload.selectedProvider),
21257
21297
  sessionId: event.sessionId,
21258
21298
  status: providerStatus,
21259
21299
  suppressionRemainingMs: getNumber7(event.payload.suppressionRemainingMs),
@@ -21750,7 +21790,7 @@ import { Elysia as Elysia36 } from "elysia";
21750
21790
  // src/opsRecovery.ts
21751
21791
  import { Elysia as Elysia35 } from "elysia";
21752
21792
  var escapeHtml36 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
21753
- var getString13 = (value) => typeof value === "string" && value.trim() ? value.trim() : undefined;
21793
+ var getString14 = (value) => typeof value === "string" && value.trim() ? value.trim() : undefined;
21754
21794
  var hrefForSession = (value, sessionId) => {
21755
21795
  if (typeof value === "function") {
21756
21796
  return value(sessionId);
@@ -21775,9 +21815,9 @@ var collectFailedSessions = (events, limit, links) => events.filter((event) => {
21775
21815
  return providerStatus !== "success" && providerStatus !== "fallback";
21776
21816
  }).sort((left, right) => right.at - left.at).slice(0, limit).map((event) => ({
21777
21817
  at: event.at,
21778
- error: getString13(event.payload.error),
21818
+ error: getString14(event.payload.error),
21779
21819
  operationsRecordHref: operationsRecordHrefForSession(links, event.sessionId),
21780
- provider: getString13(event.payload.provider),
21820
+ provider: getString14(event.payload.provider),
21781
21821
  sessionId: event.sessionId,
21782
21822
  traceId: event.traceId
21783
21823
  }));
@@ -21785,9 +21825,9 @@ var collectInterventions = (events, limit) => {
21785
21825
  const interventionEvents = events.filter((event) => event.type === "operator.action").sort((left, right) => right.at - left.at);
21786
21826
  return {
21787
21827
  events: interventionEvents.slice(0, limit).map((event) => ({
21788
- action: getString13(event.payload.action),
21828
+ action: getString14(event.payload.action),
21789
21829
  at: event.at,
21790
- operatorId: getString13(event.payload.operatorId) ?? getString13(event.payload.actorId),
21830
+ operatorId: getString14(event.payload.operatorId) ?? getString14(event.payload.actorId),
21791
21831
  sessionId: event.sessionId,
21792
21832
  traceId: event.traceId
21793
21833
  })),
@@ -22362,7 +22402,7 @@ var summarizeLiveLatency = (events, options) => {
22362
22402
  warnings
22363
22403
  };
22364
22404
  };
22365
- var getString14 = (value) => typeof value === "string" ? value : undefined;
22405
+ var getString15 = (value) => typeof value === "string" ? value : undefined;
22366
22406
  var getNumber8 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
22367
22407
  var voiceOperationsRecordHref = (base, sessionId) => {
22368
22408
  const encoded = encodeURIComponent(sessionId);
@@ -22374,7 +22414,7 @@ var voiceOperationsRecordHref = (base, sessionId) => {
22374
22414
  var buildOperationsRecordLinks = (input) => {
22375
22415
  const failedSessionSet = new Set(input.failedSessionIds);
22376
22416
  const providerErrors = input.events.filter((event) => event.type === "session.error" && (event.payload.providerStatus === "error" || typeof event.payload.error === "string")).map((event) => ({
22377
- detail: getString14(event.payload.error),
22417
+ detail: getString15(event.payload.error),
22378
22418
  href: voiceOperationsRecordHref(input.base, event.sessionId),
22379
22419
  label: "Open provider error operations record",
22380
22420
  sessionId: event.sessionId,
@@ -23885,11 +23925,11 @@ import { Elysia as Elysia40 } from "elysia";
23885
23925
  // src/traceTimeline.ts
23886
23926
  import { Elysia as Elysia39 } from "elysia";
23887
23927
  var escapeHtml40 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
23888
- var getString15 = (value) => typeof value === "string" && value.trim() ? value : undefined;
23928
+ var getString16 = (value) => typeof value === "string" && value.trim() ? value : undefined;
23889
23929
  var getNumber9 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
23890
23930
  var firstString3 = (payload, keys) => {
23891
23931
  for (const key of keys) {
23892
- const value = getString15(payload[key]);
23932
+ const value = getString16(payload[key]);
23893
23933
  if (value) {
23894
23934
  return value;
23895
23935
  }
@@ -23919,6 +23959,19 @@ var eventStatus = (event) => firstString3(event.payload, [
23919
23959
  "reason"
23920
23960
  ]);
23921
23961
  var eventElapsedMs2 = (event) => firstNumber3(event.payload, ["elapsedMs", "latencyMs", "durationMs"]);
23962
+ var resolveSessionHref2 = (value, sessionId) => {
23963
+ if (value === false) {
23964
+ return;
23965
+ }
23966
+ if (typeof value === "function") {
23967
+ return value(sessionId);
23968
+ }
23969
+ if (typeof value === "string") {
23970
+ const encoded = encodeURIComponent(sessionId);
23971
+ return value.includes(":sessionId") ? value.replace(":sessionId", encoded) : `${value.replace(/\/$/, "")}/${encoded}`;
23972
+ }
23973
+ return;
23974
+ };
23922
23975
  var timelineLabel = (event) => {
23923
23976
  switch (event.type) {
23924
23977
  case "call.lifecycle":
@@ -23926,15 +23979,15 @@ var timelineLabel = (event) => {
23926
23979
  case "turn.transcript":
23927
23980
  return event.payload.isFinal === true ? "Final transcript" : "Partial transcript";
23928
23981
  case "turn.committed":
23929
- return `Committed turn${getString15(event.payload.reason) ? ` (${getString15(event.payload.reason)})` : ""}`;
23982
+ return `Committed turn${getString16(event.payload.reason) ? ` (${getString16(event.payload.reason)})` : ""}`;
23930
23983
  case "turn.assistant":
23931
23984
  return "Assistant reply";
23932
23985
  case "agent.model":
23933
23986
  return `Model call${eventProvider(event) ? ` via ${eventProvider(event)}` : ""}`;
23934
23987
  case "agent.tool":
23935
- return `Tool ${getString15(event.payload.toolName) ?? "call"}`;
23988
+ return `Tool ${getString16(event.payload.toolName) ?? "call"}`;
23936
23989
  case "agent.handoff":
23937
- return `Agent handoff${getString15(event.payload.targetAgentId) ? ` to ${getString15(event.payload.targetAgentId)}` : ""}`;
23990
+ return `Agent handoff${getString16(event.payload.targetAgentId) ? ` to ${getString16(event.payload.targetAgentId)}` : ""}`;
23938
23991
  case "assistant.run":
23939
23992
  return `Assistant run${eventProvider(event) ? ` via ${eventProvider(event)}` : ""}`;
23940
23993
  case "assistant.guardrail":
@@ -23944,11 +23997,11 @@ var timelineLabel = (event) => {
23944
23997
  case "client.live_latency":
23945
23998
  return `Live latency${eventElapsedMs2(event) !== undefined ? ` ${eventElapsedMs2(event)}ms` : ""}`;
23946
23999
  case "session.error":
23947
- return `Error${getString15(event.payload.error) ? `: ${getString15(event.payload.error)}` : ""}`;
24000
+ return `Error${getString16(event.payload.error) ? `: ${getString16(event.payload.error)}` : ""}`;
23948
24001
  case "turn.cost":
23949
24002
  return "Cost telemetry";
23950
24003
  case "turn_latency.stage":
23951
- return `Latency ${getString15(event.payload.stage) ?? "stage"}`;
24004
+ return `Latency ${getString16(event.payload.stage) ?? "stage"}`;
23952
24005
  case "workflow.contract":
23953
24006
  return `Workflow contract ${eventStatus(event) ?? ""}`.trim();
23954
24007
  default:
@@ -24036,6 +24089,7 @@ var summarizeVoiceTraceTimeline = (events, options = {}) => {
24036
24089
  type: event.type
24037
24090
  })),
24038
24091
  lastEventAt: sorted.at(-1)?.at,
24092
+ operationsRecordHref: resolveSessionHref2(options.operationsRecordHref, sessionId),
24039
24093
  providers: summarizeProviders(sorted),
24040
24094
  sessionId,
24041
24095
  startedAt: summary.startedAt,
@@ -24056,9 +24110,10 @@ var renderProviderCards2 = (session) => session.providers.length === 0 ? '<p cla
24056
24110
  var renderVoiceTraceTimelineSessionHTML = (session, options = {}) => {
24057
24111
  const events = session.events.map((event) => `<tr class="${escapeHtml40(event.status ?? "")}"><td>+${String(event.offsetMs)}ms</td><td>${escapeHtml40(event.type)}</td><td>${escapeHtml40(event.label)}</td><td>${escapeHtml40(event.provider ?? "")}</td><td>${escapeHtml40(event.status ?? "")}</td><td>${formatMs3(event.elapsedMs)}</td></tr>`).join("");
24058
24112
  const issues = session.evaluation.issues.length ? session.evaluation.issues.map((issue) => `<li class="${escapeHtml40(issue.severity)}">${escapeHtml40(issue.code)}: ${escapeHtml40(issue.message)}</li>`).join("") : "<li>none</li>";
24059
- return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml40(options.title ?? "Voice Trace Timeline")}</title><style>${timelineCSS}</style></head><body><main><a href="/traces">Back to traces</a><header><p class="eyebrow">Call timeline</p><h1>${escapeHtml40(session.sessionId)}</h1><p class="status ${escapeHtml40(session.status)}">${escapeHtml40(session.status)}</p></header><section class="metrics"><article><span>Events</span><strong>${String(session.summary.eventCount)}</strong></article><article><span>Turns</span><strong>${String(session.summary.turnCount)}</strong></article><article><span>Errors</span><strong>${String(session.summary.errorCount)}</strong></article><article><span>Duration</span><strong>${formatMs3(session.summary.callDurationMs)}</strong></article></section><section><h2>Providers</h2>${renderProviderCards2(session)}</section><section><h2>Issues</h2><ul>${issues}</ul></section><section><h2>Timeline</h2><table><thead><tr><th>Offset</th><th>Type</th><th>Event</th><th>Provider</th><th>Status</th><th>Latency</th></tr></thead><tbody>${events}</tbody></table></section></main></body></html>`;
24113
+ const supportLinks = session.operationsRecordHref ? `<p><a href="${escapeHtml40(session.operationsRecordHref)}">Open operations record</a></p>` : "";
24114
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml40(options.title ?? "Voice Trace Timeline")}</title><style>${timelineCSS}</style></head><body><main><a href="/traces">Back to traces</a><header><p class="eyebrow">Call timeline</p><h1>${escapeHtml40(session.sessionId)}</h1><p class="status ${escapeHtml40(session.status)}">${escapeHtml40(session.status)}</p>${supportLinks}</header><section class="metrics"><article><span>Events</span><strong>${String(session.summary.eventCount)}</strong></article><article><span>Turns</span><strong>${String(session.summary.turnCount)}</strong></article><article><span>Errors</span><strong>${String(session.summary.errorCount)}</strong></article><article><span>Duration</span><strong>${formatMs3(session.summary.callDurationMs)}</strong></article></section><section><h2>Providers</h2>${renderProviderCards2(session)}</section><section><h2>Issues</h2><ul>${issues}</ul></section><section><h2>Timeline</h2><table><thead><tr><th>Offset</th><th>Type</th><th>Event</th><th>Provider</th><th>Status</th><th>Latency</th></tr></thead><tbody>${events}</tbody></table></section></main></body></html>`;
24060
24115
  };
24061
- var renderSessionRows = (report) => report.sessions.length === 0 ? '<tr><td colspan="7">No trace events recorded yet.</td></tr>' : report.sessions.map((session) => `<tr class="${escapeHtml40(session.status)}"><td><a href="/traces/${encodeURIComponent(session.sessionId)}">${escapeHtml40(session.sessionId)}</a></td><td>${escapeHtml40(session.status)}</td><td>${String(session.summary.eventCount)}</td><td>${String(session.summary.turnCount)}</td><td>${String(session.summary.errorCount)}</td><td>${formatMs3(session.summary.callDurationMs)}</td><td>${session.providers.map((provider) => escapeHtml40(provider.provider)).join(", ")}</td></tr>`).join("");
24116
+ var renderSessionRows = (report) => report.sessions.length === 0 ? '<tr><td colspan="7">No trace events recorded yet.</td></tr>' : report.sessions.map((session) => `<tr class="${escapeHtml40(session.status)}"><td>${session.operationsRecordHref ? `<a href="${escapeHtml40(session.operationsRecordHref)}">${escapeHtml40(session.sessionId)}</a>` : `<a href="/traces/${encodeURIComponent(session.sessionId)}">${escapeHtml40(session.sessionId)}</a>`}</td><td>${escapeHtml40(session.status)}</td><td>${String(session.summary.eventCount)}</td><td>${String(session.summary.turnCount)}</td><td>${String(session.summary.errorCount)}</td><td>${formatMs3(session.summary.callDurationMs)}</td><td>${session.providers.map((provider) => escapeHtml40(provider.provider)).join(", ")}</td></tr>`).join("");
24062
24117
  var timelineCSS = "body{background:#0f1318;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}a{color:#fbbf24}.eyebrow{color:#fbbf24;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,4.5rem);line-height:.92;margin:.2rem 0 1rem}.status{border:1px solid #475569;border-radius:999px;display:inline-flex;padding:8px 12px}.healthy{color:#86efac}.warning{color:#fbbf24}.failed,.error{color:#fca5a5}.metrics,.providers{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(170px,1fr));margin:20px 0}.metrics article,.providers article{background:#181f27;border:1px solid #2b3642;border-radius:20px;padding:16px}.metrics span,dt,.muted{color:#a8b0b8}.metrics strong{display:block;font-size:2rem}dl{display:grid;gap:8px;grid-template-columns:repeat(2,minmax(0,1fr));margin:12px 0 0}dd{font-weight:800;margin:4px 0 0}table{background:#181f27;border-collapse:collapse;border-radius:18px;overflow:hidden;width:100%}td,th{border-bottom:1px solid #2b3642;padding:12px;text-align:left}section{margin-top:28px}@media(max-width:760px){main{padding:20px}table{font-size:.9rem}}";
24063
24118
  var renderVoiceTraceTimelineHTML = (report, options = {}) => {
24064
24119
  const snippet = escapeHtml40(`const traceStore = createVoiceTraceSinkStore({
@@ -24099,12 +24154,14 @@ var createVoiceTraceTimelineRoutes = (options) => {
24099
24154
  const buildReport = async () => summarizeVoiceTraceTimeline(await options.store.list(), {
24100
24155
  evaluation: options.evaluation,
24101
24156
  limit: options.limit,
24157
+ operationsRecordHref: options.operationsRecordHref,
24102
24158
  redact: options.redact
24103
24159
  });
24104
24160
  const findSession = async (sessionId) => {
24105
24161
  const report = summarizeVoiceTraceTimeline(await options.store.list({ sessionId }), {
24106
24162
  evaluation: options.evaluation,
24107
24163
  limit: 1,
24164
+ operationsRecordHref: options.operationsRecordHref,
24108
24165
  redact: options.redact
24109
24166
  });
24110
24167
  return report.sessions[0];
@@ -24141,7 +24198,7 @@ var createVoiceTraceTimelineRoutes = (options) => {
24141
24198
  };
24142
24199
 
24143
24200
  // src/operationsRecord.ts
24144
- var getString16 = (value) => typeof value === "string" ? value : undefined;
24201
+ var getString17 = (value) => typeof value === "string" ? value : undefined;
24145
24202
  var getNumber10 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
24146
24203
  var countOutcome = (events, outcome) => events.filter((event) => event.outcome === outcome).length;
24147
24204
  var matchesSessionScopedId = (id, sessionId) => id === sessionId || id.startsWith(`${sessionId}:`);
@@ -24152,23 +24209,51 @@ var hasPayloadValue = (payload, key, values) => {
24152
24209
  var countIntegrationDeliveryStatus = (events, status) => events.filter((event) => event.deliveryStatus === status).length;
24153
24210
  var toHandoff = (event) => ({
24154
24211
  at: event.at,
24155
- fromAgentId: getString16(event.payload.fromAgentId),
24212
+ fromAgentId: getString17(event.payload.fromAgentId),
24156
24213
  metadata: event.payload.metadata && typeof event.payload.metadata === "object" && !Array.isArray(event.payload.metadata) ? event.payload.metadata : undefined,
24157
- reason: getString16(event.payload.reason),
24158
- status: getString16(event.payload.status),
24159
- summary: getString16(event.payload.summary),
24160
- targetAgentId: getString16(event.payload.targetAgentId),
24214
+ reason: getString17(event.payload.reason),
24215
+ status: getString17(event.payload.status),
24216
+ summary: getString17(event.payload.summary),
24217
+ targetAgentId: getString17(event.payload.targetAgentId),
24161
24218
  turnId: event.turnId
24162
24219
  });
24163
24220
  var toTool = (event) => ({
24164
24221
  at: event.at,
24165
24222
  elapsedMs: getNumber10(event.payload.elapsedMs),
24166
- error: getString16(event.payload.error),
24167
- status: getString16(event.payload.status),
24168
- toolCallId: getString16(event.payload.toolCallId),
24169
- toolName: getString16(event.payload.toolName),
24223
+ error: getString17(event.payload.error),
24224
+ status: getString17(event.payload.status),
24225
+ toolCallId: getString17(event.payload.toolCallId),
24226
+ toolName: getString17(event.payload.toolName),
24170
24227
  turnId: event.turnId
24171
24228
  });
24229
+ var toProviderDecision = (event) => {
24230
+ const provider = getString17(event.payload.provider) ?? getString17(event.payload.selectedProvider) ?? getString17(event.payload.fallbackProvider) ?? getString17(event.payload.variantId);
24231
+ const status = getString17(event.payload.providerStatus) ?? getString17(event.payload.status) ?? getString17(event.payload.reason);
24232
+ const error = getString17(event.payload.error);
24233
+ const elapsedMs = getNumber10(event.payload.elapsedMs) ?? getNumber10(event.payload.latencyMs) ?? getNumber10(event.payload.durationMs);
24234
+ if (!provider && !status && !error && elapsedMs === undefined) {
24235
+ return;
24236
+ }
24237
+ return {
24238
+ at: event.at,
24239
+ elapsedMs,
24240
+ error,
24241
+ fallbackProvider: getString17(event.payload.fallbackProvider),
24242
+ provider,
24243
+ reason: getString17(event.payload.reason),
24244
+ selectedProvider: getString17(event.payload.selectedProvider),
24245
+ status,
24246
+ type: event.type,
24247
+ turnId: event.turnId
24248
+ };
24249
+ };
24250
+ var buildTranscript = (replay) => replay.turns.map((turn) => ({
24251
+ assistantReplies: turn.assistantReplies,
24252
+ committedText: turn.committedText,
24253
+ errors: turn.errors.map((error) => getString17(error.error) ?? JSON.stringify(error)).filter((error) => typeof error === "string"),
24254
+ id: turn.id,
24255
+ transcripts: turn.transcripts.map((transcript) => transcript.text).filter((text) => typeof text === "string" && text.length > 0)
24256
+ })).filter((turn) => turn.committedText || turn.assistantReplies.length > 0 || turn.transcripts.length > 0 || turn.errors.length > 0);
24172
24257
  var resolveOutcome4 = (events) => {
24173
24258
  const agentResults = events.filter((event) => event.type === "agent.result");
24174
24259
  return {
@@ -24224,6 +24309,7 @@ var buildVoiceOperationsRecord = async (options) => {
24224
24309
  total: integrationEvents.length
24225
24310
  } : undefined,
24226
24311
  outcome: resolveOutcome4(traceEvents),
24312
+ providerDecisions: traceEvents.map(toProviderDecision).filter((decision) => decision !== undefined),
24227
24313
  providers: timelineSession?.providers ?? [],
24228
24314
  replay,
24229
24315
  reviews: reviews ? {
@@ -24244,13 +24330,46 @@ var buildVoiceOperationsRecord = async (options) => {
24244
24330
  } : undefined,
24245
24331
  timeline: timelineSession?.events ?? [],
24246
24332
  tools: traceEvents.filter((event) => event.type === "agent.tool").map(toTool),
24247
- traceEvents
24333
+ traceEvents,
24334
+ transcript: buildTranscript(replay)
24248
24335
  };
24249
24336
  };
24250
24337
  var escapeHtml41 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
24251
24338
  var formatMs4 = (value) => value === undefined ? "n/a" : `${String(value)}ms`;
24339
+ var outcomeLabels = (outcome) => [
24340
+ outcome.complete ? "complete" : undefined,
24341
+ outcome.escalated ? "escalated" : undefined,
24342
+ outcome.transferred ? "transferred" : undefined,
24343
+ outcome.voicemail ? "voicemail" : undefined,
24344
+ outcome.noAnswer ? "no-answer" : undefined
24345
+ ].filter((label) => label !== undefined);
24346
+ var renderIncidentHandoffMarkdown = (record) => {
24347
+ const outcomes = outcomeLabels(record.outcome);
24348
+ const topErrors = record.traceEvents.filter((event) => event.type === "session.error").map((event) => getString17(event.payload.error)).filter((error) => typeof error === "string").slice(0, 3);
24349
+ const openTasks = record.tasks?.tasks.filter((task) => task.status !== "done").map((task) => task.title).slice(0, 3) ?? [];
24350
+ return [
24351
+ `# Voice incident handoff: ${record.sessionId}`,
24352
+ "",
24353
+ `- Status: ${record.status}`,
24354
+ `- Duration: ${formatMs4(record.summary.callDurationMs)}`,
24355
+ `- Turns: ${String(record.summary.turnCount)}`,
24356
+ `- Errors: ${String(record.summary.errorCount)}`,
24357
+ `- Outcome: ${outcomes.join(", ") || "unknown"}`,
24358
+ `- Providers: ${record.providers.map((provider) => provider.provider).join(", ") || "none recorded"}`,
24359
+ `- Open tasks: ${openTasks.join("; ") || "none"}`,
24360
+ `- Top errors: ${topErrors.join("; ") || "none"}`,
24361
+ "",
24362
+ "## Next checks",
24363
+ "- Review provider decisions and fallback status.",
24364
+ "- Review transcript and assistant replies.",
24365
+ "- Review handoffs, tools, audit, tasks, and integration delivery."
24366
+ ].join(`
24367
+ `);
24368
+ };
24252
24369
  var renderVoiceOperationsRecordHTML = (record, options = {}) => {
24253
24370
  const providers = record.providers.length ? record.providers.map((provider) => `<article><strong>${escapeHtml41(provider.provider)}</strong><span>${String(provider.eventCount)} events</span><span>${formatMs4(provider.averageElapsedMs)} avg</span><span>${String(provider.errorCount)} errors</span></article>`).join("") : '<p class="muted">No provider events recorded.</p>';
24371
+ const transcript = record.transcript.length ? record.transcript.map((turn) => `<li><strong>${escapeHtml41(turn.id)}</strong>${turn.committedText ? `<p><span class="label">Caller</span>${escapeHtml41(turn.committedText)}</p>` : ""}${turn.assistantReplies.map((reply) => `<p><span class="label">Assistant</span>${escapeHtml41(reply)}</p>`).join("")}${turn.errors.map((error) => `<p class="error"><span class="label">Error</span>${escapeHtml41(error)}</p>`).join("")}</li>`).join("") : "<li>No transcript turns recorded.</li>";
24372
+ const providerDecisions = record.providerDecisions.length ? record.providerDecisions.map((decision) => `<li><strong>${escapeHtml41(decision.provider ?? decision.selectedProvider ?? decision.fallbackProvider ?? "provider")}</strong> <span>${escapeHtml41(decision.status ?? decision.type)}</span> ${formatMs4(decision.elapsedMs)}${decision.fallbackProvider ? `<p>Fallback: ${escapeHtml41(decision.fallbackProvider)}</p>` : ""}${decision.error ? `<p class="error">${escapeHtml41(decision.error)}</p>` : ""}${decision.reason ? `<p>${escapeHtml41(decision.reason)}</p>` : ""}</li>`).join("") : "<li>No provider decisions recorded.</li>";
24254
24373
  const handoffs = record.handoffs.length ? record.handoffs.map((handoff) => `<li><strong>${escapeHtml41(handoff.fromAgentId ?? "unknown")}</strong> to <strong>${escapeHtml41(handoff.targetAgentId ?? "unknown")}</strong> <span>${escapeHtml41(handoff.status ?? "")}</span><p>${escapeHtml41(handoff.summary ?? handoff.reason ?? "")}</p></li>`).join("") : "<li>No agent handoffs recorded.</li>";
24255
24374
  const tools = record.tools.length ? record.tools.map((tool) => `<li><strong>${escapeHtml41(tool.toolName ?? "tool")}</strong> <span>${escapeHtml41(tool.status ?? "")}</span> ${formatMs4(tool.elapsedMs)} ${tool.error ? `<p>${escapeHtml41(tool.error)}</p>` : ""}</li>`).join("") : "<li>No tool calls recorded.</li>";
24256
24375
  const reviews = record.reviews?.reviews.length ? record.reviews.reviews.map((review) => `<li><strong>${escapeHtml41(review.title)}</strong> <span>${escapeHtml41(review.summary.outcome ?? "")}</span><p>${escapeHtml41(review.postCall?.summary ?? review.transcript.actual)}</p></li>`).join("") : "<li>No call reviews recorded.</li>";
@@ -24270,7 +24389,8 @@ var renderVoiceOperationsRecordHTML = (record, options = {}) => {
24270
24389
  tasks: opsTasks
24271
24390
  })
24272
24391
  );`);
24273
- return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml41(options.title ?? "Voice Operations Record")}</title><style>body{background:#101417;color:#f9f4e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1120px;padding:32px}.eyebrow{color:#fbbf24;font-size:.8rem;font-weight:900;letter-spacing:.14em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,4.8rem);line-height:.9;margin:.2rem 0 1rem}.status{border:1px solid #475569;border-radius:999px;display:inline-flex;padding:8px 12px}.healthy{color:#86efac}.warning{color:#fbbf24}.failed,.error{color:#fca5a5}.grid{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));margin:20px 0}.card,.primitive{background:#182025;border:1px solid #2d3a43;border-radius:20px;padding:16px}.card span,.muted{color:#a9b4bd}.card strong{display:block;font-size:2rem}section{margin-top:28px}article{display:grid;gap:8px}ul{display:grid;gap:10px;list-style:none;padding:0}li{background:#182025;border:1px solid #2d3a43;border-radius:16px;padding:14px}pre{background:#080d10;border:1px solid #2d3a43;border-radius:16px;color:#dbeafe;overflow:auto;padding:14px}</style></head><body><main><p class="eyebrow">Portable production proof</p><h1>${escapeHtml41(options.title ?? "Voice Operations Record")}</h1><p class="status ${escapeHtml41(record.status)}">${escapeHtml41(record.status)}</p><section class="grid"><div class="card"><span>Events</span><strong>${String(record.summary.eventCount)}</strong></div><div class="card"><span>Turns</span><strong>${String(record.summary.turnCount)}</strong></div><div class="card"><span>Errors</span><strong>${String(record.summary.errorCount)}</strong></div><div class="card"><span>Duration</span><strong>${formatMs4(record.summary.callDurationMs)}</strong></div><div class="card"><span>Audit</span><strong>${String(record.audit?.total ?? 0)}</strong></div><div class="card"><span>Reviews</span><strong>${String(record.reviews?.total ?? 0)}</strong></div><div class="card"><span>Tasks</span><strong>${String(record.tasks?.total ?? 0)}</strong></div><div class="card"><span>Integrations</span><strong>${String(record.integrationEvents?.total ?? 0)}</strong></div></section><section class="primitive"><p class="eyebrow">Copy into your app</p><h2><code>createVoiceOperationsRecordRoutes(...)</code> gives every call one debuggable object</h2><p class="muted">Use this as the support/debug payload across traces, provider routing, tools, handoffs, audit, latency, replay, reviews, tasks, and webhook delivery.</p><pre><code>${snippet}</code></pre></section><section><h2>Providers</h2><div class="grid">${providers}</div></section><section><h2>Handoffs</h2><ul>${handoffs}</ul></section><section><h2>Tools</h2><ul>${tools}</ul></section><section><h2>Reviews</h2><ul>${reviews}</ul></section><section><h2>Tasks</h2><ul>${tasks}</ul></section><section><h2>Integration Events</h2><ul>${integrationEvents}</ul></section></main></body></html>`;
24392
+ const incidentMarkdown = escapeHtml41(renderIncidentHandoffMarkdown(record));
24393
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml41(options.title ?? "Voice Operations Record")}</title><style>body{background:#101417;color:#f9f4e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1120px;padding:32px}.eyebrow{color:#fbbf24;font-size:.8rem;font-weight:900;letter-spacing:.14em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,4.8rem);line-height:.9;margin:.2rem 0 1rem}.status{border:1px solid #475569;border-radius:999px;display:inline-flex;padding:8px 12px}.healthy{color:#86efac}.warning{color:#fbbf24}.failed,.error{color:#fca5a5}.grid{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));margin:20px 0}.card,.primitive{background:#182025;border:1px solid #2d3a43;border-radius:20px;padding:16px}.card span,.muted,.label{color:#a9b4bd}.label{display:block;font-size:.72rem;font-weight:900;letter-spacing:.12em;text-transform:uppercase}.card strong{display:block;font-size:2rem}section{margin-top:28px}article{display:grid;gap:8px}ul{display:grid;gap:10px;list-style:none;padding:0}li{background:#182025;border:1px solid #2d3a43;border-radius:16px;padding:14px}pre{background:#080d10;border:1px solid #2d3a43;border-radius:16px;color:#dbeafe;overflow:auto;padding:14px}.hero-actions{display:flex;flex-wrap:wrap;gap:10px;margin-top:16px}.hero-actions a{background:#fbbf24;border-radius:999px;color:#111827;font-weight:900;padding:10px 14px;text-decoration:none}.two-column{display:grid;gap:18px;grid-template-columns:minmax(0,1.15fr) minmax(280px,.85fr)}@media(max-width:860px){main{padding:20px}.two-column{grid-template-columns:1fr}}</style></head><body><main><p class="eyebrow">Call log replacement</p><h1>${escapeHtml41(options.title ?? "Voice Operations Record")}</h1><p class="status ${escapeHtml41(record.status)}">${escapeHtml41(record.status)}</p><div class="hero-actions"><a href="#transcript">Transcript</a><a href="#provider-decisions">Provider decisions</a><a href="#incident-handoff">Incident handoff</a></div><section class="grid"><div class="card"><span>Events</span><strong>${String(record.summary.eventCount)}</strong></div><div class="card"><span>Turns</span><strong>${String(record.summary.turnCount)}</strong></div><div class="card"><span>Errors</span><strong>${String(record.summary.errorCount)}</strong></div><div class="card"><span>Duration</span><strong>${formatMs4(record.summary.callDurationMs)}</strong></div><div class="card"><span>Audit</span><strong>${String(record.audit?.total ?? 0)}</strong></div><div class="card"><span>Reviews</span><strong>${String(record.reviews?.total ?? 0)}</strong></div><div class="card"><span>Tasks</span><strong>${String(record.tasks?.total ?? 0)}</strong></div><div class="card"><span>Integrations</span><strong>${String(record.integrationEvents?.total ?? 0)}</strong></div></section><section class="two-column"><div><h2 id="transcript">Transcript</h2><ul>${transcript}</ul></div><div><h2 id="provider-decisions">Provider Decisions</h2><ul>${providerDecisions}</ul></div></section><section id="incident-handoff"><h2>Copyable Incident Handoff</h2><p class="muted">Paste this into Slack, Linear, Zendesk, or an incident review.</p><pre><code>${incidentMarkdown}</code></pre></section><section class="primitive"><p class="eyebrow">Copy into your app</p><h2><code>createVoiceOperationsRecordRoutes(...)</code> gives every call one debuggable object</h2><p class="muted">Use this as the support/debug payload across traces, provider routing, tools, handoffs, audit, latency, replay, reviews, tasks, and webhook delivery.</p><pre><code>${snippet}</code></pre></section><section><h2>Provider Summary</h2><div class="grid">${providers}</div></section><section><h2>Handoffs</h2><ul>${handoffs}</ul></section><section><h2>Tools</h2><ul>${tools}</ul></section><section><h2>Reviews</h2><ul>${reviews}</ul></section><section><h2>Tasks</h2><ul>${tasks}</ul></section><section><h2>Integration Events</h2><ul>${integrationEvents}</ul></section></main></body></html>`;
24274
24394
  };
24275
24395
  var createVoiceOperationsRecordRoutes = (options) => {
24276
24396
  const path = options.path ?? "/api/voice-operations/:sessionId";
@@ -25155,7 +25275,7 @@ var createVoiceTTSProviderRouter = (options) => {
25155
25275
  // src/traceDeliveryRoutes.ts
25156
25276
  import { Elysia as Elysia43 } from "elysia";
25157
25277
  var escapeHtml43 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
25158
- var getString17 = (value) => typeof value === "string" && value.trim() ? value.trim() : undefined;
25278
+ var getString18 = (value) => typeof value === "string" && value.trim() ? value.trim() : undefined;
25159
25279
  var getNumber11 = (value) => {
25160
25280
  if (typeof value === "number" && Number.isFinite(value)) {
25161
25281
  return value;
@@ -25167,13 +25287,13 @@ var getNumber11 = (value) => {
25167
25287
  return;
25168
25288
  };
25169
25289
  var parseStatus2 = (value) => {
25170
- const text = getString17(value);
25290
+ const text = getString18(value);
25171
25291
  return text === "pending" || text === "delivered" || text === "failed" || text === "skipped" || text === "all" ? text : undefined;
25172
25292
  };
25173
25293
  var resolveVoiceTraceDeliveryFilter = (query = {}, base = {}) => ({
25174
25294
  ...base,
25175
25295
  limit: getNumber11(query.limit) ?? base.limit,
25176
- q: getString17(query.q) ?? base.q,
25296
+ q: getString18(query.q) ?? base.q,
25177
25297
  status: parseStatus2(query.status) ?? base.status
25178
25298
  });
25179
25299
  var deliverySearchText2 = (delivery) => [
@@ -33,6 +33,25 @@ export type VoiceOperationsRecordTool = {
33
33
  toolName?: string;
34
34
  turnId?: string;
35
35
  };
36
+ export type VoiceOperationsRecordTranscriptTurn = {
37
+ assistantReplies: string[];
38
+ committedText?: string;
39
+ errors: string[];
40
+ id: string;
41
+ transcripts: string[];
42
+ };
43
+ export type VoiceOperationsRecordProviderDecision = {
44
+ at: number;
45
+ elapsedMs?: number;
46
+ error?: string;
47
+ fallbackProvider?: string;
48
+ provider?: string;
49
+ reason?: string;
50
+ selectedProvider?: string;
51
+ status?: string;
52
+ type: StoredVoiceTraceEvent['type'];
53
+ turnId?: string;
54
+ };
36
55
  export type VoiceOperationsRecordAuditSummary = {
37
56
  error: number;
38
57
  events: StoredVoiceAuditEvent[];
@@ -68,6 +87,7 @@ export type VoiceOperationsRecord = {
68
87
  handoffs: VoiceOperationsRecordAgentHandoff[];
69
88
  integrationEvents?: VoiceOperationsRecordIntegrationEventSummary;
70
89
  outcome: VoiceOperationsRecordOutcome;
90
+ providerDecisions: VoiceOperationsRecordProviderDecision[];
71
91
  providers: VoiceTraceTimelineProviderSummary[];
72
92
  replay: VoiceSessionReplay;
73
93
  reviews?: VoiceOperationsRecordReviewSummary;
@@ -78,6 +98,7 @@ export type VoiceOperationsRecord = {
78
98
  timeline: VoiceTraceTimelineEvent[];
79
99
  tools: VoiceOperationsRecordTool[];
80
100
  traceEvents: StoredVoiceTraceEvent[];
101
+ transcript: VoiceOperationsRecordTranscriptTurn[];
81
102
  };
82
103
  export type VoiceOperationsRecordOptions = {
83
104
  audit?: VoiceAuditEventStore;
@@ -34,6 +34,7 @@ export type VoiceSessionListItem = {
34
34
  errorCount: number;
35
35
  eventCount: number;
36
36
  latestOutcome?: string;
37
+ operationsRecordHref?: string;
37
38
  providerErrors: Record<string, number>;
38
39
  providers: string[];
39
40
  replayHref: string;
@@ -55,6 +56,7 @@ export type VoiceProviderFallbackRecoverySummary = {
55
56
  export type VoiceSessionListOptions = {
56
57
  events?: StoredVoiceTraceEvent[];
57
58
  limit?: number;
59
+ operationsRecordHref?: false | string | ((session: Omit<VoiceSessionListItem, 'operationsRecordHref' | 'replayHref'>) => string);
58
60
  provider?: string;
59
61
  q?: string;
60
62
  replayHref?: false | string | ((session: Omit<VoiceSessionListItem, 'replayHref'>) => string);
@@ -26,6 +26,7 @@ export type VoiceTraceTimelineSession = {
26
26
  evaluation: ReturnType<typeof evaluateVoiceTrace>;
27
27
  events: VoiceTraceTimelineEvent[];
28
28
  lastEventAt?: number;
29
+ operationsRecordHref?: string;
29
30
  providers: VoiceTraceTimelineProviderSummary[];
30
31
  sessionId: string;
31
32
  startedAt?: number;
@@ -45,6 +46,7 @@ export type VoiceTraceTimelineRoutesOptions = {
45
46
  htmlPath?: string;
46
47
  limit?: number;
47
48
  name?: string;
49
+ operationsRecordHref?: false | string | ((sessionId: string) => string);
48
50
  path?: string;
49
51
  redact?: VoiceTraceRedactionConfig;
50
52
  render?: (report: VoiceTraceTimelineReport) => string | Promise<string>;
@@ -55,6 +57,7 @@ export type VoiceTraceTimelineRoutesOptions = {
55
57
  export declare const summarizeVoiceTraceTimeline: (events: StoredVoiceTraceEvent[], options?: {
56
58
  evaluation?: VoiceTraceEvaluationOptions;
57
59
  limit?: number;
60
+ operationsRecordHref?: false | string | ((sessionId: string) => string);
58
61
  redact?: VoiceTraceRedactionConfig;
59
62
  }) => VoiceTraceTimelineReport;
60
63
  export declare const renderVoiceTraceTimelineSessionHTML: (session: VoiceTraceTimelineSession, options?: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.197",
3
+ "version": "0.0.22-beta.199",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",