@absolutejs/voice 0.0.22-beta.534 → 0.0.22-beta.536

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.
Files changed (224) hide show
  1. package/dist/angular/index.js +347 -347
  2. package/dist/angular/voice-cost-dashboard.service.d.ts +1 -1
  3. package/dist/angular/voice-replay-timeline.service.d.ts +1 -1
  4. package/dist/client/agentSquadStatusWidget.d.ts +3 -3
  5. package/dist/client/browserVoiceSupport.d.ts +1 -1
  6. package/dist/client/callDebugger.d.ts +2 -2
  7. package/dist/client/callDebuggerWidget.d.ts +3 -3
  8. package/dist/client/campaignDialerProof.d.ts +4 -4
  9. package/dist/client/deliveryRuntime.d.ts +4 -4
  10. package/dist/client/deliveryRuntimeWidget.d.ts +3 -3
  11. package/dist/client/htmxAttributes.d.ts +1 -5
  12. package/dist/client/htmxBootstrap.js +82 -82
  13. package/dist/client/htmxDashboardRenderers.d.ts +17 -17
  14. package/dist/client/index.js +2478 -2484
  15. package/dist/client/liveOps.d.ts +2 -2
  16. package/dist/client/liveOpsWidget.d.ts +3 -3
  17. package/dist/client/opsActionCenter.d.ts +3 -3
  18. package/dist/client/opsActionCenterWidget.d.ts +3 -3
  19. package/dist/client/opsActionHistory.d.ts +2 -2
  20. package/dist/client/opsActionHistoryWidget.d.ts +2 -2
  21. package/dist/client/opsStatus.d.ts +2 -2
  22. package/dist/client/opsStatusWidget.d.ts +4 -4
  23. package/dist/client/platformCoverage.d.ts +2 -2
  24. package/dist/client/platformCoverageWidget.d.ts +3 -3
  25. package/dist/client/profileComparison.d.ts +2 -2
  26. package/dist/client/profileComparisonWidget.d.ts +3 -3
  27. package/dist/client/profileSwitchRecommendation.d.ts +2 -2
  28. package/dist/client/profileSwitchRecommendationWidget.d.ts +3 -3
  29. package/dist/client/proofTrends.d.ts +2 -2
  30. package/dist/client/proofTrendsWidget.d.ts +3 -3
  31. package/dist/client/providerCapabilities.d.ts +2 -2
  32. package/dist/client/providerCapabilitiesWidget.d.ts +3 -3
  33. package/dist/client/providerContracts.d.ts +2 -2
  34. package/dist/client/providerContractsWidget.d.ts +3 -3
  35. package/dist/client/providerSimulationControls.d.ts +1 -1
  36. package/dist/client/providerSimulationControlsWidget.d.ts +4 -4
  37. package/dist/client/providerStatus.d.ts +2 -2
  38. package/dist/client/providerStatusWidget.d.ts +3 -3
  39. package/dist/client/readinessFailures.d.ts +2 -2
  40. package/dist/client/readinessFailuresWidget.d.ts +3 -3
  41. package/dist/client/reconnectProfileEvidence.d.ts +2 -2
  42. package/dist/client/reconnectProfileEvidenceWidget.d.ts +3 -3
  43. package/dist/client/routingStatus.d.ts +2 -2
  44. package/dist/client/routingStatusWidget.d.ts +3 -3
  45. package/dist/client/sessionObservability.d.ts +2 -2
  46. package/dist/client/sessionObservabilityWidget.d.ts +3 -3
  47. package/dist/client/sessionSnapshot.d.ts +2 -2
  48. package/dist/client/sessionSnapshotWidget.d.ts +2 -2
  49. package/dist/client/traceTimeline.d.ts +2 -2
  50. package/dist/client/traceTimelineWidget.d.ts +3 -3
  51. package/dist/client/turnLatency.d.ts +4 -4
  52. package/dist/client/turnLatencyWidget.d.ts +3 -3
  53. package/dist/client/turnQuality.d.ts +2 -2
  54. package/dist/client/turnQualityWidget.d.ts +3 -3
  55. package/dist/client/workflowStatus.d.ts +2 -2
  56. package/dist/core/agent.d.ts +1 -1
  57. package/dist/core/agentSquadContract.d.ts +2 -2
  58. package/dist/core/agentState.d.ts +1 -1
  59. package/dist/core/aiScorecard.d.ts +1 -1
  60. package/dist/core/assistant.d.ts +1 -1
  61. package/dist/core/assistantHealth.d.ts +3 -3
  62. package/dist/core/assistantMemory.d.ts +7 -7
  63. package/dist/core/audioConditioning.d.ts +1 -1
  64. package/dist/core/audit.d.ts +6 -6
  65. package/dist/core/auditDeliveryRoutes.d.ts +7 -7
  66. package/dist/core/auditExport.d.ts +10 -10
  67. package/dist/core/auditRoutes.d.ts +5 -5
  68. package/dist/core/auditSinks.d.ts +7 -7
  69. package/dist/core/bargeInRoutes.d.ts +6 -6
  70. package/dist/core/bookingFlow.d.ts +1 -1
  71. package/dist/core/browserCallProfiles.d.ts +3 -3
  72. package/dist/core/browserMediaRoutes.d.ts +5 -5
  73. package/dist/core/callDebugger.d.ts +1 -1
  74. package/dist/core/callDisposition.d.ts +1 -1
  75. package/dist/core/callScorecard.d.ts +1 -1
  76. package/dist/core/campaign.d.ts +5 -5
  77. package/dist/core/campaignControls.d.ts +1 -1
  78. package/dist/core/campaignDialers.d.ts +4 -4
  79. package/dist/core/campaignTemplate.d.ts +1 -1
  80. package/dist/core/competitiveCoverage.d.ts +2 -2
  81. package/dist/core/conversationSimulator.d.ts +1 -1
  82. package/dist/core/correction.d.ts +1 -1
  83. package/dist/core/dataControl.d.ts +5 -5
  84. package/dist/core/deliveryRuntime.d.ts +7 -7
  85. package/dist/core/deliverySinkRoutes.d.ts +7 -7
  86. package/dist/core/demoReadyRoutes.d.ts +1 -1
  87. package/dist/core/dncRegistry.d.ts +1 -1
  88. package/dist/core/dtmfCollector.d.ts +1 -1
  89. package/dist/core/evalRoutes.d.ts +16 -16
  90. package/dist/core/fileStore.d.ts +18 -18
  91. package/dist/core/guardrails.d.ts +2 -2
  92. package/dist/core/handoff.d.ts +4 -4
  93. package/dist/core/handoffHealth.d.ts +4 -4
  94. package/dist/core/htmx.d.ts +1 -1
  95. package/dist/core/incidentBundle.d.ts +1 -1
  96. package/dist/core/incidentTimeline.d.ts +11 -11
  97. package/dist/core/latencySlo.d.ts +1 -1
  98. package/dist/core/liveCoach.d.ts +1 -1
  99. package/dist/core/liveLatency.d.ts +3 -3
  100. package/dist/core/liveOps.d.ts +6 -6
  101. package/dist/core/mediaPipelineRoutes.d.ts +4 -4
  102. package/dist/core/monitor.d.ts +1 -1
  103. package/dist/core/multilingualProof.d.ts +1 -1
  104. package/dist/core/observabilityExport.d.ts +15 -15
  105. package/dist/core/operationalStatus.d.ts +3 -3
  106. package/dist/core/operationsRecord.d.ts +8 -8
  107. package/dist/core/ops.d.ts +58 -58
  108. package/dist/core/opsActionAuditRoutes.d.ts +10 -10
  109. package/dist/core/opsConsoleRoutes.d.ts +3 -3
  110. package/dist/core/opsRecovery.d.ts +4 -4
  111. package/dist/core/opsSinks.d.ts +6 -6
  112. package/dist/core/opsStatusRoutes.d.ts +3 -3
  113. package/dist/core/opsWebhook.d.ts +9 -9
  114. package/dist/core/otelExporter.d.ts +1 -1
  115. package/dist/core/outcomeContract.d.ts +6 -6
  116. package/dist/core/pathway.d.ts +2 -2
  117. package/dist/core/pathwayRuntime.d.ts +2 -2
  118. package/dist/core/phoneAgent.d.ts +2 -2
  119. package/dist/core/phoneAgentProductionSmoke.d.ts +7 -7
  120. package/dist/core/platformCoverage.d.ts +1 -1
  121. package/dist/core/postCallSurvey.d.ts +1 -1
  122. package/dist/core/postgresStore.d.ts +8 -8
  123. package/dist/core/productionReadiness.d.ts +9 -9
  124. package/dist/core/profileSwitchRecommendation.d.ts +9 -9
  125. package/dist/core/proofAssertions.d.ts +1 -1
  126. package/dist/core/proofPack.d.ts +12 -12
  127. package/dist/core/proofRunner.d.ts +2 -2
  128. package/dist/core/proofTrends.d.ts +26 -26
  129. package/dist/core/providerCapabilities.d.ts +5 -5
  130. package/dist/core/providerDecisionTraces.d.ts +4 -4
  131. package/dist/core/providerHealth.d.ts +3 -3
  132. package/dist/core/providerOrchestration.d.ts +4 -4
  133. package/dist/core/providerRouterTraces.d.ts +2 -2
  134. package/dist/core/providerRoutingContract.d.ts +2 -2
  135. package/dist/core/providerSlo.d.ts +5 -5
  136. package/dist/core/providerStackRecommendations.d.ts +8 -8
  137. package/dist/core/qualityRoutes.d.ts +3 -3
  138. package/dist/core/queue.d.ts +26 -26
  139. package/dist/core/realtimeChannel.d.ts +5 -5
  140. package/dist/core/realtimeProviderContracts.d.ts +3 -3
  141. package/dist/core/reconnectContract.d.ts +16 -16
  142. package/dist/core/recordingStore.d.ts +2 -2
  143. package/dist/core/reminderScheduler.d.ts +1 -1
  144. package/dist/core/resilienceRoutes.d.ts +1 -1
  145. package/dist/core/routing.d.ts +1 -1
  146. package/dist/core/sessionObservability.d.ts +2 -2
  147. package/dist/core/sessionReplay.d.ts +12 -12
  148. package/dist/core/sessionSnapshot.d.ts +1 -1
  149. package/dist/core/simulationSuite.d.ts +4 -4
  150. package/dist/core/sloCalibration.d.ts +12 -12
  151. package/dist/core/sqliteStore.d.ts +8 -8
  152. package/dist/core/telephonyMediaRoutes.d.ts +4 -4
  153. package/dist/core/telephonyOutcome.d.ts +2 -2
  154. package/dist/core/toolContract.d.ts +10 -10
  155. package/dist/core/toolRuntime.d.ts +1 -1
  156. package/dist/core/trace.d.ts +18 -18
  157. package/dist/core/traceDeliveryRoutes.d.ts +7 -7
  158. package/dist/core/traceTimeline.d.ts +3 -3
  159. package/dist/core/turnLatency.d.ts +4 -4
  160. package/dist/core/turnQuality.d.ts +5 -5
  161. package/dist/core/types.d.ts +7 -2
  162. package/dist/core/voiceMonitoring.d.ts +11 -11
  163. package/dist/core/webhookVerification.d.ts +4 -4
  164. package/dist/core/whisperChannel.d.ts +4 -4
  165. package/dist/core/workflowContract.d.ts +13 -13
  166. package/dist/core/zeroDataRetention.d.ts +3 -13
  167. package/dist/drizzle/assistantMemory.d.ts +95 -0
  168. package/dist/drizzle/eval.d.ts +61 -0
  169. package/dist/drizzle/handoff.d.ts +62 -0
  170. package/dist/drizzle/incidentBundle.d.ts +61 -0
  171. package/dist/drizzle/index.d.ts +1088 -0
  172. package/dist/drizzle/index.js +3064 -0
  173. package/dist/drizzle/observabilityExport.d.ts +61 -0
  174. package/dist/drizzle/proofTrends.d.ts +126 -0
  175. package/dist/drizzle/runtimeStorage.d.ts +1315 -0
  176. package/dist/drizzle/shared.d.ts +76 -0
  177. package/dist/embed/index.js +72 -72
  178. package/dist/embed/voice-widget.js +2 -2
  179. package/dist/index.js +7034 -7101
  180. package/dist/react/index.js +2148 -2150
  181. package/dist/svelte/createVoiceAgentSquadStatus.d.ts +2 -2
  182. package/dist/svelte/createVoiceCallDebugger.d.ts +1 -1
  183. package/dist/svelte/createVoiceCallPlayer.d.ts +8 -8
  184. package/dist/svelte/createVoiceCampaignDialerProof.d.ts +2 -2
  185. package/dist/svelte/createVoiceCostDashboard.d.ts +4 -4
  186. package/dist/svelte/createVoiceDeliveryRuntime.d.ts +3 -3
  187. package/dist/svelte/createVoiceLiveAgentConsole.d.ts +4 -4
  188. package/dist/svelte/createVoiceLiveOps.d.ts +4 -4
  189. package/dist/svelte/createVoiceOpsActionCenter.d.ts +2 -2
  190. package/dist/svelte/createVoiceOpsStatus.d.ts +2 -2
  191. package/dist/svelte/createVoiceProviderCapabilities.d.ts +1 -1
  192. package/dist/svelte/createVoiceProviderContracts.d.ts +1 -1
  193. package/dist/svelte/createVoiceProviderSimulationControls.d.ts +1 -1
  194. package/dist/svelte/createVoiceProviderStatus.d.ts +1 -1
  195. package/dist/svelte/createVoiceReplayTimeline.d.ts +1 -1
  196. package/dist/svelte/createVoiceRoutingStatus.d.ts +1 -1
  197. package/dist/svelte/createVoiceSessionObservability.d.ts +1 -1
  198. package/dist/svelte/createVoiceSessionSnapshot.d.ts +1 -1
  199. package/dist/svelte/createVoiceTraceTimeline.d.ts +1 -1
  200. package/dist/svelte/createVoiceTurnLatency.d.ts +2 -2
  201. package/dist/svelte/createVoiceTurnQuality.d.ts +1 -1
  202. package/dist/svelte/createVoiceWidget.d.ts +2 -2
  203. package/dist/svelte/createVoiceWorkflowStatus.d.ts +1 -1
  204. package/dist/svelte/index.js +1518 -1522
  205. package/dist/telephony/matrix.d.ts +3 -3
  206. package/dist/telephony/plivo.d.ts +2 -2
  207. package/dist/telephony/security.d.ts +3 -3
  208. package/dist/telephony/telnyx.d.ts +1 -1
  209. package/dist/telephony/twilio.d.ts +2 -2
  210. package/dist/testing/benchmark.d.ts +6 -6
  211. package/dist/testing/corrected.d.ts +4 -4
  212. package/dist/testing/duplex.d.ts +2 -2
  213. package/dist/testing/fixtures.d.ts +1 -1
  214. package/dist/testing/index.js +1382 -1388
  215. package/dist/testing/review.d.ts +4 -4
  216. package/dist/testing/sessionBenchmark.d.ts +10 -10
  217. package/dist/testing/telephony.d.ts +3 -3
  218. package/dist/testing/tts.d.ts +1 -1
  219. package/dist/vue/VoiceCostDashboard.d.ts +2 -2
  220. package/dist/vue/index.js +2110 -2112
  221. package/dist/vue/useVoiceController.d.ts +5 -5
  222. package/dist/vue/useVoiceDeliveryRuntime.d.ts +1 -1
  223. package/dist/vue/useVoiceStream.d.ts +5 -5
  224. package/package.json +28 -6
@@ -512,8 +512,8 @@ var scoreSpeakerTurns = (fixture, result) => {
512
512
  const available = scoredTurns.every((turn) => turn.speaker !== undefined);
513
513
  if (!available) {
514
514
  return {
515
- available: false,
516
515
  actualTurnCount: scoredTurns.length,
516
+ available: false,
517
517
  expectedTurnCount: expectedTurns.length,
518
518
  passes: false,
519
519
  patternMatchRate: 0,
@@ -531,8 +531,8 @@ var scoreSpeakerTurns = (fixture, result) => {
531
531
  }
532
532
  const patternMatchRate = roundMetric(matches / maxLength) ?? 0;
533
533
  return {
534
- available: true,
535
534
  actualTurnCount: scoredTurns.length,
535
+ available: true,
536
536
  expectedTurnCount: expectedTurns.length,
537
537
  passes: scoredTurns.length === expectedTurns.length && patternMatchRate === 1,
538
538
  patternMatchRate,
@@ -622,37 +622,33 @@ var toFixtureBenchmarkResult = (fixture, result, elapsedMs) => {
622
622
  title: fixture.title
623
623
  };
624
624
  };
625
- var summarizeSTTBenchmark = (adapterId, fixtures) => {
626
- const fixtureCount = fixtures.length;
627
- const passCount = fixtures.filter((fixture) => fixture.passes).length;
625
+ var compareSTTBenchmarks = (reports) => {
626
+ const entries = reports.map((report) => ({
627
+ adapterId: report.adapterId,
628
+ summary: report.summary
629
+ }));
630
+ const bestByMetric = (selectMetric, direction) => entries.reduce((best, entry) => {
631
+ if (!best) {
632
+ return entry;
633
+ }
634
+ const next = selectMetric(entry);
635
+ const current = selectMetric(best);
636
+ if (direction === "max" ? next > current : next < current) {
637
+ return entry;
638
+ }
639
+ return best;
640
+ }, undefined);
628
641
  return {
629
- adapterId,
630
- averageCharErrorRate: roundMetric(average(fixtures.map((fixture) => fixture.accuracy.charErrorRate))) ?? 0,
631
- averageElapsedMs: roundMetric(average(fixtures.map((fixture) => fixture.elapsedMs)), 2) ?? 0,
632
- averageEndOfTurnCount: roundMetric(average(fixtures.map((fixture) => fixture.endOfTurnCount)), 2) ?? 0,
633
- averageFinalCount: roundMetric(average(fixtures.map((fixture) => fixture.finalCount)), 2) ?? 0,
634
- averageSpeakerTurnMatchRate: roundMetric(average(fixtures.map((fixture) => fixture.speakerTurns?.patternMatchRate))),
635
- averageTermRecall: roundMetric(average(fixtures.map((fixture) => fixture.expectedTerms.recall))) ?? 0,
636
- averagePostSpeechTimeToEndOfTurnMs: roundMetric(average(fixtures.map((fixture) => fixture.postSpeechTimeToEndOfTurnMs)), 2),
637
- averagePostSpeechTimeToFirstFinalMs: roundMetric(average(fixtures.map((fixture) => fixture.postSpeechTimeToFirstFinalMs)), 2),
638
- averageTimeToEndOfTurnMs: roundMetric(average(fixtures.map((fixture) => fixture.timeToEndOfTurnMs)), 2),
639
- averageTimeToFirstFinalMs: roundMetric(average(fixtures.map((fixture) => fixture.timeToFirstFinalMs)), 2),
640
- averageTimeToFirstPartialMs: roundMetric(average(fixtures.map((fixture) => fixture.timeToFirstPartialMs)), 2),
641
- averageWordErrorRate: roundMetric(average(fixtures.map((fixture) => fixture.accuracy.wordErrorRate))) ?? 0,
642
- fixtureCount,
643
- fixturesWithErrors: fixtures.filter((fixture) => fixture.errorCount > 0).length,
644
- fixturesWithFragmentation: fixtures.filter((fixture) => fixture.fragmentationCount > 0).length,
645
- groupSummaries: calculateGroupSummary(fixtures),
646
- passCount,
647
- passRate: fixtureCount > 0 ? roundMetric(passCount / fixtureCount) ?? 0 : 0,
648
- totalErrorCount: fixtures.reduce((sum, fixture) => sum + fixture.errorCount, 0),
649
- wordAccuracyRate: fixtureCount > 0 ? roundMetric(1 - (average(fixtures.map((fixture) => fixture.accuracy.wordErrorRate)) ?? 0)) ?? 0 : 0
642
+ bestByPassRate: bestByMetric((entry) => entry.summary.passRate, "max"),
643
+ bestByTermRecall: bestByMetric((entry) => entry.summary.averageTermRecall, "max"),
644
+ bestByWordErrorRate: bestByMetric((entry) => entry.summary.averageWordErrorRate, "min"),
645
+ entries
650
646
  };
651
647
  };
652
648
  var evaluateSTTBenchmarkAcceptance = (report, thresholds = {}) => {
653
649
  const failures = [];
654
650
  const details = thresholds;
655
- const overallPassRate = details.overallPassRate;
651
+ const { overallPassRate } = details;
656
652
  if (overallPassRate !== undefined && report.summary.passRate < overallPassRate) {
657
653
  failures.push(`overall passRate ${(report.summary.passRate * 100).toFixed(2)}% below ${(overallPassRate * 100).toFixed(2)}%`);
658
654
  }
@@ -687,29 +683,6 @@ var evaluateSTTBenchmarkAcceptance = (report, thresholds = {}) => {
687
683
  score
688
684
  };
689
685
  };
690
- var compareSTTBenchmarks = (reports) => {
691
- const entries = reports.map((report) => ({
692
- adapterId: report.adapterId,
693
- summary: report.summary
694
- }));
695
- const bestByMetric = (selectMetric, direction) => entries.reduce((best, entry) => {
696
- if (!best) {
697
- return entry;
698
- }
699
- const next = selectMetric(entry);
700
- const current = selectMetric(best);
701
- if (direction === "max" ? next > current : next < current) {
702
- return entry;
703
- }
704
- return best;
705
- }, undefined);
706
- return {
707
- bestByPassRate: bestByMetric((entry) => entry.summary.passRate, "max"),
708
- bestByTermRecall: bestByMetric((entry) => entry.summary.averageTermRecall, "max"),
709
- bestByWordErrorRate: bestByMetric((entry) => entry.summary.averageWordErrorRate, "min"),
710
- entries
711
- };
712
- };
713
686
  var runSTTAdapterBenchmark = async ({
714
687
  adapter,
715
688
  adapterId,
@@ -732,6 +705,55 @@ var runSTTAdapterBenchmark = async ({
732
705
  summary: summarizeSTTBenchmark(adapterId, results)
733
706
  };
734
707
  };
708
+ var runSTTAdapterBenchmarkSeries = async ({
709
+ adapter,
710
+ adapterId,
711
+ fixtures,
712
+ options = {},
713
+ runs
714
+ }) => {
715
+ const reports = [];
716
+ const runCount = Math.max(1, Math.floor(runs));
717
+ for (let runIndex = 0;runIndex < runCount; runIndex += 1) {
718
+ reports.push(await runSTTAdapterBenchmark({
719
+ adapter,
720
+ adapterId,
721
+ fixtures,
722
+ options
723
+ }));
724
+ }
725
+ return summarizeSTTBenchmarkSeries({
726
+ adapterId,
727
+ reports
728
+ });
729
+ };
730
+ var summarizeSTTBenchmark = (adapterId, fixtures) => {
731
+ const fixtureCount = fixtures.length;
732
+ const passCount = fixtures.filter((fixture) => fixture.passes).length;
733
+ return {
734
+ adapterId,
735
+ averageCharErrorRate: roundMetric(average(fixtures.map((fixture) => fixture.accuracy.charErrorRate))) ?? 0,
736
+ averageElapsedMs: roundMetric(average(fixtures.map((fixture) => fixture.elapsedMs)), 2) ?? 0,
737
+ averageEndOfTurnCount: roundMetric(average(fixtures.map((fixture) => fixture.endOfTurnCount)), 2) ?? 0,
738
+ averageFinalCount: roundMetric(average(fixtures.map((fixture) => fixture.finalCount)), 2) ?? 0,
739
+ averageSpeakerTurnMatchRate: roundMetric(average(fixtures.map((fixture) => fixture.speakerTurns?.patternMatchRate))),
740
+ averageTermRecall: roundMetric(average(fixtures.map((fixture) => fixture.expectedTerms.recall))) ?? 0,
741
+ averagePostSpeechTimeToEndOfTurnMs: roundMetric(average(fixtures.map((fixture) => fixture.postSpeechTimeToEndOfTurnMs)), 2),
742
+ averagePostSpeechTimeToFirstFinalMs: roundMetric(average(fixtures.map((fixture) => fixture.postSpeechTimeToFirstFinalMs)), 2),
743
+ averageTimeToEndOfTurnMs: roundMetric(average(fixtures.map((fixture) => fixture.timeToEndOfTurnMs)), 2),
744
+ averageTimeToFirstFinalMs: roundMetric(average(fixtures.map((fixture) => fixture.timeToFirstFinalMs)), 2),
745
+ averageTimeToFirstPartialMs: roundMetric(average(fixtures.map((fixture) => fixture.timeToFirstPartialMs)), 2),
746
+ averageWordErrorRate: roundMetric(average(fixtures.map((fixture) => fixture.accuracy.wordErrorRate))) ?? 0,
747
+ fixtureCount,
748
+ fixturesWithErrors: fixtures.filter((fixture) => fixture.errorCount > 0).length,
749
+ fixturesWithFragmentation: fixtures.filter((fixture) => fixture.fragmentationCount > 0).length,
750
+ groupSummaries: calculateGroupSummary(fixtures),
751
+ passCount,
752
+ passRate: fixtureCount > 0 ? roundMetric(passCount / fixtureCount) ?? 0 : 0,
753
+ totalErrorCount: fixtures.reduce((sum, fixture) => sum + fixture.errorCount, 0),
754
+ wordAccuracyRate: fixtureCount > 0 ? roundMetric(1 - (average(fixtures.map((fixture) => fixture.accuracy.wordErrorRate)) ?? 0)) ?? 0 : 0
755
+ };
756
+ };
735
757
  var summarizeSTTBenchmarkSeries = (input) => {
736
758
  const fixtureMap = new Map;
737
759
  for (const report of input.reports) {
@@ -780,28 +802,6 @@ var summarizeSTTBenchmarkSeries = (input) => {
780
802
  }
781
803
  };
782
804
  };
783
- var runSTTAdapterBenchmarkSeries = async ({
784
- adapter,
785
- adapterId,
786
- fixtures,
787
- options = {},
788
- runs
789
- }) => {
790
- const reports = [];
791
- const runCount = Math.max(1, Math.floor(runs));
792
- for (let runIndex = 0;runIndex < runCount; runIndex += 1) {
793
- reports.push(await runSTTAdapterBenchmark({
794
- adapter,
795
- adapterId,
796
- fixtures,
797
- options
798
- }));
799
- }
800
- return summarizeSTTBenchmarkSeries({
801
- adapterId,
802
- reports
803
- });
804
- };
805
805
  // src/core/correction.ts
806
806
  var escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
807
807
  var buildAliasMatcher = (alias) => new RegExp(`(?<![\\p{L}\\p{N}'])${escapeRegExp(alias)}(?![\\p{L}\\p{N}'])`, "giu");
@@ -911,11 +911,9 @@ var findFuzzyAliasMatch = (text, alias, riskTier) => {
911
911
  return bestMatch;
912
912
  };
913
913
  var normalizeHintAliases = (hint) => (hint.aliases ?? []).map((alias) => alias.trim()).filter((alias) => alias.length > 0).sort((left, right) => right.length - left.length);
914
- var applyPhraseHintCorrections = (text, phraseHints) => {
915
- return applyRiskTieredPhraseHintCorrections(text, phraseHints, {
916
- riskTier: "risky"
917
- });
918
- };
914
+ var applyPhraseHintCorrections = (text, phraseHints) => applyRiskTieredPhraseHintCorrections(text, phraseHints, {
915
+ riskTier: "risky"
916
+ });
919
917
  var applyRiskTieredPhraseHintCorrections = (text, phraseHints, options = {}) => {
920
918
  const riskTier = options.riskTier ?? "safe";
921
919
  let corrected = text;
@@ -973,6 +971,25 @@ var isSafeAlias = (alias) => {
973
971
  const tokens = normalized.split(" ").filter((token) => token.length > 0);
974
972
  return tokens.length >= 2 || normalized.length >= 7;
975
973
  };
974
+ var createDomainLexicon = (terms) => {
975
+ const entries = [];
976
+ const seen = new Set;
977
+ for (const term of terms) {
978
+ const normalizedText = normalizeDomainTerm(term.text);
979
+ if (!normalizedText || seen.has(normalizedText)) {
980
+ continue;
981
+ }
982
+ entries.push({
983
+ aliases: dedupeAliases(term.aliases ?? []),
984
+ language: term.language,
985
+ metadata: term.metadata,
986
+ pronunciation: term.pronunciation,
987
+ text: term.text
988
+ });
989
+ seen.add(normalizedText);
990
+ }
991
+ return entries;
992
+ };
976
993
  var createDomainPhraseHints = (terms, options = {}) => {
977
994
  const riskTier = options.riskTier ?? "safe";
978
995
  const hints = [];
@@ -1002,25 +1019,6 @@ var createDomainPhraseHints = (terms, options = {}) => {
1002
1019
  }
1003
1020
  return hints;
1004
1021
  };
1005
- var createDomainLexicon = (terms) => {
1006
- const entries = [];
1007
- const seen = new Set;
1008
- for (const term of terms) {
1009
- const normalizedText = normalizeDomainTerm(term.text);
1010
- if (!normalizedText || seen.has(normalizedText)) {
1011
- continue;
1012
- }
1013
- entries.push({
1014
- aliases: dedupeAliases(term.aliases ?? []),
1015
- language: term.language,
1016
- metadata: term.metadata,
1017
- pronunciation: term.pronunciation,
1018
- text: term.text
1019
- });
1020
- seen.add(normalizedText);
1021
- }
1022
- return entries;
1023
- };
1024
1022
  var averageTranscriptConfidence = (transcripts) => {
1025
1023
  const confidences = transcripts.map((transcript) => transcript.confidence).filter((value) => typeof value === "number");
1026
1024
  return confidences.length > 0 ? confidences.reduce((sum, value) => sum + value, 0) / confidences.length : undefined;
@@ -1155,7 +1153,7 @@ var countDistinctScripts = (value) => {
1155
1153
  return Number(hasLatin) + Number(hasDevanagari);
1156
1154
  };
1157
1155
  var scoreCodeSwitchLexiconEntry = (entry) => {
1158
- const text = entry.text;
1156
+ const { text } = entry;
1159
1157
  const tokens = tokenizeCodeSwitchText(text);
1160
1158
  const normalized = normalizeBenchmarkText2(text);
1161
1159
  const hasAlias = (entry.aliases?.length ?? 0) > 0;
@@ -1192,7 +1190,7 @@ var pushUniqueLexiconEntryWithAliases = (target, seen, text, aliases, language)
1192
1190
  var hasFixtureTag = (fixture, targetTag) => ((fixturesTags) => fixturesTags.some((tag) => tag === targetTag))((fixture.tags ?? []).map((tag) => tag.trim().toLowerCase()));
1193
1191
  var appendParlamentParlaCodeSwitchLexicon = (target, seen, fixture) => {
1194
1192
  const expectedText = normalizeBenchmarkText2(fixture.expectedText ?? "");
1195
- const language = fixture.language;
1193
+ const { language } = fixture;
1196
1194
  if (expectedText.includes("espanya es paro y muerte")) {
1197
1195
  pushUniqueLexiconEntryWithAliases(target, seen, "espanya es paro y muerte", [
1198
1196
  "espanya espanya i muertes",
@@ -1315,66 +1313,6 @@ var buildFixtureDomainTerms = (fixture) => {
1315
1313
  }
1316
1314
  return terms;
1317
1315
  };
1318
- var buildCodeSwitchBenchmarkLexicon = (fixture) => {
1319
- const expectedTermEntries = [];
1320
- const candidateEntries = [];
1321
- const seen = new Set;
1322
- const language = fixture.language;
1323
- const tokens = tokenizeCodeSwitchText(fixture.expectedText ?? "");
1324
- for (const expectedTerm of fixture.expectedTerms ?? []) {
1325
- pushUniqueLexiconEntry(expectedTermEntries, seen, expectedTerm, language);
1326
- }
1327
- for (const token of tokens) {
1328
- if (!token.marked || token.text.length < 4) {
1329
- continue;
1330
- }
1331
- pushUniqueLexiconEntry(candidateEntries, seen, token.text, language);
1332
- }
1333
- for (let startIndex = 0;startIndex < tokens.length; startIndex += 1) {
1334
- for (let windowSize = 2;windowSize <= 3; windowSize += 1) {
1335
- const window2 = tokens.slice(startIndex, startIndex + windowSize);
1336
- if (window2.length !== windowSize || !isUsefulCodeSwitchWindow(window2)) {
1337
- continue;
1338
- }
1339
- pushUniqueLexiconEntry(candidateEntries, seen, window2.map((token) => token.text).join(" "), language);
1340
- }
1341
- }
1342
- if (hasFixtureTag(fixture, "parlament_parla")) {
1343
- appendParlamentParlaCodeSwitchLexicon(candidateEntries, seen, fixture);
1344
- }
1345
- return [
1346
- ...expectedTermEntries,
1347
- ...candidateEntries.sort((left, right) => scoreCodeSwitchLexiconEntry(right) - scoreCodeSwitchLexiconEntry(left))
1348
- ].slice(0, MAX_CODE_SWITCH_LEXICON_ENTRIES);
1349
- };
1350
- var buildCodeSwitchBenchmarkPhraseHints = (fixture) => buildCodeSwitchBenchmarkLexicon(fixture).map((entry) => ({
1351
- aliases: entry.aliases,
1352
- metadata: entry.metadata,
1353
- text: entry.text
1354
- }));
1355
- var createCodeSwitchBenchmarkCorrectionHandler = () => createLexiconCorrectionHandler({
1356
- provider: "codeswitch-lexicon-corrector",
1357
- reason: "codeswitch-lexicon-correction"
1358
- });
1359
- var createBenchmarkCorrectionHandler = (profile) => createPhraseHintCorrectionHandler({
1360
- provider: profile === "generic" ? "generic-hint-corrector" : "benchmark-seeded-corrector",
1361
- reason: profile === "generic" ? "generic-domain-correction" : "benchmark-seeded-correction"
1362
- });
1363
- var scoreCorrectedExpectedTerms = (actualText, expectedTerms) => {
1364
- const normalizedActual = normalizeBenchmarkText2(actualText);
1365
- const normalizedExpectedTerms = (expectedTerms ?? []).map((entry) => normalizeBenchmarkText2(entry));
1366
- const matchedTerms = normalizedExpectedTerms.filter((term) => term.length > 0 && normalizedActual.includes(term));
1367
- const missingTerms = normalizedExpectedTerms.filter((term) => term.length > 0 && !matchedTerms.includes(term));
1368
- const denominator = normalizedExpectedTerms.length;
1369
- const recall = denominator > 0 ? matchedTerms.length / denominator : 1;
1370
- return {
1371
- allMatched: missingTerms.length === 0,
1372
- expectedTerms: normalizedExpectedTerms,
1373
- matchedTerms,
1374
- missingTerms,
1375
- recall
1376
- };
1377
- };
1378
1316
  var applyCorrectedBenchmarkReport = (report, fixtures, profile = "benchmark-seeded") => {
1379
1317
  const fixtureMap = new Map(fixtures.map((fixture) => [fixture.id, fixture]));
1380
1318
  const correctedFixtures = report.fixtures.map((result) => {
@@ -1473,20 +1411,80 @@ var applyLexiconCorrectedBenchmarkReport = (report, fixtures, buildLexicon) => {
1473
1411
  summary: summarizeSTTBenchmark(report.adapterId, correctedFixtures)
1474
1412
  };
1475
1413
  };
1476
- var sliceSTTHoldoutReport = (report, fixtureIds) => {
1477
- const fixtureIdSet = new Set(fixtureIds);
1478
- const fixtures = report.fixtures.filter((fixture) => fixtureIdSet.has(fixture.fixtureId));
1479
- return {
1480
- adapterId: report.adapterId,
1481
- fixtures,
1482
- generatedAt: report.generatedAt,
1483
- summary: summarizeSTTBenchmark(report.adapterId, fixtures)
1484
- };
1485
- };
1486
- var buildCorrectionBenchmarkAudit = (rawReport, fixtures) => {
1487
- const generic = applyCorrectedBenchmarkReport(rawReport, fixtures, "generic");
1488
- const experimental = applyExperimentalBenchmarkReport(rawReport, fixtures);
1489
- const benchmarkSeeded = applyCorrectedBenchmarkReport(rawReport, fixtures, "benchmark-seeded");
1414
+ var buildCodeSwitchBenchmarkLexicon = (fixture) => {
1415
+ const expectedTermEntries = [];
1416
+ const candidateEntries = [];
1417
+ const seen = new Set;
1418
+ const { language } = fixture;
1419
+ const tokens = tokenizeCodeSwitchText(fixture.expectedText ?? "");
1420
+ for (const expectedTerm of fixture.expectedTerms ?? []) {
1421
+ pushUniqueLexiconEntry(expectedTermEntries, seen, expectedTerm, language);
1422
+ }
1423
+ for (const token of tokens) {
1424
+ if (!token.marked || token.text.length < 4) {
1425
+ continue;
1426
+ }
1427
+ pushUniqueLexiconEntry(candidateEntries, seen, token.text, language);
1428
+ }
1429
+ for (let startIndex = 0;startIndex < tokens.length; startIndex += 1) {
1430
+ for (let windowSize = 2;windowSize <= 3; windowSize += 1) {
1431
+ const window2 = tokens.slice(startIndex, startIndex + windowSize);
1432
+ if (window2.length !== windowSize || !isUsefulCodeSwitchWindow(window2)) {
1433
+ continue;
1434
+ }
1435
+ pushUniqueLexiconEntry(candidateEntries, seen, window2.map((token) => token.text).join(" "), language);
1436
+ }
1437
+ }
1438
+ if (hasFixtureTag(fixture, "parlament_parla")) {
1439
+ appendParlamentParlaCodeSwitchLexicon(candidateEntries, seen, fixture);
1440
+ }
1441
+ return [
1442
+ ...expectedTermEntries,
1443
+ ...candidateEntries.sort((left, right) => scoreCodeSwitchLexiconEntry(right) - scoreCodeSwitchLexiconEntry(left))
1444
+ ].slice(0, MAX_CODE_SWITCH_LEXICON_ENTRIES);
1445
+ };
1446
+ var buildCodeSwitchBenchmarkPhraseHints = (fixture) => buildCodeSwitchBenchmarkLexicon(fixture).map((entry) => ({
1447
+ aliases: entry.aliases,
1448
+ metadata: entry.metadata,
1449
+ text: entry.text
1450
+ }));
1451
+ var createBenchmarkCorrectionHandler = (profile) => createPhraseHintCorrectionHandler({
1452
+ provider: profile === "generic" ? "generic-hint-corrector" : "benchmark-seeded-corrector",
1453
+ reason: profile === "generic" ? "generic-domain-correction" : "benchmark-seeded-correction"
1454
+ });
1455
+ var createCodeSwitchBenchmarkCorrectionHandler = () => createLexiconCorrectionHandler({
1456
+ provider: "codeswitch-lexicon-corrector",
1457
+ reason: "codeswitch-lexicon-correction"
1458
+ });
1459
+ var scoreCorrectedExpectedTerms = (actualText, expectedTerms) => {
1460
+ const normalizedActual = normalizeBenchmarkText2(actualText);
1461
+ const normalizedExpectedTerms = (expectedTerms ?? []).map((entry) => normalizeBenchmarkText2(entry));
1462
+ const matchedTerms = normalizedExpectedTerms.filter((term) => term.length > 0 && normalizedActual.includes(term));
1463
+ const missingTerms = normalizedExpectedTerms.filter((term) => term.length > 0 && !matchedTerms.includes(term));
1464
+ const denominator = normalizedExpectedTerms.length;
1465
+ const recall = denominator > 0 ? matchedTerms.length / denominator : 1;
1466
+ return {
1467
+ allMatched: missingTerms.length === 0,
1468
+ expectedTerms: normalizedExpectedTerms,
1469
+ matchedTerms,
1470
+ missingTerms,
1471
+ recall
1472
+ };
1473
+ };
1474
+ var sliceSTTHoldoutReport = (report, fixtureIds) => {
1475
+ const fixtureIdSet = new Set(fixtureIds);
1476
+ const fixtures = report.fixtures.filter((fixture) => fixtureIdSet.has(fixture.fixtureId));
1477
+ return {
1478
+ adapterId: report.adapterId,
1479
+ fixtures,
1480
+ generatedAt: report.generatedAt,
1481
+ summary: summarizeSTTBenchmark(report.adapterId, fixtures)
1482
+ };
1483
+ };
1484
+ var buildCorrectionBenchmarkAudit = (rawReport, fixtures) => {
1485
+ const generic = applyCorrectedBenchmarkReport(rawReport, fixtures, "generic");
1486
+ const experimental = applyExperimentalBenchmarkReport(rawReport, fixtures);
1487
+ const benchmarkSeeded = applyCorrectedBenchmarkReport(rawReport, fixtures, "benchmark-seeded");
1490
1488
  const holdoutFixtureIds = rawReport.fixtures.map((fixture) => fixture.fixtureId).filter(isCorrectionHoldoutFixtureId);
1491
1489
  const lexicalHoldoutFixtureIds = fixtures.filter((fixture) => !usesBenchmarkSeedVocabulary(fixture)).map((fixture) => fixture.id).filter((fixtureId) => rawReport.fixtures.some((result) => result.fixtureId === fixtureId));
1492
1490
  return {
@@ -1598,7 +1596,7 @@ var getAudioContextCtor = () => {
1598
1596
  return window.AudioContext ?? window.webkitAudioContext;
1599
1597
  };
1600
1598
  var decodePCM16LEChunk = (audioContext, chunk) => {
1601
- const format = chunk.format;
1599
+ const { format } = chunk;
1602
1600
  if (format.container !== "raw" || format.encoding !== "pcm_s16le") {
1603
1601
  throw new Error(`Unsupported assistant audio format: ${format.container}/${format.encoding}`);
1604
1602
  }
@@ -1799,6 +1797,9 @@ var createVoiceAudioPlayer = (source, options = {}) => {
1799
1797
  }
1800
1798
  });
1801
1799
  const player = {
1800
+ get activeSourceCount() {
1801
+ return state.activeSourceCount;
1802
+ },
1802
1803
  close: async () => {
1803
1804
  unsubscribeSource();
1804
1805
  stopQueuedPlayback({ forceClear: true });
@@ -1820,19 +1821,10 @@ var createVoiceAudioPlayer = (source, options = {}) => {
1820
1821
  isPlaying: false
1821
1822
  });
1822
1823
  },
1823
- get activeSourceCount() {
1824
- return state.activeSourceCount;
1825
- },
1826
1824
  get error() {
1827
1825
  return state.error;
1828
1826
  },
1829
1827
  getSnapshot: () => state,
1830
- get isActive() {
1831
- return state.isActive;
1832
- },
1833
- get isPlaying() {
1834
- return state.isPlaying;
1835
- },
1836
1828
  interrupt: async () => {
1837
1829
  const startedAt = Date.now();
1838
1830
  const context = await ensureAudioContext();
@@ -1864,6 +1856,12 @@ var createVoiceAudioPlayer = (source, options = {}) => {
1864
1856
  stopQueuedPlayback();
1865
1857
  await interruptPromise;
1866
1858
  },
1859
+ get isActive() {
1860
+ return state.isActive;
1861
+ },
1862
+ get isPlaying() {
1863
+ return state.isPlaying;
1864
+ },
1867
1865
  get lastInterruptLatencyMs() {
1868
1866
  return state.lastInterruptLatencyMs;
1869
1867
  },
@@ -2239,14 +2237,14 @@ var createVoiceBrowserMediaReporter = (options) => {
2239
2237
  return {
2240
2238
  close: stop,
2241
2239
  reportOnce,
2240
+ stop,
2242
2241
  start: () => {
2243
2242
  if (interval) {
2244
2243
  return;
2245
2244
  }
2246
2245
  run();
2247
2246
  interval = setInterval(run, options.intervalMs ?? DEFAULT_BROWSER_MEDIA_INTERVAL_MS);
2248
- },
2249
- stop
2247
+ }
2250
2248
  };
2251
2249
  };
2252
2250
 
@@ -2264,14 +2262,14 @@ var NOOP_CONNECTION = {
2264
2262
  callControl: noop,
2265
2263
  close: noop,
2266
2264
  endTurn: noop,
2267
- getReadyState: () => WS_CLOSED,
2268
- getScenarioId: () => "",
2269
- getSessionId: () => "",
2270
2265
  send: noop,
2271
2266
  sendAudio: noop,
2272
2267
  simulateDisconnect: noop,
2273
- start: () => {},
2274
- subscribe: noopUnsubscribe
2268
+ subscribe: noopUnsubscribe,
2269
+ getReadyState: () => WS_CLOSED,
2270
+ getScenarioId: () => "",
2271
+ getSessionId: () => "",
2272
+ start: () => {}
2275
2273
  };
2276
2274
  var createSessionId = () => crypto.randomUUID();
2277
2275
  var buildWsUrl = (path, sessionId, scenarioId) => {
@@ -2468,9 +2466,9 @@ var createVoiceConnection = (path, options = {}) => {
2468
2466
  state.scenarioId = input.scenarioId;
2469
2467
  }
2470
2468
  send({
2471
- type: "start",
2469
+ scenarioId: state.scenarioId ?? undefined,
2472
2470
  sessionId: state.sessionId,
2473
- scenarioId: state.scenarioId ?? undefined
2471
+ type: "start"
2474
2472
  });
2475
2473
  };
2476
2474
  const sendAudio = (audio) => {
@@ -2510,14 +2508,14 @@ var createVoiceConnection = (path, options = {}) => {
2510
2508
  callControl,
2511
2509
  close,
2512
2510
  endTurn,
2513
- getReadyState: () => state.ws?.readyState ?? WS_CLOSED,
2514
- getScenarioId: () => state.scenarioId ?? "",
2515
- getSessionId: () => state.sessionId,
2516
2511
  send,
2517
2512
  sendAudio,
2518
2513
  simulateDisconnect,
2519
2514
  start,
2520
- subscribe
2515
+ subscribe,
2516
+ getReadyState: () => state.ws?.readyState ?? WS_CLOSED,
2517
+ getScenarioId: () => state.scenarioId ?? "",
2518
+ getSessionId: () => state.sessionId
2521
2519
  };
2522
2520
  };
2523
2521
 
@@ -2533,11 +2531,11 @@ var createInitialState2 = () => ({
2533
2531
  call: null,
2534
2532
  error: null,
2535
2533
  isConnected: false,
2536
- sessionMetadata: null,
2537
- scenarioId: null,
2538
2534
  partial: "",
2539
2535
  reconnect: createInitialReconnectState(),
2536
+ scenarioId: null,
2540
2537
  sessionId: null,
2538
+ sessionMetadata: null,
2541
2539
  status: "idle",
2542
2540
  turns: []
2543
2541
  });
@@ -2740,6 +2738,16 @@ var createVoiceStream = (path, options = {}) => {
2740
2738
  }
2741
2739
  });
2742
2740
  return {
2741
+ start,
2742
+ get assistantAudio() {
2743
+ return store.getSnapshot().assistantAudio;
2744
+ },
2745
+ get assistantTexts() {
2746
+ return store.getSnapshot().assistantTexts;
2747
+ },
2748
+ get call() {
2749
+ return store.getSnapshot().call;
2750
+ },
2743
2751
  callControl(message) {
2744
2752
  connection.callControl(message);
2745
2753
  },
@@ -2765,48 +2773,38 @@ var createVoiceStream = (path, options = {}) => {
2765
2773
  get isConnected() {
2766
2774
  return store.getSnapshot().isConnected;
2767
2775
  },
2768
- get scenarioId() {
2769
- return store.getSnapshot().scenarioId;
2770
- },
2771
- get sessionMetadata() {
2772
- return store.getSnapshot().sessionMetadata;
2773
- },
2774
- start,
2775
2776
  get partial() {
2776
2777
  return store.getSnapshot().partial;
2777
2778
  },
2778
2779
  get reconnect() {
2779
2780
  return store.getSnapshot().reconnect;
2780
2781
  },
2781
- get sessionId() {
2782
- return connection.getSessionId();
2783
- },
2784
- get status() {
2785
- return store.getSnapshot().status;
2786
- },
2787
- get turns() {
2788
- return store.getSnapshot().turns;
2789
- },
2790
- get assistantTexts() {
2791
- return store.getSnapshot().assistantTexts;
2792
- },
2793
- get assistantAudio() {
2794
- return store.getSnapshot().assistantAudio;
2795
- },
2796
- get call() {
2797
- return store.getSnapshot().call;
2782
+ get scenarioId() {
2783
+ return store.getSnapshot().scenarioId;
2798
2784
  },
2799
2785
  sendAudio(audio) {
2800
2786
  connection.sendAudio(audio);
2801
2787
  },
2788
+ get sessionId() {
2789
+ return connection.getSessionId();
2790
+ },
2791
+ get sessionMetadata() {
2792
+ return store.getSnapshot().sessionMetadata;
2793
+ },
2802
2794
  simulateDisconnect() {
2803
2795
  connection.simulateDisconnect();
2804
2796
  },
2797
+ get status() {
2798
+ return store.getSnapshot().status;
2799
+ },
2805
2800
  subscribe(subscriber) {
2806
2801
  subscribers.add(subscriber);
2807
2802
  return () => {
2808
2803
  subscribers.delete(subscriber);
2809
2804
  };
2805
+ },
2806
+ get turns() {
2807
+ return store.getSnapshot().turns;
2810
2808
  }
2811
2809
  };
2812
2810
  };
@@ -2833,18 +2831,6 @@ var computeRms = (samples) => {
2833
2831
  }
2834
2832
  return Math.sqrt(sumSquares / samples.length);
2835
2833
  };
2836
- var resolveAudioConditioningConfig = (config) => {
2837
- if (!config || config.enabled === false) {
2838
- return;
2839
- }
2840
- return {
2841
- enabled: true,
2842
- maxGain: config.maxGain ?? DEFAULT_MAX_GAIN,
2843
- noiseGateAttenuation: config.noiseGateAttenuation ?? DEFAULT_NOISE_GATE_ATTENUATION,
2844
- noiseGateThreshold: config.noiseGateThreshold ?? DEFAULT_NOISE_GATE_THRESHOLD,
2845
- targetLevel: config.targetLevel ?? DEFAULT_TARGET_LEVEL
2846
- };
2847
- };
2848
2834
  var conditionAudioChunk = (audio, config) => {
2849
2835
  if (!config) {
2850
2836
  return audio;
@@ -2865,6 +2851,18 @@ var conditionAudioChunk = (audio, config) => {
2865
2851
  }
2866
2852
  return new Uint8Array(output.buffer);
2867
2853
  };
2854
+ var resolveAudioConditioningConfig = (config) => {
2855
+ if (!config || config.enabled === false) {
2856
+ return;
2857
+ }
2858
+ return {
2859
+ enabled: true,
2860
+ maxGain: config.maxGain ?? DEFAULT_MAX_GAIN,
2861
+ noiseGateAttenuation: config.noiseGateAttenuation ?? DEFAULT_NOISE_GATE_ATTENUATION,
2862
+ noiseGateThreshold: config.noiseGateThreshold ?? DEFAULT_NOISE_GATE_THRESHOLD,
2863
+ targetLevel: config.targetLevel ?? DEFAULT_TARGET_LEVEL
2864
+ };
2865
+ };
2868
2866
 
2869
2867
  // src/core/turnProfiles.ts
2870
2868
  var TURN_PROFILE_DEFAULTS = {
@@ -2888,12 +2886,12 @@ var TURN_PROFILE_DEFAULTS = {
2888
2886
  }
2889
2887
  };
2890
2888
  var QUALITY_PROFILE_DEFAULTS = {
2891
- general: {},
2892
2889
  "accent-heavy": {
2893
2890
  silenceMs: 1200,
2894
2891
  speechThreshold: 0.01,
2895
2892
  transcriptStabilityMs: 1200
2896
2893
  },
2894
+ general: {},
2897
2895
  "noisy-room": {
2898
2896
  silenceMs: 2000,
2899
2897
  speechThreshold: 0.02,
@@ -2942,8 +2940,8 @@ var PRESET_INPUTS = {
2942
2940
  },
2943
2941
  sttLifecycle: "continuous",
2944
2942
  turnDetection: {
2945
- qualityProfile: "short-command",
2946
- profile: "balanced"
2943
+ profile: "balanced",
2944
+ qualityProfile: "short-command"
2947
2945
  }
2948
2946
  },
2949
2947
  default: {
@@ -2958,8 +2956,8 @@ var PRESET_INPUTS = {
2958
2956
  },
2959
2957
  sttLifecycle: "continuous",
2960
2958
  turnDetection: {
2961
- qualityProfile: "general",
2962
- profile: "fast"
2959
+ profile: "fast",
2960
+ qualityProfile: "general"
2963
2961
  }
2964
2962
  },
2965
2963
  dictation: {
@@ -2981,8 +2979,8 @@ var PRESET_INPUTS = {
2981
2979
  },
2982
2980
  sttLifecycle: "continuous",
2983
2981
  turnDetection: {
2984
- qualityProfile: "accent-heavy",
2985
- profile: "long-form"
2982
+ profile: "long-form",
2983
+ qualityProfile: "accent-heavy"
2986
2984
  }
2987
2985
  },
2988
2986
  "guided-intake": {
@@ -3004,8 +3002,8 @@ var PRESET_INPUTS = {
3004
3002
  },
3005
3003
  sttLifecycle: "turn-scoped",
3006
3004
  turnDetection: {
3007
- qualityProfile: "accent-heavy",
3008
- profile: "long-form"
3005
+ profile: "long-form",
3006
+ qualityProfile: "accent-heavy"
3009
3007
  }
3010
3008
  },
3011
3009
  "noisy-room": {
@@ -3027,8 +3025,8 @@ var PRESET_INPUTS = {
3027
3025
  },
3028
3026
  sttLifecycle: "continuous",
3029
3027
  turnDetection: {
3030
- qualityProfile: "noisy-room",
3031
3028
  profile: "long-form",
3029
+ qualityProfile: "noisy-room",
3032
3030
  silenceMs: 2100,
3033
3031
  speechThreshold: 0.02,
3034
3032
  transcriptStabilityMs: 1650
@@ -3053,8 +3051,8 @@ var PRESET_INPUTS = {
3053
3051
  },
3054
3052
  sttLifecycle: "continuous",
3055
3053
  turnDetection: {
3056
- qualityProfile: "noisy-room",
3057
3054
  profile: "long-form",
3055
+ qualityProfile: "noisy-room",
3058
3056
  silenceMs: 660,
3059
3057
  speechThreshold: 0.012,
3060
3058
  transcriptStabilityMs: 300
@@ -3079,8 +3077,8 @@ var PRESET_INPUTS = {
3079
3077
  },
3080
3078
  sttLifecycle: "continuous",
3081
3079
  turnDetection: {
3082
- qualityProfile: "noisy-room",
3083
3080
  profile: "long-form",
3081
+ qualityProfile: "noisy-room",
3084
3082
  silenceMs: 620,
3085
3083
  speechThreshold: 0.012,
3086
3084
  transcriptStabilityMs: 280
@@ -3105,8 +3103,8 @@ var PRESET_INPUTS = {
3105
3103
  },
3106
3104
  sttLifecycle: "continuous",
3107
3105
  turnDetection: {
3108
- qualityProfile: "noisy-room",
3109
- profile: "long-form"
3106
+ profile: "long-form",
3107
+ qualityProfile: "noisy-room"
3110
3108
  }
3111
3109
  }
3112
3110
  };
@@ -3246,11 +3244,22 @@ var createVoiceController = (path, options = {}) => {
3246
3244
  stream.close();
3247
3245
  };
3248
3246
  return {
3247
+ close,
3248
+ startRecording,
3249
+ stopRecording,
3250
+ get assistantAudio() {
3251
+ return state.assistantAudio;
3252
+ },
3253
+ get assistantTexts() {
3254
+ return state.assistantTexts;
3255
+ },
3249
3256
  bindHTMX(bindingOptions) {
3250
3257
  return bindVoiceHTMX(stream, bindingOptions);
3251
3258
  },
3259
+ get call() {
3260
+ return state.call;
3261
+ },
3252
3262
  callControl: (message) => stream.callControl(message),
3253
- close,
3254
3263
  endTurn: () => stream.endTurn(),
3255
3264
  get error() {
3256
3265
  return state.error;
@@ -3266,28 +3275,26 @@ var createVoiceController = (path, options = {}) => {
3266
3275
  get partial() {
3267
3276
  return state.partial;
3268
3277
  },
3278
+ get reconnect() {
3279
+ return state.reconnect;
3280
+ },
3269
3281
  get recordingError() {
3270
3282
  return state.recordingError;
3271
3283
  },
3272
- get reconnect() {
3273
- return state.reconnect;
3284
+ get scenarioId() {
3285
+ return state.scenarioId;
3274
3286
  },
3275
3287
  sendAudio: (audio) => stream.sendAudio(audio),
3276
- simulateDisconnect: () => stream.simulateDisconnect(),
3277
3288
  get sessionId() {
3278
3289
  return state.sessionId;
3279
3290
  },
3280
3291
  get sessionMetadata() {
3281
3292
  return state.sessionMetadata;
3282
3293
  },
3283
- get scenarioId() {
3284
- return state.scenarioId;
3285
- },
3286
- startRecording,
3294
+ simulateDisconnect: () => stream.simulateDisconnect(),
3287
3295
  get status() {
3288
3296
  return state.status;
3289
3297
  },
3290
- stopRecording,
3291
3298
  subscribe: (subscriber) => {
3292
3299
  subscribers.add(subscriber);
3293
3300
  return () => {
@@ -3303,15 +3310,6 @@ var createVoiceController = (path, options = {}) => {
3303
3310
  },
3304
3311
  get turns() {
3305
3312
  return state.turns;
3306
- },
3307
- get assistantTexts() {
3308
- return state.assistantTexts;
3309
- },
3310
- get assistantAudio() {
3311
- return state.assistantAudio;
3312
- },
3313
- get call() {
3314
- return state.call;
3315
3313
  }
3316
3314
  };
3317
3315
  };
@@ -3438,9 +3436,6 @@ var createFakeController = (scenario) => {
3438
3436
  const subscribers = new Set;
3439
3437
  const sentAudio = [];
3440
3438
  return {
3441
- get partial() {
3442
- return partial;
3443
- },
3444
3439
  emitPartial(next) {
3445
3440
  partial = next;
3446
3441
  for (const subscriber of subscribers) {
@@ -3450,6 +3445,9 @@ var createFakeController = (scenario) => {
3450
3445
  getSentAudio() {
3451
3446
  return sentAudio;
3452
3447
  },
3448
+ get partial() {
3449
+ return partial;
3450
+ },
3453
3451
  sendAudio(audio) {
3454
3452
  sentAudio.push(audio);
3455
3453
  },
@@ -3466,6 +3464,13 @@ var createFakePlayer = (delayMs) => {
3466
3464
  let isPlaying = true;
3467
3465
  let lastInterruptLatencyMs;
3468
3466
  return {
3467
+ async interrupt() {
3468
+ const startedAt = Date.now();
3469
+ await Bun.sleep(delayMs);
3470
+ interruptCount += 1;
3471
+ isPlaying = false;
3472
+ lastInterruptLatencyMs = Date.now() - startedAt;
3473
+ },
3469
3474
  get interruptCount() {
3470
3475
  return interruptCount;
3471
3476
  },
@@ -3474,26 +3479,30 @@ var createFakePlayer = (delayMs) => {
3474
3479
  },
3475
3480
  get lastInterruptLatencyMs() {
3476
3481
  return lastInterruptLatencyMs;
3477
- },
3478
- async interrupt() {
3479
- const startedAt = Date.now();
3480
- await Bun.sleep(delayMs);
3481
- interruptCount += 1;
3482
- isPlaying = false;
3483
- lastInterruptLatencyMs = Date.now() - startedAt;
3484
3482
  }
3485
3483
  };
3486
3484
  };
3487
3485
  var getDefaultVoiceDuplexBenchmarkScenarios = () => DEFAULT_SCENARIOS.map((scenario) => ({ ...scenario }));
3488
- var runVoiceDuplexBenchmarkScenario = async (scenario, options = {}) => {
3489
- const controller = createFakeController(scenario);
3490
- const player = createFakePlayer(scenario.interruptDelayMs ?? options.defaultInterruptDelayMs ?? 1);
3491
- const binding = bindVoiceBargeIn(controller, player, options.bargeIn);
3492
- const startedAt = Date.now();
3493
- try {
3494
- switch (scenario.mode) {
3495
- case "audio-send":
3496
- binding.sendAudio(new Uint8Array([1, 2, 3]));
3486
+ var runVoiceDuplexBenchmark = async (scenarios = getDefaultVoiceDuplexBenchmarkScenarios(), options = {}) => {
3487
+ const fixtures = [];
3488
+ for (const scenario of scenarios) {
3489
+ fixtures.push(await runVoiceDuplexBenchmarkScenario(scenario, options));
3490
+ }
3491
+ return {
3492
+ fixtures,
3493
+ generatedAt: Date.now(),
3494
+ summary: summarizeVoiceDuplexBenchmark(fixtures)
3495
+ };
3496
+ };
3497
+ var runVoiceDuplexBenchmarkScenario = async (scenario, options = {}) => {
3498
+ const controller = createFakeController(scenario);
3499
+ const player = createFakePlayer(scenario.interruptDelayMs ?? options.defaultInterruptDelayMs ?? 1);
3500
+ const binding = bindVoiceBargeIn(controller, player, options.bargeIn);
3501
+ const startedAt = Date.now();
3502
+ try {
3503
+ switch (scenario.mode) {
3504
+ case "audio-send":
3505
+ binding.sendAudio(new Uint8Array([1, 2, 3]));
3497
3506
  break;
3498
3507
  case "input-level":
3499
3508
  binding.handleLevel(scenario.level ?? 0.2);
@@ -3529,17 +3538,6 @@ var summarizeVoiceDuplexBenchmark = (fixtures) => {
3529
3538
  scenarioCount
3530
3539
  };
3531
3540
  };
3532
- var runVoiceDuplexBenchmark = async (scenarios = getDefaultVoiceDuplexBenchmarkScenarios(), options = {}) => {
3533
- const fixtures = [];
3534
- for (const scenario of scenarios) {
3535
- fixtures.push(await runVoiceDuplexBenchmarkScenario(scenario, options));
3536
- }
3537
- return {
3538
- fixtures,
3539
- generatedAt: Date.now(),
3540
- summary: summarizeVoiceDuplexBenchmark(fixtures)
3541
- };
3542
- };
3543
3541
  // src/testing/fixtures.ts
3544
3542
  import { resolve } from "path";
3545
3543
  var JARGON_FIXTURE_IDS = [
@@ -3711,6 +3709,12 @@ var requireFixture = (fixtures, id) => {
3711
3709
  }
3712
3710
  return fixture;
3713
3711
  };
3712
+ var createJargonVoiceTestFixtures = (fixtures) => JARGON_FIXTURE_IDS.map((id) => requireFixture(fixtures, id)).filter((fixture) => (fixture.expectedTerms?.length ?? 0) > 0).map((fixture) => ({
3713
+ ...fixture,
3714
+ id: `${fixture.id}-jargon`,
3715
+ tags: Array.from(new Set([...fixture.tags ?? [], "domain-heavy", "jargon"])),
3716
+ title: `${fixture.title} (jargon)`
3717
+ }));
3714
3718
  var createMultiSpeakerVoiceTestFixtures = (fixtures, options = {}) => {
3715
3719
  const silenceMs = options.silenceMs ?? DEFAULT_MULTI_SPEAKER_SILENCE_MS;
3716
3720
  const speakerA = requireFixture(fixtures, "quietly-alone-clean");
@@ -3776,12 +3780,6 @@ var createMultiSpeakerVoiceTestFixtures = (fixtures, options = {}) => {
3776
3780
  }
3777
3781
  ];
3778
3782
  };
3779
- var createJargonVoiceTestFixtures = (fixtures) => JARGON_FIXTURE_IDS.map((id) => requireFixture(fixtures, id)).filter((fixture) => (fixture.expectedTerms?.length ?? 0) > 0).map((fixture) => ({
3780
- ...fixture,
3781
- id: `${fixture.id}-jargon`,
3782
- tags: Array.from(new Set([...fixture.tags ?? [], "domain-heavy", "jargon"])),
3783
- title: `${fixture.title} (jargon)`
3784
- }));
3785
3783
  var loadVoiceTestFixtures = async (fixtureDirectory) => {
3786
3784
  const fixtureDirectories = await resolveVoiceFixtureDirectories(fixtureDirectory);
3787
3785
  const fixtures = [];
@@ -4019,6 +4017,7 @@ var createVoiceProviderOrchestrationProfile = (options) => {
4019
4017
  return {
4020
4018
  defaultSurface,
4021
4019
  id: options.id,
4020
+ surfaces: options.surfaces,
4022
4021
  resolve: (surface = defaultSurface) => {
4023
4022
  const config = options.surfaces[surface];
4024
4023
  if (!config) {
@@ -4034,8 +4033,7 @@ var createVoiceProviderOrchestrationProfile = (options) => {
4034
4033
  providerProfiles: config.providerProfiles,
4035
4034
  timeoutMs: config.timeoutMs
4036
4035
  };
4037
- },
4038
- surfaces: options.surfaces
4036
+ }
4039
4037
  };
4040
4038
  };
4041
4039
  var OUTPUT_SCHEMA = {
@@ -4249,7 +4247,7 @@ var createVoiceProviderRouter = (options) => {
4249
4247
  if (!healthOptions) {
4250
4248
  return;
4251
4249
  }
4252
- const suppressedUntil = getHealth(provider).suppressedUntil;
4250
+ const { suppressedUntil } = getHealth(provider);
4253
4251
  return typeof suppressedUntil === "number" ? Math.max(0, suppressedUntil - now()) : undefined;
4254
4252
  };
4255
4253
  const isSuppressed = (provider) => {
@@ -4775,11 +4773,11 @@ var extractGeminiCandidateParts = (response) => {
4775
4773
  if (!first || typeof first !== "object") {
4776
4774
  return [];
4777
4775
  }
4778
- const content = first.content;
4776
+ const { content } = first;
4779
4777
  if (!content || typeof content !== "object") {
4780
4778
  return [];
4781
4779
  }
4782
- const parts = content.parts;
4780
+ const { parts } = content;
4783
4781
  return Array.isArray(parts) ? parts : [];
4784
4782
  };
4785
4783
  var extractGeminiText = (response) => extractGeminiCandidateParts(response).map((part) => part && typeof part === "object" && typeof part.text === "string" ? part.text : "").filter(Boolean).join(`
@@ -4790,7 +4788,7 @@ var extractGeminiToolCalls = (response) => {
4790
4788
  if (!part || typeof part !== "object") {
4791
4789
  continue;
4792
4790
  }
4793
- const functionCall = part.functionCall;
4791
+ const { functionCall } = part;
4794
4792
  if (!functionCall || typeof functionCall !== "object") {
4795
4793
  continue;
4796
4794
  }
@@ -4926,7 +4924,7 @@ var toVoiceSessionSummary = (session) => ({
4926
4924
  var getContextQuery = (context) => context.query;
4927
4925
  var titleCaseProvider = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => part[0]?.toUpperCase() + part.slice(1)).join(" ");
4928
4926
  var resolveRequestedProvider = (context, providers) => {
4929
- const provider = getContextQuery(context).provider;
4927
+ const { provider } = getContextQuery(context);
4930
4928
  return providers.includes(provider) ? provider : providers[0];
4931
4929
  };
4932
4930
  var createVoiceProviderFailureSimulator = (options) => {
@@ -4956,8 +4954,19 @@ var createVoiceProviderFailureSimulator = (options) => {
4956
4954
  }
4957
4955
  ]));
4958
4956
  const router = createVoiceProviderRouter({
4957
+ fallbackMode: "provider-error",
4958
+ isProviderError: options.isProviderError,
4959
+ isRateLimitError: options.isRateLimitError,
4960
+ onProviderEvent: options.onProviderEvent,
4961
+ policy: "prefer-selected",
4962
+ providerHealth: options.providerHealth ?? {
4963
+ cooldownMs: 30000,
4964
+ failureThreshold: 1,
4965
+ rateLimitCooldownMs: 120000
4966
+ },
4967
+ providers: providerModels,
4959
4968
  allowProviders: async (input) => {
4960
- const recoverProvider = getContextQuery(input.context).recoverProvider;
4969
+ const { recoverProvider } = getContextQuery(input.context);
4961
4970
  if (recoverProvider) {
4962
4971
  return [recoverProvider];
4963
4972
  }
@@ -4973,17 +4982,6 @@ var createVoiceProviderFailureSimulator = (options) => {
4973
4982
  }
4974
4983
  return options.fallback ?? options.providers.filter((provider) => provider !== selectedProvider);
4975
4984
  },
4976
- fallbackMode: "provider-error",
4977
- isProviderError: options.isProviderError,
4978
- isRateLimitError: options.isRateLimitError,
4979
- onProviderEvent: options.onProviderEvent,
4980
- policy: "prefer-selected",
4981
- providerHealth: options.providerHealth ?? {
4982
- cooldownMs: 30000,
4983
- failureThreshold: 1,
4984
- rateLimitCooldownMs: 120000
4985
- },
4986
- providers: providerModels,
4987
4985
  selectProvider: ({ context }) => resolveRequestedProvider(context, options.providers)
4988
4986
  });
4989
4987
  const run = async (provider, mode) => {
@@ -5107,41 +5105,15 @@ var defaultWebhookBody = (input) => ({
5107
5105
  source: "absolutejs-voice",
5108
5106
  target: input.target
5109
5107
  });
5110
- var deliverVoiceHandoff = async (input) => {
5111
- if (!input.config || input.config.adapters.length === 0) {
5112
- return;
5113
- }
5114
- const deliveries = {};
5115
- for (const adapter of input.config.adapters) {
5116
- if (adapter.actions && !adapter.actions.includes(input.handoff.action)) {
5117
- deliveries[adapter.id] = createSkippedDelivery(adapter);
5118
- continue;
5119
- }
5120
- try {
5121
- const result = await adapter.handoff(input.handoff);
5122
- deliveries[adapter.id] = {
5123
- ...result,
5124
- adapterId: adapter.id,
5125
- adapterKind: adapter.kind
5126
- };
5127
- } catch (error) {
5128
- deliveries[adapter.id] = {
5129
- adapterId: adapter.id,
5130
- adapterKind: adapter.kind,
5131
- error: toErrorMessage(error),
5132
- status: "failed"
5133
- };
5134
- if (input.config.failMode === "throw") {
5135
- throw error;
5136
- }
5137
- }
5138
- }
5139
- return {
5140
- action: input.handoff.action,
5141
- deliveries,
5142
- status: aggregateHandoffStatus(deliveries)
5143
- };
5144
- };
5108
+ var applyVoiceHandoffDeliveryResult = (delivery, result) => ({
5109
+ ...delivery,
5110
+ deliveredAt: result.status === "delivered" || result.status === "skipped" ? Date.now() : delivery.deliveredAt,
5111
+ deliveries: result.deliveries,
5112
+ deliveryAttempts: (delivery.deliveryAttempts ?? 0) + 1,
5113
+ deliveryError: result.status === "failed" ? resolveHandoffDeliveryError(result.deliveries) : undefined,
5114
+ deliveryStatus: result.status,
5115
+ updatedAt: Date.now()
5116
+ });
5145
5117
  var createVoiceHandoffDeliveryRecord = (input) => {
5146
5118
  const now = Date.now();
5147
5119
  return {
@@ -5163,39 +5135,6 @@ var createVoiceHandoffDeliveryRecord = (input) => {
5163
5135
  updatedAt: now
5164
5136
  };
5165
5137
  };
5166
- var applyVoiceHandoffDeliveryResult = (delivery, result) => ({
5167
- ...delivery,
5168
- deliveredAt: result.status === "delivered" || result.status === "skipped" ? Date.now() : delivery.deliveredAt,
5169
- deliveries: result.deliveries,
5170
- deliveryAttempts: (delivery.deliveryAttempts ?? 0) + 1,
5171
- deliveryError: result.status === "failed" ? resolveHandoffDeliveryError(result.deliveries) : undefined,
5172
- deliveryStatus: result.status,
5173
- updatedAt: Date.now()
5174
- });
5175
- var deliverVoiceHandoffDelivery = async (options) => {
5176
- const result = await deliverVoiceHandoff({
5177
- config: {
5178
- adapters: options.adapters,
5179
- failMode: options.failMode
5180
- },
5181
- handoff: {
5182
- action: options.delivery.action,
5183
- api: options.api,
5184
- context: options.delivery.context,
5185
- metadata: options.delivery.metadata,
5186
- reason: options.delivery.reason,
5187
- result: options.delivery.result,
5188
- session: options.delivery.session,
5189
- target: options.delivery.target
5190
- }
5191
- });
5192
- return result ? applyVoiceHandoffDeliveryResult(options.delivery, result) : {
5193
- ...options.delivery,
5194
- deliveryAttempts: (options.delivery.deliveryAttempts ?? 0) + 1,
5195
- deliveryStatus: "skipped",
5196
- updatedAt: Date.now()
5197
- };
5198
- };
5199
5138
  var createVoiceMemoryHandoffDeliveryStore = () => {
5200
5139
  const deliveries = new Map;
5201
5140
  return {
@@ -5211,6 +5150,8 @@ var createVoiceMemoryHandoffDeliveryStore = () => {
5211
5150
  };
5212
5151
  var createVoiceWebhookHandoffAdapter = (options) => ({
5213
5152
  actions: options.actions,
5153
+ id: options.id,
5154
+ kind: options.kind ?? "webhook",
5214
5155
  handoff: async (input) => {
5215
5156
  const fetchImpl = options.fetch ?? globalThis.fetch;
5216
5157
  if (typeof fetchImpl !== "function") {
@@ -5260,10 +5201,67 @@ var createVoiceWebhookHandoffAdapter = (options) => ({
5260
5201
  clearTimeout(timeout);
5261
5202
  }
5262
5203
  }
5263
- },
5264
- id: options.id,
5265
- kind: options.kind ?? "webhook"
5204
+ }
5266
5205
  });
5206
+ var deliverVoiceHandoff = async (input) => {
5207
+ if (!input.config || input.config.adapters.length === 0) {
5208
+ return;
5209
+ }
5210
+ const deliveries = {};
5211
+ for (const adapter of input.config.adapters) {
5212
+ if (adapter.actions && !adapter.actions.includes(input.handoff.action)) {
5213
+ deliveries[adapter.id] = createSkippedDelivery(adapter);
5214
+ continue;
5215
+ }
5216
+ try {
5217
+ const result = await adapter.handoff(input.handoff);
5218
+ deliveries[adapter.id] = {
5219
+ ...result,
5220
+ adapterId: adapter.id,
5221
+ adapterKind: adapter.kind
5222
+ };
5223
+ } catch (error) {
5224
+ deliveries[adapter.id] = {
5225
+ adapterId: adapter.id,
5226
+ adapterKind: adapter.kind,
5227
+ error: toErrorMessage(error),
5228
+ status: "failed"
5229
+ };
5230
+ if (input.config.failMode === "throw") {
5231
+ throw error;
5232
+ }
5233
+ }
5234
+ }
5235
+ return {
5236
+ action: input.handoff.action,
5237
+ deliveries,
5238
+ status: aggregateHandoffStatus(deliveries)
5239
+ };
5240
+ };
5241
+ var deliverVoiceHandoffDelivery = async (options) => {
5242
+ const result = await deliverVoiceHandoff({
5243
+ config: {
5244
+ adapters: options.adapters,
5245
+ failMode: options.failMode
5246
+ },
5247
+ handoff: {
5248
+ action: options.delivery.action,
5249
+ api: options.api,
5250
+ context: options.delivery.context,
5251
+ metadata: options.delivery.metadata,
5252
+ reason: options.delivery.reason,
5253
+ result: options.delivery.result,
5254
+ session: options.delivery.session,
5255
+ target: options.delivery.target
5256
+ }
5257
+ });
5258
+ return result ? applyVoiceHandoffDeliveryResult(options.delivery, result) : {
5259
+ ...options.delivery,
5260
+ deliveryAttempts: (options.delivery.deliveryAttempts ?? 0) + 1,
5261
+ deliveryStatus: "skipped",
5262
+ updatedAt: Date.now()
5263
+ };
5264
+ };
5267
5265
  var escapeXml = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
5268
5266
  var defaultTwilioTransferTwiML = (input) => {
5269
5267
  if (!input.target) {
@@ -5285,6 +5283,8 @@ var resolveTwilioCallSid = async (resolver, input) => {
5285
5283
  };
5286
5284
  var createVoiceTwilioRedirectHandoffAdapter = (options) => ({
5287
5285
  actions: options.actions ?? ["transfer"],
5286
+ id: options.id ?? "twilio-redirect",
5287
+ kind: "twilio-redirect",
5288
5288
  handoff: async (input) => {
5289
5289
  const fetchImpl = options.fetch ?? globalThis.fetch;
5290
5290
  const callSid = await resolveTwilioCallSid(options.callSid, input);
@@ -5337,9 +5337,7 @@ var createVoiceTwilioRedirectHandoffAdapter = (options) => ({
5337
5337
  clearTimeout(timeout);
5338
5338
  }
5339
5339
  }
5340
- },
5341
- id: options.id ?? "twilio-redirect",
5342
- kind: "twilio-redirect"
5340
+ }
5343
5341
  });
5344
5342
 
5345
5343
  // src/core/logger.ts
@@ -5374,7 +5372,7 @@ var encodePcmAsWav = (pcm, format) => {
5374
5372
  if (format.container !== "raw" || format.encoding !== "pcm_s16le") {
5375
5373
  throw new Error(`encodePcmAsWav only supports raw pcm_s16le input (got container=${format.container}, encoding=${format.encoding})`);
5376
5374
  }
5377
- const channels = format.channels;
5375
+ const { channels } = format;
5378
5376
  const sampleRate = format.sampleRateHz;
5379
5377
  const bitsPerSample = 16;
5380
5378
  const byteRate = sampleRate * channels * bitsPerSample / 8;
@@ -5410,28 +5408,6 @@ var interleaveStereoPcm = (input) => {
5410
5408
  }
5411
5409
  return new Uint8Array(output.buffer);
5412
5410
  };
5413
- var encodeStereoWav = ({
5414
- format,
5415
- left,
5416
- right
5417
- }) => {
5418
- if (format.container !== "raw" || format.encoding !== "pcm_s16le") {
5419
- throw new Error("encodeStereoWav requires raw pcm_s16le format on each channel");
5420
- }
5421
- if (format.channels !== 1) {
5422
- throw new Error("encodeStereoWav expects mono input channels");
5423
- }
5424
- const interleaved = interleaveStereoPcm({ left, right });
5425
- return encodePcmAsWav(interleaved, { ...format, channels: 2 });
5426
- };
5427
- var createVoiceWavRecordingEncoder = () => ({
5428
- encode: ({ format, pcm }) => ({
5429
- bytes: encodePcmAsWav(pcm, format),
5430
- contentType: "audio/wav",
5431
- extension: "wav"
5432
- }),
5433
- kind: "wav"
5434
- });
5435
5411
  var computePcmDurationMs = (pcmByteLength, format) => {
5436
5412
  if (format.container !== "raw" || format.encoding !== "pcm_s16le") {
5437
5413
  return 0;
@@ -5458,6 +5434,28 @@ var createVoiceMemoryRecordingStore = () => {
5458
5434
  }
5459
5435
  };
5460
5436
  };
5437
+ var createVoiceWavRecordingEncoder = () => ({
5438
+ kind: "wav",
5439
+ encode: ({ format, pcm }) => ({
5440
+ bytes: encodePcmAsWav(pcm, format),
5441
+ contentType: "audio/wav",
5442
+ extension: "wav"
5443
+ })
5444
+ });
5445
+ var encodeStereoWav = ({
5446
+ format,
5447
+ left,
5448
+ right
5449
+ }) => {
5450
+ if (format.container !== "raw" || format.encoding !== "pcm_s16le") {
5451
+ throw new Error("encodeStereoWav requires raw pcm_s16le format on each channel");
5452
+ }
5453
+ if (format.channels !== 1) {
5454
+ throw new Error("encodeStereoWav expects mono input channels");
5455
+ }
5456
+ const interleaved = interleaveStereoPcm({ left, right });
5457
+ return encodePcmAsWav(interleaved, { ...format, channels: 2 });
5458
+ };
5461
5459
 
5462
5460
  // src/core/assistantMode.ts
5463
5461
  var resolveVoiceAssistantMode = (options) => {
@@ -5710,7 +5708,7 @@ var createVoiceSession = (options) => {
5710
5708
  });
5711
5709
  const phraseHints = options.phraseHints ?? [];
5712
5710
  const lexicon = options.lexicon ?? [];
5713
- let socket = options.socket;
5711
+ let { socket } = options;
5714
5712
  let sttSession = null;
5715
5713
  let ttsSession = null;
5716
5714
  let ttsSessionPromise = null;
@@ -7589,13 +7587,13 @@ var createVoiceSession = (options) => {
7589
7587
  disconnect: async (event) => runSerial("api.disconnect", async () => {
7590
7588
  await disconnectInternal(event);
7591
7589
  }),
7592
- fail: async (error) => runSerial("api.fail", async () => {
7593
- await failInternal(error);
7594
- }),
7595
7590
  escalate: async (input) => runSerial("api.escalate", async () => {
7596
7591
  await escalateInternal(input);
7597
7592
  }),
7598
- markNoAnswer: async (input) => runSerial("api.markNoAnswer", async () => {
7593
+ fail: async (error) => runSerial("api.fail", async () => {
7594
+ await failInternal(error);
7595
+ }),
7596
+ markNoAnswer: async (input) => runSerial("api.markNoAnswer", async () => {
7599
7597
  await markNoAnswerInternal(input);
7600
7598
  }),
7601
7599
  markVoicemail: async (input) => runSerial("api.markVoicemail", async () => {
@@ -7604,10 +7602,10 @@ var createVoiceSession = (options) => {
7604
7602
  receiveAudio: async (audio) => runSerial("api.receiveAudio", async () => {
7605
7603
  await receiveAudioInternal(audio);
7606
7604
  }),
7605
+ snapshot: async () => runSerial("api.snapshot", async () => readSession()),
7607
7606
  transfer: async (input) => runSerial("api.transfer", async () => {
7608
7607
  await transferInternal(input);
7609
- }),
7610
- snapshot: async () => runSerial("api.snapshot", async () => readSession())
7608
+ })
7611
7609
  };
7612
7610
  return api;
7613
7611
  };
@@ -7689,6 +7687,7 @@ var runScenario = async (id, title, run) => {
7689
7687
  try {
7690
7688
  await run({
7691
7689
  adapter,
7690
+ turns,
7692
7691
  commit: async (text, transcriptId = `${id}-${turns.length}`) => {
7693
7692
  await adapter.session.emit("final", {
7694
7693
  receivedAt: Date.now(),
@@ -7729,8 +7728,7 @@ var runScenario = async (id, title, run) => {
7729
7728
  },
7730
7729
  type: "final"
7731
7730
  });
7732
- },
7733
- turns
7731
+ }
7734
7732
  });
7735
7733
  } finally {
7736
7734
  await voice.close("resilience-complete");
@@ -7962,10 +7960,6 @@ var compactTimeline = (timeline) => {
7962
7960
  }
7963
7961
  return rows;
7964
7962
  };
7965
- var withVoiceCallReviewId = (id, artifact) => ({
7966
- ...artifact,
7967
- id
7968
- });
7969
7963
  var createVoiceCallReviewFromLiveTelephonyReport = (report, options = {}) => {
7970
7964
  const fixture = report.fixtures?.[0];
7971
7965
  if (!fixture) {
@@ -8044,6 +8038,10 @@ var createVoiceCallReviewFromLiveTelephonyReport = (report, options = {}) => {
8044
8038
  }
8045
8039
  };
8046
8040
  };
8041
+ var withVoiceCallReviewId = (id, artifact) => ({
8042
+ ...artifact,
8043
+ id
8044
+ });
8047
8045
  var toErrorMessage2 = (error) => {
8048
8046
  if (typeof error === "string" && error.trim().length > 0) {
8049
8047
  return error;
@@ -8206,7 +8204,6 @@ var createVoiceCallReviewRecorder = (options = {}) => {
8206
8204
  return;
8207
8205
  case "pong":
8208
8206
  push("benchmark", "pong");
8209
- return;
8210
8207
  }
8211
8208
  }
8212
8209
  };
@@ -8267,54 +8264,6 @@ var renderLatencyBreakdown = (breakdown) => {
8267
8264
  ].join(`
8268
8265
  `);
8269
8266
  };
8270
- var renderVoiceCallReviewMarkdown = (artifact) => {
8271
- const summaryLines = [
8272
- `- pass: ${artifact.summary.pass ? "yes" : "no"}`,
8273
- formatMetric("first turn", artifact.summary.firstTurnLatencyMs),
8274
- formatMetric("first outbound media", artifact.summary.firstOutboundMediaLatencyMs),
8275
- formatMetric("mark", artifact.summary.markLatencyMs),
8276
- formatMetric("clear", artifact.summary.clearLatencyMs),
8277
- formatMetric("elapsed", artifact.summary.elapsedMs),
8278
- typeof artifact.summary.wordErrorRate === "number" ? `- word error rate: ${artifact.summary.wordErrorRate}` : undefined,
8279
- typeof artifact.summary.termRecall === "number" ? `- term recall: ${artifact.summary.termRecall}` : undefined,
8280
- typeof artifact.summary.turnCount === "number" ? `- turn count: ${artifact.summary.turnCount}` : undefined,
8281
- typeof artifact.summary.outboundMediaCount === "number" ? `- outbound media count: ${artifact.summary.outboundMediaCount}` : undefined
8282
- ].filter((value) => typeof value === "string");
8283
- const notes = artifact.notes.length ? ["## Notes", "", ...artifact.notes.map((note) => `- ${note}`)].join(`
8284
- `) : "";
8285
- const errors = artifact.errors.length ? ["## Errors", "", ...artifact.errors.map((error) => `- ${error}`)].join(`
8286
- `) : "";
8287
- const latency = renderLatencyBreakdown(artifact.latencyBreakdown);
8288
- const transportSummary = renderTransportSummary(artifact.timeline);
8289
- return [
8290
- `# ${artifact.title}`,
8291
- "",
8292
- artifact.path ? `Source: \`${artifact.path}\`` : undefined,
8293
- artifact.fixtureId ? `Fixture: \`${artifact.fixtureId}\`` : undefined,
8294
- "",
8295
- "## Summary",
8296
- "",
8297
- ...summaryLines,
8298
- "",
8299
- "## Transcript",
8300
- "",
8301
- `- expected: ${artifact.transcript.expected ?? "_n/a_"}`,
8302
- `- actual: ${artifact.transcript.actual}`,
8303
- "",
8304
- notes,
8305
- notes ? "" : undefined,
8306
- latency,
8307
- latency ? "" : undefined,
8308
- transportSummary,
8309
- transportSummary ? "" : undefined,
8310
- errors,
8311
- errors ? "" : undefined,
8312
- renderConfigSection(artifact.config),
8313
- renderConfigSection(artifact.config) ? "" : undefined,
8314
- renderTimeline(artifact.timeline)
8315
- ].filter((value) => typeof value === "string").join(`
8316
- `);
8317
- };
8318
8267
  var renderVoiceCallReviewHTML = (artifact) => {
8319
8268
  const notes = artifact.notes.map((note) => `<li>${escapeHtml(note)}</li>`).join("");
8320
8269
  const latency = artifact.latencyBreakdown.map((entry) => `<li><strong>${escapeHtml(entry.label)}:</strong> ${roundMetric4(entry.valueMs)}ms</li>`).join("");
@@ -8392,6 +8341,54 @@ var renderVoiceCallReviewHTML = (artifact) => {
8392
8341
  </body>
8393
8342
  </html>`;
8394
8343
  };
8344
+ var renderVoiceCallReviewMarkdown = (artifact) => {
8345
+ const summaryLines = [
8346
+ `- pass: ${artifact.summary.pass ? "yes" : "no"}`,
8347
+ formatMetric("first turn", artifact.summary.firstTurnLatencyMs),
8348
+ formatMetric("first outbound media", artifact.summary.firstOutboundMediaLatencyMs),
8349
+ formatMetric("mark", artifact.summary.markLatencyMs),
8350
+ formatMetric("clear", artifact.summary.clearLatencyMs),
8351
+ formatMetric("elapsed", artifact.summary.elapsedMs),
8352
+ typeof artifact.summary.wordErrorRate === "number" ? `- word error rate: ${artifact.summary.wordErrorRate}` : undefined,
8353
+ typeof artifact.summary.termRecall === "number" ? `- term recall: ${artifact.summary.termRecall}` : undefined,
8354
+ typeof artifact.summary.turnCount === "number" ? `- turn count: ${artifact.summary.turnCount}` : undefined,
8355
+ typeof artifact.summary.outboundMediaCount === "number" ? `- outbound media count: ${artifact.summary.outboundMediaCount}` : undefined
8356
+ ].filter((value) => typeof value === "string");
8357
+ const notes = artifact.notes.length ? ["## Notes", "", ...artifact.notes.map((note) => `- ${note}`)].join(`
8358
+ `) : "";
8359
+ const errors = artifact.errors.length ? ["## Errors", "", ...artifact.errors.map((error) => `- ${error}`)].join(`
8360
+ `) : "";
8361
+ const latency = renderLatencyBreakdown(artifact.latencyBreakdown);
8362
+ const transportSummary = renderTransportSummary(artifact.timeline);
8363
+ return [
8364
+ `# ${artifact.title}`,
8365
+ "",
8366
+ artifact.path ? `Source: \`${artifact.path}\`` : undefined,
8367
+ artifact.fixtureId ? `Fixture: \`${artifact.fixtureId}\`` : undefined,
8368
+ "",
8369
+ "## Summary",
8370
+ "",
8371
+ ...summaryLines,
8372
+ "",
8373
+ "## Transcript",
8374
+ "",
8375
+ `- expected: ${artifact.transcript.expected ?? "_n/a_"}`,
8376
+ `- actual: ${artifact.transcript.actual}`,
8377
+ "",
8378
+ notes,
8379
+ notes ? "" : undefined,
8380
+ latency,
8381
+ latency ? "" : undefined,
8382
+ transportSummary,
8383
+ transportSummary ? "" : undefined,
8384
+ errors,
8385
+ errors ? "" : undefined,
8386
+ renderConfigSection(artifact.config),
8387
+ renderConfigSection(artifact.config) ? "" : undefined,
8388
+ renderTimeline(artifact.timeline)
8389
+ ].filter((value) => typeof value === "string").join(`
8390
+ `);
8391
+ };
8395
8392
  // src/testing/sessionBenchmark.ts
8396
8393
  var average3 = (values) => values.length > 0 ? values.reduce((sum, value) => sum + value, 0) / values.length : 0;
8397
8394
  var normalizeTurnText = (value) => value.toLowerCase().replace(/[^\p{L}\p{N}\s']/gu, " ").replace(/\s+/g, " ").trim();
@@ -8498,6 +8495,22 @@ var waitForSessionIdle = async (session, settleMs, idleTimeoutMs) => {
8498
8495
  await Bun.sleep(Math.min(100, settleMs));
8499
8496
  }
8500
8497
  };
8498
+ var runVoiceSessionBenchmark = async (input) => {
8499
+ const scenarioResults = [];
8500
+ for (const scenario of input.scenarios) {
8501
+ scenarioResults.push(await runVoiceSessionBenchmarkScenario(input.adapter, scenario, {
8502
+ correctTurn: input.correctTurn,
8503
+ sttFallback: input.sttFallback,
8504
+ trace: input.trace
8505
+ }));
8506
+ }
8507
+ return {
8508
+ adapterId: input.adapterId,
8509
+ generatedAt: Date.now(),
8510
+ scenarios: scenarioResults,
8511
+ summary: summarizeVoiceSessionBenchmark(input.adapterId, scenarioResults)
8512
+ };
8513
+ };
8501
8514
  var runVoiceSessionBenchmarkScenario = async (adapter, fixture, options = {}) => {
8502
8515
  const store = createVoiceMemoryStore();
8503
8516
  const committedTurns = [];
@@ -8712,6 +8725,24 @@ var runVoiceSessionBenchmarkScenario = async (adapter, fixture, options = {}) =>
8712
8725
  trace: options.trace ? trace : undefined
8713
8726
  };
8714
8727
  };
8728
+ var runVoiceSessionBenchmarkSeries = async (input) => {
8729
+ const reports = [];
8730
+ const runCount = Math.max(1, Math.floor(input.runs));
8731
+ for (let runIndex = 0;runIndex < runCount; runIndex += 1) {
8732
+ reports.push(await runVoiceSessionBenchmark({
8733
+ adapter: input.adapter,
8734
+ adapterId: input.adapterId,
8735
+ correctTurn: input.correctTurn,
8736
+ scenarios: input.scenarios,
8737
+ sttFallback: input.sttFallback,
8738
+ trace: input.trace
8739
+ }));
8740
+ }
8741
+ return summarizeVoiceSessionBenchmarkSeries({
8742
+ adapterId: input.adapterId,
8743
+ reports
8744
+ });
8745
+ };
8715
8746
  var summarizeVoiceSessionBenchmark = (adapterId, scenarios) => {
8716
8747
  const passCount = scenarios.filter((scenario) => scenario.passes).length;
8717
8748
  const reconnectScenarios = scenarios.filter((scenario) => scenario.expectedReconnectCount > 0);
@@ -8801,40 +8832,6 @@ var summarizeVoiceSessionBenchmarkSeries = (input) => {
8801
8832
  }
8802
8833
  };
8803
8834
  };
8804
- var runVoiceSessionBenchmark = async (input) => {
8805
- const scenarioResults = [];
8806
- for (const scenario of input.scenarios) {
8807
- scenarioResults.push(await runVoiceSessionBenchmarkScenario(input.adapter, scenario, {
8808
- correctTurn: input.correctTurn,
8809
- sttFallback: input.sttFallback,
8810
- trace: input.trace
8811
- }));
8812
- }
8813
- return {
8814
- adapterId: input.adapterId,
8815
- generatedAt: Date.now(),
8816
- scenarios: scenarioResults,
8817
- summary: summarizeVoiceSessionBenchmark(input.adapterId, scenarioResults)
8818
- };
8819
- };
8820
- var runVoiceSessionBenchmarkSeries = async (input) => {
8821
- const reports = [];
8822
- const runCount = Math.max(1, Math.floor(input.runs));
8823
- for (let runIndex = 0;runIndex < runCount; runIndex += 1) {
8824
- reports.push(await runVoiceSessionBenchmark({
8825
- adapter: input.adapter,
8826
- adapterId: input.adapterId,
8827
- correctTurn: input.correctTurn,
8828
- scenarios: input.scenarios,
8829
- sttFallback: input.sttFallback,
8830
- trace: input.trace
8831
- }));
8832
- }
8833
- return summarizeVoiceSessionBenchmarkSeries({
8834
- adapterId: input.adapterId,
8835
- reports
8836
- });
8837
- };
8838
8835
  // src/core/operationsRecord.ts
8839
8836
  import { Elysia as Elysia4 } from "elysia";
8840
8837
  import {
@@ -8858,6 +8855,63 @@ var createVoiceAuditEvent = (event) => ({
8858
8855
  at: event.at ?? Date.now(),
8859
8856
  id: event.id ?? crypto.randomUUID()
8860
8857
  });
8858
+ var createVoiceAuditLogger = (store) => ({
8859
+ handoff: (input) => recordVoiceHandoffAuditEvent({ ...input, store }),
8860
+ operatorAction: (input) => recordVoiceOperatorAuditEvent({ ...input, store }),
8861
+ providerCall: (input) => recordVoiceProviderAuditEvent({ ...input, store }),
8862
+ record: (event) => recordVoiceAuditEvent(store, event),
8863
+ retention: (input) => recordVoiceRetentionAuditEvent({ ...input, store }),
8864
+ toolCall: (input) => recordVoiceToolAuditEvent({ ...input, store })
8865
+ });
8866
+ var createVoiceMemoryAuditEventStore = () => {
8867
+ const events = new Map;
8868
+ return {
8869
+ append: (event) => {
8870
+ const stored = createVoiceAuditEvent(event);
8871
+ events.set(stored.id, stored);
8872
+ return stored;
8873
+ },
8874
+ get: (id) => events.get(id),
8875
+ list: (filter) => filterVoiceAuditEvents([...events.values()], filter)
8876
+ };
8877
+ };
8878
+ var createVoiceScopedAuditEventStore = (store, scope) => {
8879
+ const upstreamFilter = (filter = {}) => {
8880
+ const next = { ...filter };
8881
+ delete next.limit;
8882
+ if (scope.actorId !== undefined) {
8883
+ delete next.actorId;
8884
+ }
8885
+ if (scope.outcome !== undefined) {
8886
+ delete next.outcome;
8887
+ }
8888
+ if (scope.resourceId !== undefined) {
8889
+ delete next.resourceId;
8890
+ }
8891
+ if (scope.resourceType !== undefined) {
8892
+ delete next.resourceType;
8893
+ }
8894
+ if (scope.sessionId !== undefined) {
8895
+ delete next.sessionId;
8896
+ }
8897
+ if (scope.traceId !== undefined) {
8898
+ delete next.traceId;
8899
+ }
8900
+ if (scope.type !== undefined) {
8901
+ delete next.type;
8902
+ }
8903
+ return next;
8904
+ };
8905
+ const scopedFilter = (filter = {}) => ({
8906
+ ...filter,
8907
+ ...scope
8908
+ });
8909
+ return {
8910
+ append: (event) => store.append(event),
8911
+ get: (id) => store.get(id),
8912
+ list: async (filter) => filterVoiceAuditEvents(await store.list(upstreamFilter(filter)), scopedFilter(filter))
8913
+ };
8914
+ };
8861
8915
  var filterVoiceAuditEvents = (events, filter = {}) => {
8862
8916
  const sorted = events.filter((event) => {
8863
8917
  if (!includes(filter.type, event.type)) {
@@ -8897,114 +8951,57 @@ var filterVoiceAuditEvents = (events, filter = {}) => {
8897
8951
  }).sort((left, right) => left.at - right.at || left.id.localeCompare(right.id));
8898
8952
  return typeof filter.limit === "number" && filter.limit >= 0 ? sorted.slice(0, filter.limit) : sorted;
8899
8953
  };
8900
- var createVoiceScopedAuditEventStore = (store, scope) => {
8901
- const upstreamFilter = (filter = {}) => {
8902
- const next = { ...filter };
8903
- delete next.limit;
8904
- if (scope.actorId !== undefined) {
8905
- delete next.actorId;
8906
- }
8907
- if (scope.outcome !== undefined) {
8908
- delete next.outcome;
8909
- }
8910
- if (scope.resourceId !== undefined) {
8911
- delete next.resourceId;
8912
- }
8913
- if (scope.resourceType !== undefined) {
8914
- delete next.resourceType;
8915
- }
8916
- if (scope.sessionId !== undefined) {
8917
- delete next.sessionId;
8918
- }
8919
- if (scope.traceId !== undefined) {
8920
- delete next.traceId;
8921
- }
8922
- if (scope.type !== undefined) {
8923
- delete next.type;
8924
- }
8925
- return next;
8926
- };
8927
- const scopedFilter = (filter = {}) => ({
8928
- ...filter,
8929
- ...scope
8930
- });
8931
- return {
8932
- append: (event) => store.append(event),
8933
- get: (id) => store.get(id),
8934
- list: async (filter) => filterVoiceAuditEvents(await store.list(upstreamFilter(filter)), scopedFilter(filter))
8935
- };
8936
- };
8937
- var createVoiceMemoryAuditEventStore = () => {
8938
- const events = new Map;
8939
- return {
8940
- append: (event) => {
8941
- const stored = createVoiceAuditEvent(event);
8942
- events.set(stored.id, stored);
8943
- return stored;
8944
- },
8945
- get: (id) => events.get(id),
8946
- list: (filter) => filterVoiceAuditEvents([...events.values()], filter)
8947
- };
8948
- };
8949
8954
  var recordVoiceAuditEvent = (store, event) => store.append(createVoiceAuditEvent(event));
8950
- var recordVoiceProviderAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
8951
- action: `${input.kind}.provider.call`,
8955
+ var recordVoiceHandoffAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
8956
+ action: "handoff",
8952
8957
  actor: input.actor,
8953
8958
  metadata: input.metadata,
8954
8959
  outcome: input.outcome,
8955
8960
  payload: {
8956
- cost: input.cost,
8957
- elapsedMs: input.elapsedMs,
8958
- error: input.error,
8959
- kind: input.kind,
8960
- model: input.model,
8961
- provider: input.provider
8961
+ fromAgentId: input.fromAgentId,
8962
+ reason: input.reason,
8963
+ target: input.target,
8964
+ toAgentId: input.toAgentId
8962
8965
  },
8963
8966
  resource: {
8964
- id: input.provider,
8965
- type: "provider"
8967
+ id: input.toAgentId ?? input.target,
8968
+ type: "handoff"
8966
8969
  },
8967
8970
  sessionId: input.sessionId,
8968
8971
  traceId: input.traceId,
8969
- type: "provider.call"
8972
+ type: "handoff"
8970
8973
  });
8971
- var recordVoiceToolAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
8972
- action: "tool.call",
8974
+ var recordVoiceOperatorAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
8975
+ action: input.action,
8973
8976
  actor: input.actor,
8974
8977
  metadata: input.metadata,
8975
- outcome: input.outcome,
8976
- payload: {
8977
- elapsedMs: input.elapsedMs,
8978
- error: input.error,
8979
- toolCallId: input.toolCallId,
8980
- toolName: input.toolName
8981
- },
8982
- resource: {
8983
- id: input.toolName,
8984
- type: "tool"
8985
- },
8978
+ outcome: input.outcome ?? "success",
8979
+ payload: input.payload,
8980
+ resource: input.resource,
8986
8981
  sessionId: input.sessionId,
8987
8982
  traceId: input.traceId,
8988
- type: "tool.call"
8983
+ type: "operator.action"
8989
8984
  });
8990
- var recordVoiceHandoffAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
8991
- action: "handoff",
8985
+ var recordVoiceProviderAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
8986
+ action: `${input.kind}.provider.call`,
8992
8987
  actor: input.actor,
8993
8988
  metadata: input.metadata,
8994
8989
  outcome: input.outcome,
8995
8990
  payload: {
8996
- fromAgentId: input.fromAgentId,
8997
- reason: input.reason,
8998
- target: input.target,
8999
- toAgentId: input.toAgentId
8991
+ cost: input.cost,
8992
+ elapsedMs: input.elapsedMs,
8993
+ error: input.error,
8994
+ kind: input.kind,
8995
+ model: input.model,
8996
+ provider: input.provider
9000
8997
  },
9001
8998
  resource: {
9002
- id: input.toAgentId ?? input.target,
9003
- type: "handoff"
8999
+ id: input.provider,
9000
+ type: "provider"
9004
9001
  },
9005
9002
  sessionId: input.sessionId,
9006
9003
  traceId: input.traceId,
9007
- type: "handoff"
9004
+ type: "provider.call"
9008
9005
  });
9009
9006
  var recordVoiceRetentionAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
9010
9007
  action: input.dryRun ? "retention.plan" : "retention.apply",
@@ -9024,34 +9021,27 @@ var recordVoiceRetentionAuditEvent = (input) => recordVoiceAuditEvent(input.stor
9024
9021
  },
9025
9022
  type: "retention.policy"
9026
9023
  });
9027
- var recordVoiceOperatorAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
9028
- action: input.action,
9024
+ var recordVoiceToolAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
9025
+ action: "tool.call",
9029
9026
  actor: input.actor,
9030
9027
  metadata: input.metadata,
9031
- outcome: input.outcome ?? "success",
9032
- payload: input.payload,
9033
- resource: input.resource,
9028
+ outcome: input.outcome,
9029
+ payload: {
9030
+ elapsedMs: input.elapsedMs,
9031
+ error: input.error,
9032
+ toolCallId: input.toolCallId,
9033
+ toolName: input.toolName
9034
+ },
9035
+ resource: {
9036
+ id: input.toolName,
9037
+ type: "tool"
9038
+ },
9034
9039
  sessionId: input.sessionId,
9035
9040
  traceId: input.traceId,
9036
- type: "operator.action"
9037
- });
9038
- var createVoiceAuditLogger = (store) => ({
9039
- handoff: (input) => recordVoiceHandoffAuditEvent({ ...input, store }),
9040
- operatorAction: (input) => recordVoiceOperatorAuditEvent({ ...input, store }),
9041
- providerCall: (input) => recordVoiceProviderAuditEvent({ ...input, store }),
9042
- record: (event) => recordVoiceAuditEvent(store, event),
9043
- retention: (input) => recordVoiceRetentionAuditEvent({ ...input, store }),
9044
- toolCall: (input) => recordVoiceToolAuditEvent({ ...input, store })
9041
+ type: "tool.call"
9045
9042
  });
9046
9043
 
9047
9044
  // src/core/trace.ts
9048
- var createVoiceTraceEventId = (event) => [
9049
- event.sessionId,
9050
- event.turnId ?? "session",
9051
- event.type,
9052
- String(event.at ?? Date.now()),
9053
- crypto.randomUUID()
9054
- ].map(encodeURIComponent).join(":");
9055
9045
  var createVoiceTraceEvent = (event) => ({
9056
9046
  ...event,
9057
9047
  at: event.at,
@@ -9062,6 +9052,13 @@ var createVoiceTraceEvent = (event) => ({
9062
9052
  type: event.type
9063
9053
  })
9064
9054
  });
9055
+ var createVoiceTraceEventId = (event) => [
9056
+ event.sessionId,
9057
+ event.turnId ?? "session",
9058
+ event.type,
9059
+ String(event.at ?? Date.now()),
9060
+ crypto.randomUUID()
9061
+ ].map(encodeURIComponent).join(":");
9065
9062
  var createVoiceTraceSinkDeliveryId = (events) => {
9066
9063
  const firstEvent = events[0];
9067
9064
  return [
@@ -9106,9 +9103,24 @@ var matchesTraceFilter = (event, filter) => {
9106
9103
  }
9107
9104
  return true;
9108
9105
  };
9109
- var filterVoiceTraceEvents = (events, filter = {}) => {
9110
- const sorted = events.filter((event) => matchesTraceFilter(event, filter)).sort((left, right) => left.at - right.at || left.id.localeCompare(right.id));
9111
- return typeof filter.limit === "number" && filter.limit >= 0 ? sorted.slice(0, filter.limit) : sorted;
9106
+ var createVoiceProofTraceStore = (options = {}) => {
9107
+ const proofStore = options.proofStore ?? createVoiceMemoryTraceEventStore();
9108
+ const scopedProofStore = options.scope ? createVoiceScopedTraceEventStore(proofStore, options.scope) : proofStore;
9109
+ return {
9110
+ append: async (event) => {
9111
+ const stored = await proofStore.append(event);
9112
+ await options.mirrorStore?.append(stored);
9113
+ return stored;
9114
+ },
9115
+ get: async (id) => await proofStore.get(id) ?? await options.mirrorStore?.get(id),
9116
+ list: (filter) => scopedProofStore.list(filter),
9117
+ remove: async (id) => {
9118
+ await Promise.all([
9119
+ proofStore.remove(id),
9120
+ options.mirrorStore?.remove(id) ?? Promise.resolve()
9121
+ ]);
9122
+ }
9123
+ };
9112
9124
  };
9113
9125
  var createVoiceScopedTraceEventStore = (store, scope) => {
9114
9126
  const upstreamFilter = (filter = {}) => {
@@ -9142,24 +9154,9 @@ var createVoiceScopedTraceEventStore = (store, scope) => {
9142
9154
  remove: (id) => store.remove(id)
9143
9155
  };
9144
9156
  };
9145
- var createVoiceProofTraceStore = (options = {}) => {
9146
- const proofStore = options.proofStore ?? createVoiceMemoryTraceEventStore();
9147
- const scopedProofStore = options.scope ? createVoiceScopedTraceEventStore(proofStore, options.scope) : proofStore;
9148
- return {
9149
- append: async (event) => {
9150
- const stored = await proofStore.append(event);
9151
- await options.mirrorStore?.append(stored);
9152
- return stored;
9153
- },
9154
- get: async (id) => await proofStore.get(id) ?? await options.mirrorStore?.get(id),
9155
- list: (filter) => scopedProofStore.list(filter),
9156
- remove: async (id) => {
9157
- await Promise.all([
9158
- proofStore.remove(id),
9159
- options.mirrorStore?.remove(id) ?? Promise.resolve()
9160
- ]);
9161
- }
9162
- };
9157
+ var filterVoiceTraceEvents = (events, filter = {}) => {
9158
+ const sorted = events.filter((event) => matchesTraceFilter(event, filter)).sort((left, right) => left.at - right.at || left.id.localeCompare(right.id));
9159
+ return typeof filter.limit === "number" && filter.limit >= 0 ? sorted.slice(0, filter.limit) : sorted;
9163
9160
  };
9164
9161
  var isPruneTimeMatch = (event, options) => {
9165
9162
  if (typeof options.before === "number" && event.at >= options.before) {
@@ -9170,14 +9167,6 @@ var isPruneTimeMatch = (event, options) => {
9170
9167
  }
9171
9168
  return true;
9172
9169
  };
9173
- var selectVoiceTraceEventsForPrune = (events, options = {}) => {
9174
- let candidates = filterVoiceTraceEvents(events, options.filter).filter((event) => isPruneTimeMatch(event, options));
9175
- if (typeof options.keepNewest === "number" && options.keepNewest >= 0) {
9176
- const newestIds = new Set([...candidates].sort((left, right) => right.at - left.at || right.id.localeCompare(left.id)).slice(0, options.keepNewest).map((event) => event.id));
9177
- candidates = candidates.filter((event) => !newestIds.has(event.id));
9178
- }
9179
- return typeof options.limit === "number" && options.limit >= 0 ? candidates.slice(0, options.limit) : candidates;
9180
- };
9181
9170
  var pruneVoiceTraceEvents = async (options) => {
9182
9171
  const events = await options.store.list(options.filter);
9183
9172
  const deleted = selectVoiceTraceEventsForPrune(events, options);
@@ -9191,6 +9180,14 @@ var pruneVoiceTraceEvents = async (options) => {
9191
9180
  scannedCount: events.length
9192
9181
  };
9193
9182
  };
9183
+ var selectVoiceTraceEventsForPrune = (events, options = {}) => {
9184
+ let candidates = filterVoiceTraceEvents(events, options.filter).filter((event) => isPruneTimeMatch(event, options));
9185
+ if (typeof options.keepNewest === "number" && options.keepNewest >= 0) {
9186
+ const newestIds = new Set([...candidates].sort((left, right) => right.at - left.at || right.id.localeCompare(left.id)).slice(0, options.keepNewest).map((event) => event.id));
9187
+ candidates = candidates.filter((event) => !newestIds.has(event.id));
9188
+ }
9189
+ return typeof options.limit === "number" && options.limit >= 0 ? candidates.slice(0, options.limit) : candidates;
9190
+ };
9194
9191
  var sleep2 = async (delayMs) => {
9195
9192
  if (delayMs <= 0) {
9196
9193
  return;
@@ -9226,7 +9223,7 @@ var createVoiceTraceS3ObjectKey = (prefix, events) => {
9226
9223
  return `${prefix}/${safeSessionId}/${Date.now()}-${safeEventId}.json`;
9227
9224
  };
9228
9225
  var resolveVoiceS3DeliveredTo = (options, key) => {
9229
- const bucket = options.bucket;
9226
+ const { bucket } = options;
9230
9227
  return bucket ? `s3://${bucket}/${key}` : `s3://${key}`;
9231
9228
  };
9232
9229
  var aggregateVoiceTraceSinkDeliveryStatus = (deliveries) => {
@@ -9240,6 +9237,9 @@ var aggregateVoiceTraceSinkDeliveryStatus = (deliveries) => {
9240
9237
  return "delivered";
9241
9238
  };
9242
9239
  var createVoiceTraceHTTPSink = (options) => ({
9240
+ eventTypes: options.eventTypes,
9241
+ id: options.id,
9242
+ kind: options.kind ?? "http",
9243
9243
  deliver: async ({ events }) => {
9244
9244
  const fetchImpl = options.fetch ?? globalThis.fetch;
9245
9245
  if (typeof fetchImpl !== "function") {
@@ -9329,15 +9329,15 @@ var createVoiceTraceHTTPSink = (options) => ({
9329
9329
  eventCount: events.length,
9330
9330
  status: "failed"
9331
9331
  };
9332
- },
9333
- eventTypes: options.eventTypes,
9334
- id: options.id,
9335
- kind: options.kind ?? "http"
9332
+ }
9336
9333
  });
9337
9334
  var createVoiceTraceS3Sink = (options) => {
9338
9335
  const client = options.client ?? new Bun.S3Client(options);
9339
9336
  const keyPrefix = normalizeVoiceTraceS3KeyPrefix(options.keyPrefix);
9340
9337
  return {
9338
+ eventTypes: options.eventTypes,
9339
+ id: options.id,
9340
+ kind: options.kind ?? "s3",
9341
9341
  deliver: async ({ events }) => {
9342
9342
  const key = createVoiceTraceS3ObjectKey(keyPrefix, events);
9343
9343
  const payload = options.body ? await options.body({ events, key }) : {
@@ -9368,43 +9368,7 @@ var createVoiceTraceS3Sink = (options) => {
9368
9368
  status: "failed"
9369
9369
  };
9370
9370
  }
9371
- },
9372
- eventTypes: options.eventTypes,
9373
- id: options.id,
9374
- kind: options.kind ?? "s3"
9375
- };
9376
- };
9377
- var deliverVoiceTraceEventsToSinks = async (input) => {
9378
- const events = input.redact ? redactVoiceTraceEvents(input.events, input.redact) : input.events;
9379
- const sinkDeliveries = {};
9380
- for (const sink of input.sinks) {
9381
- const sinkEvents = sink.eventTypes?.length ? events.filter((event) => sink.eventTypes?.includes(event.type)) : events;
9382
- if (sinkEvents.length === 0) {
9383
- sinkDeliveries[sink.id] = {
9384
- attempts: 0,
9385
- eventCount: 0,
9386
- status: "skipped"
9387
- };
9388
- continue;
9389
- }
9390
- try {
9391
- sinkDeliveries[sink.id] = await sink.deliver({
9392
- events: sinkEvents
9393
- });
9394
- } catch (error) {
9395
- sinkDeliveries[sink.id] = {
9396
- attempts: 1,
9397
- error: error instanceof Error ? error.message : String(error),
9398
- eventCount: sinkEvents.length,
9399
- status: "failed"
9400
- };
9401
9371
  }
9402
- }
9403
- return {
9404
- deliveredAt: Date.now(),
9405
- eventCount: events.length,
9406
- sinkDeliveries,
9407
- status: aggregateVoiceTraceSinkDeliveryStatus(sinkDeliveries)
9408
9372
  };
9409
9373
  };
9410
9374
  var createVoiceTraceSinkStore = (options) => {
@@ -9441,45 +9405,53 @@ var createVoiceTraceSinkStore = (options) => {
9441
9405
  remove: (id) => options.store.remove(id)
9442
9406
  };
9443
9407
  };
9444
- var normalizeVoiceProfileTraceTaggerProfile = (profile) => typeof profile === "string" ? { id: profile } : profile?.id ? profile : undefined;
9445
- var createVoiceProfileTraceTagger = (options) => {
9446
- const profiles = new Map((options.profiles ?? []).map((profile) => [profile.id, profile]));
9447
- const defaultProfile = normalizeVoiceProfileTraceTaggerProfile(options.defaultProfile);
9448
- const resolveProfile = async (event) => {
9449
- const resolved = normalizeVoiceProfileTraceTaggerProfile(await options.resolveProfile?.(event));
9450
- const profile = resolved ?? defaultProfile;
9451
- return profile ? profiles.get(profile.id) ?? profile : undefined;
9452
- };
9453
- return {
9454
- append: async (event) => {
9455
- const profile = await resolveProfile(event);
9456
- if (!profile) {
9457
- return options.store.append(event);
9458
- }
9459
- const metadata = {
9460
- ...event.metadata ?? {},
9461
- benchmarkProfileId: profile.id,
9462
- profileDescription: event.metadata?.profileDescription ?? profile.description,
9463
- profileId: profile.id,
9464
- profileLabel: event.metadata?.profileLabel ?? profile.label
9408
+ var deliverVoiceTraceEventsToSinks = async (input) => {
9409
+ const events = input.redact ? redactVoiceTraceEvents(input.events, input.redact) : input.events;
9410
+ const sinkDeliveries = {};
9411
+ for (const sink of input.sinks) {
9412
+ const sinkEvents = sink.eventTypes?.length ? events.filter((event) => sink.eventTypes?.includes(event.type)) : events;
9413
+ if (sinkEvents.length === 0) {
9414
+ sinkDeliveries[sink.id] = {
9415
+ attempts: 0,
9416
+ eventCount: 0,
9417
+ status: "skipped"
9465
9418
  };
9466
- const payload = event.payload && typeof event.payload === "object" ? {
9467
- ...event.payload,
9468
- benchmarkProfileId: event.payload.benchmarkProfileId ?? profile.id,
9469
- profileDescription: event.payload.profileDescription ?? profile.description,
9470
- profileId: event.payload.profileId ?? profile.id,
9471
- profileLabel: event.payload.profileLabel ?? profile.label
9472
- } : event.payload;
9473
- return options.store.append({
9474
- ...event,
9475
- metadata,
9476
- payload
9419
+ continue;
9420
+ }
9421
+ try {
9422
+ sinkDeliveries[sink.id] = await sink.deliver({
9423
+ events: sinkEvents
9477
9424
  });
9478
- },
9479
- get: (id) => options.store.get(id),
9480
- list: (filter) => options.store.list(filter),
9481
- remove: (id) => options.store.remove(id)
9425
+ } catch (error) {
9426
+ sinkDeliveries[sink.id] = {
9427
+ attempts: 1,
9428
+ error: error instanceof Error ? error.message : String(error),
9429
+ eventCount: sinkEvents.length,
9430
+ status: "failed"
9431
+ };
9432
+ }
9433
+ }
9434
+ return {
9435
+ deliveredAt: Date.now(),
9436
+ eventCount: events.length,
9437
+ sinkDeliveries,
9438
+ status: aggregateVoiceTraceSinkDeliveryStatus(sinkDeliveries)
9439
+ };
9440
+ };
9441
+ var normalizeVoiceProfileTraceTaggerProfile = (profile) => typeof profile === "string" ? { id: profile } : profile?.id ? profile : undefined;
9442
+ var createVoiceMemoryTraceEventStore = () => {
9443
+ const events = new Map;
9444
+ const append = async (event) => {
9445
+ const stored = createVoiceTraceEvent(event);
9446
+ events.set(stored.id, stored);
9447
+ return stored;
9448
+ };
9449
+ const get = async (id) => events.get(id);
9450
+ const list = async (filter) => filterVoiceTraceEvents([...events.values()], filter);
9451
+ const remove = async (id) => {
9452
+ events.delete(id);
9482
9453
  };
9454
+ return { append, get, list, remove };
9483
9455
  };
9484
9456
  var createVoiceMemoryTraceSinkDeliveryStore = () => {
9485
9457
  const deliveries = new Map;
@@ -9494,19 +9466,44 @@ var createVoiceMemoryTraceSinkDeliveryStore = () => {
9494
9466
  }
9495
9467
  };
9496
9468
  };
9497
- var createVoiceMemoryTraceEventStore = () => {
9498
- const events = new Map;
9499
- const append = async (event) => {
9500
- const stored = createVoiceTraceEvent(event);
9501
- events.set(stored.id, stored);
9502
- return stored;
9469
+ var createVoiceProfileTraceTagger = (options) => {
9470
+ const profiles = new Map((options.profiles ?? []).map((profile) => [profile.id, profile]));
9471
+ const defaultProfile = normalizeVoiceProfileTraceTaggerProfile(options.defaultProfile);
9472
+ const resolveProfile = async (event) => {
9473
+ const resolved = normalizeVoiceProfileTraceTaggerProfile(await options.resolveProfile?.(event));
9474
+ const profile = resolved ?? defaultProfile;
9475
+ return profile ? profiles.get(profile.id) ?? profile : undefined;
9503
9476
  };
9504
- const get = async (id) => events.get(id);
9505
- const list = async (filter) => filterVoiceTraceEvents([...events.values()], filter);
9506
- const remove = async (id) => {
9507
- events.delete(id);
9477
+ return {
9478
+ append: async (event) => {
9479
+ const profile = await resolveProfile(event);
9480
+ if (!profile) {
9481
+ return options.store.append(event);
9482
+ }
9483
+ const metadata = {
9484
+ ...event.metadata ?? {},
9485
+ benchmarkProfileId: profile.id,
9486
+ profileDescription: event.metadata?.profileDescription ?? profile.description,
9487
+ profileId: profile.id,
9488
+ profileLabel: event.metadata?.profileLabel ?? profile.label
9489
+ };
9490
+ const payload = event.payload && typeof event.payload === "object" ? {
9491
+ ...event.payload,
9492
+ benchmarkProfileId: event.payload.benchmarkProfileId ?? profile.id,
9493
+ profileDescription: event.payload.profileDescription ?? profile.description,
9494
+ profileId: event.payload.profileId ?? profile.id,
9495
+ profileLabel: event.payload.profileLabel ?? profile.label
9496
+ } : event.payload;
9497
+ return options.store.append({
9498
+ ...event,
9499
+ metadata,
9500
+ payload
9501
+ });
9502
+ },
9503
+ get: (id) => options.store.get(id),
9504
+ list: (filter) => options.store.list(filter),
9505
+ remove: (id) => options.store.remove(id)
9508
9506
  };
9509
- return { append, get, list, remove };
9510
9507
  };
9511
9508
  var exportVoiceTrace = async (input) => {
9512
9509
  const events = await input.store.list(input.filter);
@@ -9618,53 +9615,6 @@ var redactTraceValue = (value, options, path) => {
9618
9615
  }
9619
9616
  return value;
9620
9617
  };
9621
- var redactVoiceTraceEvent = (event, options = {}) => {
9622
- const resolved = resolveVoiceTraceRedactionOptions(options);
9623
- return {
9624
- ...event,
9625
- metadata: redactTraceValue(event.metadata, resolved, [
9626
- "metadata"
9627
- ]),
9628
- payload: redactTraceValue(event.payload, resolved, [
9629
- "payload"
9630
- ])
9631
- };
9632
- };
9633
- var redactVoiceTraceEvents = (events, options = {}) => events.map((event) => redactVoiceTraceEvent(event, options));
9634
- var summarizeVoiceTrace = (events) => {
9635
- const sorted = filterVoiceTraceEvents(events);
9636
- const firstEvent = sorted[0];
9637
- const lastEvent = sorted.at(-1);
9638
- const lifecycleEvents = sorted.filter((event) => event.type === "call.lifecycle");
9639
- const startEvent = lifecycleEvents.find((event) => event.payload.type === "start");
9640
- const endEvent = lifecycleEvents.toReversed().find((event) => event.payload.type === "end");
9641
- const costEvents = sorted.filter((event) => event.type === "turn.cost");
9642
- const toolEvents = sorted.filter((event) => event.type === "agent.tool");
9643
- const startedAt = startEvent?.at ?? firstEvent?.at;
9644
- const endedAt = endEvent?.at ?? lastEvent?.at;
9645
- const failed = sorted.some((event) => event.type === "session.error") || endEvent?.payload.disposition === "failed";
9646
- return {
9647
- assistantReplyCount: sorted.filter((event) => event.type === "turn.assistant").length,
9648
- callDurationMs: startedAt !== undefined && endedAt !== undefined ? Math.max(0, endedAt - startedAt) : undefined,
9649
- cost: {
9650
- estimatedRelativeCostUnits: costEvents.reduce((total, event) => total + toNumber(event.payload.estimatedRelativeCostUnits), 0),
9651
- totalBillableAudioMs: costEvents.reduce((total, event) => total + toNumber(event.payload.totalBillableAudioMs), 0)
9652
- },
9653
- endedAt,
9654
- errorCount: sorted.filter((event) => event.type === "session.error").length,
9655
- eventCount: sorted.length,
9656
- failed,
9657
- handoffCount: sorted.filter((event) => event.type === "agent.handoff").length,
9658
- modelCallCount: sorted.filter((event) => event.type === "agent.model").length,
9659
- sessionId: firstEvent?.sessionId,
9660
- startedAt,
9661
- toolCallCount: toolEvents.length,
9662
- toolErrorCount: toolEvents.filter((event) => event.payload.status === "error").length,
9663
- traceId: firstEvent?.traceId,
9664
- transcriptCount: sorted.filter((event) => event.type === "turn.transcript").length,
9665
- turnCount: sorted.filter((event) => event.type === "turn.committed").length
9666
- };
9667
- };
9668
9618
  var evaluateVoiceTrace = (events, options = {}) => {
9669
9619
  const summary = summarizeVoiceTrace(events);
9670
9620
  const issues = [];
@@ -9734,6 +9684,53 @@ var evaluateVoiceTrace = (events, options = {}) => {
9734
9684
  summary
9735
9685
  };
9736
9686
  };
9687
+ var redactVoiceTraceEvent = (event, options = {}) => {
9688
+ const resolved = resolveVoiceTraceRedactionOptions(options);
9689
+ return {
9690
+ ...event,
9691
+ metadata: redactTraceValue(event.metadata, resolved, [
9692
+ "metadata"
9693
+ ]),
9694
+ payload: redactTraceValue(event.payload, resolved, [
9695
+ "payload"
9696
+ ])
9697
+ };
9698
+ };
9699
+ var redactVoiceTraceEvents = (events, options = {}) => events.map((event) => redactVoiceTraceEvent(event, options));
9700
+ var summarizeVoiceTrace = (events) => {
9701
+ const sorted = filterVoiceTraceEvents(events);
9702
+ const firstEvent = sorted[0];
9703
+ const lastEvent = sorted.at(-1);
9704
+ const lifecycleEvents = sorted.filter((event) => event.type === "call.lifecycle");
9705
+ const startEvent = lifecycleEvents.find((event) => event.payload.type === "start");
9706
+ const endEvent = lifecycleEvents.toReversed().find((event) => event.payload.type === "end");
9707
+ const costEvents = sorted.filter((event) => event.type === "turn.cost");
9708
+ const toolEvents = sorted.filter((event) => event.type === "agent.tool");
9709
+ const startedAt = startEvent?.at ?? firstEvent?.at;
9710
+ const endedAt = endEvent?.at ?? lastEvent?.at;
9711
+ const failed = sorted.some((event) => event.type === "session.error") || endEvent?.payload.disposition === "failed";
9712
+ return {
9713
+ assistantReplyCount: sorted.filter((event) => event.type === "turn.assistant").length,
9714
+ callDurationMs: startedAt !== undefined && endedAt !== undefined ? Math.max(0, endedAt - startedAt) : undefined,
9715
+ cost: {
9716
+ estimatedRelativeCostUnits: costEvents.reduce((total, event) => total + toNumber(event.payload.estimatedRelativeCostUnits), 0),
9717
+ totalBillableAudioMs: costEvents.reduce((total, event) => total + toNumber(event.payload.totalBillableAudioMs), 0)
9718
+ },
9719
+ endedAt,
9720
+ errorCount: sorted.filter((event) => event.type === "session.error").length,
9721
+ eventCount: sorted.length,
9722
+ failed,
9723
+ handoffCount: sorted.filter((event) => event.type === "agent.handoff").length,
9724
+ modelCallCount: sorted.filter((event) => event.type === "agent.model").length,
9725
+ sessionId: firstEvent?.sessionId,
9726
+ startedAt,
9727
+ toolCallCount: toolEvents.length,
9728
+ toolErrorCount: toolEvents.filter((event) => event.payload.status === "error").length,
9729
+ traceId: firstEvent?.traceId,
9730
+ transcriptCount: sorted.filter((event) => event.type === "turn.transcript").length,
9731
+ turnCount: sorted.filter((event) => event.type === "turn.committed").length
9732
+ };
9733
+ };
9737
9734
  var renderTraceEventMarkdown = (event, startedAt) => {
9738
9735
  const offset = startedAt === undefined ? `${event.at}` : `+${Math.max(0, event.at - startedAt)}ms`;
9739
9736
  const label = `- ${offset} [${event.type}]`;
@@ -9758,40 +9755,12 @@ var renderTraceEventMarkdown = (event, startedAt) => {
9758
9755
  return `${label} ${formatTraceValue(event.payload)}`;
9759
9756
  }
9760
9757
  };
9761
- var renderVoiceTraceMarkdown = (events, options = {}) => {
9762
- const sorted = filterVoiceTraceEvents(options.redact ? redactVoiceTraceEvents(events, options.redact) : events);
9763
- const summary = summarizeVoiceTrace(sorted);
9764
- const evaluation = evaluateVoiceTrace(sorted, options.evaluation);
9765
- const lines = [
9766
- `# ${options.title ?? `Voice Trace ${summary.sessionId ?? ""}`.trim()}`,
9767
- "",
9768
- `Pass: ${evaluation.pass ? "yes" : "no"}`,
9769
- `Session: ${summary.sessionId ?? "unknown"}`,
9770
- `Events: ${summary.eventCount}`,
9771
- `Turns: ${summary.turnCount}`,
9772
- `Transcripts: ${summary.transcriptCount}`,
9773
- `Assistant replies: ${summary.assistantReplyCount}`,
9774
- `Model calls: ${summary.modelCallCount}`,
9775
- `Tool calls: ${summary.toolCallCount}`,
9776
- `Handoffs: ${summary.handoffCount}`,
9777
- `Errors: ${summary.errorCount}`,
9778
- `Estimated cost units: ${summary.cost.estimatedRelativeCostUnits}`,
9779
- ""
9780
- ];
9781
- if (evaluation.issues.length > 0) {
9782
- lines.push("## Issues", "");
9783
- for (const issue of evaluation.issues) {
9784
- lines.push(`- [${issue.severity}] ${issue.code}: ${issue.message}`);
9785
- }
9786
- lines.push("");
9787
- }
9788
- lines.push("## Timeline", "");
9789
- for (const event of sorted) {
9790
- lines.push(renderTraceEventMarkdown(event, summary.startedAt));
9791
- }
9792
- return lines.join(`
9793
- `);
9794
- };
9758
+ var buildVoiceTraceReplay = (events, options = {}) => ({
9759
+ evaluation: evaluateVoiceTrace(options.redact ? redactVoiceTraceEvents(events, options.redact) : events, options.evaluation),
9760
+ html: renderVoiceTraceHTML(events, options),
9761
+ markdown: renderVoiceTraceMarkdown(events, options),
9762
+ summary: summarizeVoiceTrace(options.redact ? redactVoiceTraceEvents(events, options.redact) : events)
9763
+ });
9795
9764
  var renderVoiceTraceHTML = (events, options = {}) => {
9796
9765
  const markdown = renderVoiceTraceMarkdown(events, options);
9797
9766
  const renderEvents = options.redact ? redactVoiceTraceEvents(events, options.redact) : events;
@@ -9848,22 +9817,50 @@ var renderVoiceTraceHTML = (events, options = {}) => {
9848
9817
  ].join(`
9849
9818
  `);
9850
9819
  };
9851
- var buildVoiceTraceReplay = (events, options = {}) => ({
9852
- evaluation: evaluateVoiceTrace(options.redact ? redactVoiceTraceEvents(events, options.redact) : events, options.evaluation),
9853
- html: renderVoiceTraceHTML(events, options),
9854
- markdown: renderVoiceTraceMarkdown(events, options),
9855
- summary: summarizeVoiceTrace(options.redact ? redactVoiceTraceEvents(events, options.redact) : events)
9856
- });
9857
-
9858
- // src/core/auditRoutes.ts
9859
- import { Elysia } from "elysia";
9860
- var getString = (value) => typeof value === "string" && value.trim() ? value.trim() : undefined;
9861
- var getNumber = (value) => {
9862
- if (typeof value === "number" && Number.isFinite(value)) {
9863
- return value;
9864
- }
9865
- if (typeof value === "string" && value.trim()) {
9866
- const parsed = Number(value);
9820
+ var renderVoiceTraceMarkdown = (events, options = {}) => {
9821
+ const sorted = filterVoiceTraceEvents(options.redact ? redactVoiceTraceEvents(events, options.redact) : events);
9822
+ const summary = summarizeVoiceTrace(sorted);
9823
+ const evaluation = evaluateVoiceTrace(sorted, options.evaluation);
9824
+ const lines = [
9825
+ `# ${options.title ?? `Voice Trace ${summary.sessionId ?? ""}`.trim()}`,
9826
+ "",
9827
+ `Pass: ${evaluation.pass ? "yes" : "no"}`,
9828
+ `Session: ${summary.sessionId ?? "unknown"}`,
9829
+ `Events: ${summary.eventCount}`,
9830
+ `Turns: ${summary.turnCount}`,
9831
+ `Transcripts: ${summary.transcriptCount}`,
9832
+ `Assistant replies: ${summary.assistantReplyCount}`,
9833
+ `Model calls: ${summary.modelCallCount}`,
9834
+ `Tool calls: ${summary.toolCallCount}`,
9835
+ `Handoffs: ${summary.handoffCount}`,
9836
+ `Errors: ${summary.errorCount}`,
9837
+ `Estimated cost units: ${summary.cost.estimatedRelativeCostUnits}`,
9838
+ ""
9839
+ ];
9840
+ if (evaluation.issues.length > 0) {
9841
+ lines.push("## Issues", "");
9842
+ for (const issue of evaluation.issues) {
9843
+ lines.push(`- [${issue.severity}] ${issue.code}: ${issue.message}`);
9844
+ }
9845
+ lines.push("");
9846
+ }
9847
+ lines.push("## Timeline", "");
9848
+ for (const event of sorted) {
9849
+ lines.push(renderTraceEventMarkdown(event, summary.startedAt));
9850
+ }
9851
+ return lines.join(`
9852
+ `);
9853
+ };
9854
+
9855
+ // src/core/auditRoutes.ts
9856
+ import { Elysia } from "elysia";
9857
+ var getString = (value) => typeof value === "string" && value.trim() ? value.trim() : undefined;
9858
+ var getNumber = (value) => {
9859
+ if (typeof value === "number" && Number.isFinite(value)) {
9860
+ return value;
9861
+ }
9862
+ if (typeof value === "string" && value.trim()) {
9863
+ const parsed = Number(value);
9867
9864
  return Number.isFinite(parsed) ? parsed : undefined;
9868
9865
  }
9869
9866
  return;
@@ -9903,42 +9900,6 @@ var increment = (counts, key) => {
9903
9900
  counts.set(key, (counts.get(key) ?? 0) + 1);
9904
9901
  };
9905
9902
  var sortedCounts = (counts) => [...counts.entries()].sort(([leftKey, leftCount], [rightKey, rightCount]) => rightCount - leftCount || leftKey.localeCompare(rightKey));
9906
- var resolveVoiceAuditTrailFilter = (query = {}, base = {}) => ({
9907
- ...base,
9908
- actorId: getString(query.actorId) ?? base.actorId,
9909
- after: getNumber(query.after) ?? base.after,
9910
- afterOrAt: getNumber(query.afterOrAt) ?? base.afterOrAt,
9911
- before: getNumber(query.before) ?? base.before,
9912
- beforeOrAt: getNumber(query.beforeOrAt) ?? base.beforeOrAt,
9913
- limit: getNumber(query.limit) ?? base.limit,
9914
- outcome: parseOutcome(query.outcome) ?? base.outcome,
9915
- resourceId: getString(query.resourceId) ?? base.resourceId,
9916
- resourceType: getString(query.resourceType) ?? base.resourceType,
9917
- sessionId: getString(query.sessionId) ?? base.sessionId,
9918
- traceId: getString(query.traceId) ?? base.traceId,
9919
- type: parseType(query.type) ?? base.type
9920
- });
9921
- var summarizeVoiceAuditTrail = (events) => {
9922
- const byActor = new Map;
9923
- const byOutcome = new Map;
9924
- const byResourceType = new Map;
9925
- const byType = new Map;
9926
- for (const event of events) {
9927
- increment(byActor, event.actor?.id);
9928
- increment(byOutcome, event.outcome);
9929
- increment(byResourceType, event.resource?.type);
9930
- increment(byType, event.type);
9931
- }
9932
- return {
9933
- byActor: sortedCounts(byActor),
9934
- byOutcome: sortedCounts(byOutcome),
9935
- byResourceType: sortedCounts(byResourceType),
9936
- byType: sortedCounts(byType),
9937
- errors: events.filter((event) => event.outcome === "error").length,
9938
- latestAt: events.at(-1)?.at,
9939
- total: events.length
9940
- };
9941
- };
9942
9903
  var buildVoiceAuditTrailReport = async (options) => {
9943
9904
  const filter = {
9944
9905
  ...options.filter,
@@ -9952,17 +9913,6 @@ var buildVoiceAuditTrailReport = async (options) => {
9952
9913
  summary: summarizeVoiceAuditTrail(events)
9953
9914
  };
9954
9915
  };
9955
- var renderVoiceAuditTrailHTML = (report, options = {}) => {
9956
- const title = options.title ?? "AbsoluteJS Voice Audit Trail";
9957
- const chips = report.summary.byType.map(([type, count]) => `<span>${escapeHtml(type)} <strong>${count}</strong></span>`).join("");
9958
- const rows = report.events.map((event) => {
9959
- const actor = event.actor ? `${event.actor.kind}:${event.actor.id}` : "unknown";
9960
- const resource = event.resource ? `${event.resource.type}${event.resource.id ? `:${event.resource.id}` : ""}` : "";
9961
- const payload = event.payload ? JSON.stringify(event.payload, null, 2) : "";
9962
- return `<article class="event ${escapeHtml(event.outcome ?? "unknown")}"><div><span>${escapeHtml(event.type)}</span><h2>${escapeHtml(event.action)}</h2><p>${escapeHtml(new Date(event.at).toLocaleString())}</p><p>Actor: ${escapeHtml(actor)}${resource ? ` \xB7 Resource: ${escapeHtml(resource)}` : ""}</p>${event.sessionId ? `<p>Session: ${escapeHtml(event.sessionId)}</p>` : ""}</div><strong>${escapeHtml(event.outcome ?? "recorded")}</strong>${payload ? `<pre>${escapeHtml(payload)}</pre>` : ""}</article>`;
9963
- }).join("");
9964
- return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml(title)}</title><style>body{background:#11140f;color:#f7f1df;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1120px;padding:32px}.hero{background:linear-gradient(135deg,rgba(34,197,94,.18),rgba(245,158,11,.12));border:1px solid #2c3327;border-radius:28px;margin-bottom:18px;padding:28px}.eyebrow{color:#facc15;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.3rem,6vw,4.8rem);line-height:.92;margin:.2rem 0 1rem}.chips{display:flex;flex-wrap:wrap;gap:10px}.chips span{border:1px solid #46513b;border-radius:999px;padding:8px 12px}.events{display:grid;gap:14px}.event{background:#181d15;border:1px solid #2c3327;border-radius:22px;display:grid;gap:16px;grid-template-columns:1fr auto;padding:18px}.event.error{border-color:rgba(239,68,68,.75)}.event.skipped{border-color:rgba(245,158,11,.7)}.event.success{border-color:rgba(34,197,94,.55)}.event span{color:#facc15;font-size:.78rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}.event h2{margin:.2rem 0}.event p{color:#c8ccb8;margin:.2rem 0}.event strong{text-transform:uppercase}pre{background:#0c0f0a;border-radius:14px;grid-column:1/-1;overflow:auto;padding:14px;white-space:pre-wrap}@media(max-width:760px){main{padding:20px}.event{grid-template-columns:1fr}}</style></head><body><main><section class="hero"><p class="eyebrow">Self-hosted evidence</p><h1>${escapeHtml(title)}</h1><p>${report.summary.total} event(s), ${report.summary.errors} error(s). Latest ${report.summary.latestAt ? escapeHtml(new Date(report.summary.latestAt).toLocaleString()) : "never"}.</p><div class="chips">${chips}</div></section><section class="events">${rows || "<p>No audit events match this filter.</p>"}</section></main></body></html>`;
9965
- };
9966
9916
  var createVoiceAuditTrailRoutes = (options) => {
9967
9917
  const path = options.path ?? "/api/voice-audit";
9968
9918
  const htmlPath = options.htmlPath ?? "/audit";
@@ -10051,6 +10001,53 @@ var createVoiceAuditTrailRoutes = (options) => {
10051
10001
  }
10052
10002
  return routes;
10053
10003
  };
10004
+ var renderVoiceAuditTrailHTML = (report, options = {}) => {
10005
+ const title = options.title ?? "AbsoluteJS Voice Audit Trail";
10006
+ const chips = report.summary.byType.map(([type, count]) => `<span>${escapeHtml(type)} <strong>${count}</strong></span>`).join("");
10007
+ const rows = report.events.map((event) => {
10008
+ const actor = event.actor ? `${event.actor.kind}:${event.actor.id}` : "unknown";
10009
+ const resource = event.resource ? `${event.resource.type}${event.resource.id ? `:${event.resource.id}` : ""}` : "";
10010
+ const payload = event.payload ? JSON.stringify(event.payload, null, 2) : "";
10011
+ return `<article class="event ${escapeHtml(event.outcome ?? "unknown")}"><div><span>${escapeHtml(event.type)}</span><h2>${escapeHtml(event.action)}</h2><p>${escapeHtml(new Date(event.at).toLocaleString())}</p><p>Actor: ${escapeHtml(actor)}${resource ? ` \xB7 Resource: ${escapeHtml(resource)}` : ""}</p>${event.sessionId ? `<p>Session: ${escapeHtml(event.sessionId)}</p>` : ""}</div><strong>${escapeHtml(event.outcome ?? "recorded")}</strong>${payload ? `<pre>${escapeHtml(payload)}</pre>` : ""}</article>`;
10012
+ }).join("");
10013
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml(title)}</title><style>body{background:#11140f;color:#f7f1df;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1120px;padding:32px}.hero{background:linear-gradient(135deg,rgba(34,197,94,.18),rgba(245,158,11,.12));border:1px solid #2c3327;border-radius:28px;margin-bottom:18px;padding:28px}.eyebrow{color:#facc15;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.3rem,6vw,4.8rem);line-height:.92;margin:.2rem 0 1rem}.chips{display:flex;flex-wrap:wrap;gap:10px}.chips span{border:1px solid #46513b;border-radius:999px;padding:8px 12px}.events{display:grid;gap:14px}.event{background:#181d15;border:1px solid #2c3327;border-radius:22px;display:grid;gap:16px;grid-template-columns:1fr auto;padding:18px}.event.error{border-color:rgba(239,68,68,.75)}.event.skipped{border-color:rgba(245,158,11,.7)}.event.success{border-color:rgba(34,197,94,.55)}.event span{color:#facc15;font-size:.78rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}.event h2{margin:.2rem 0}.event p{color:#c8ccb8;margin:.2rem 0}.event strong{text-transform:uppercase}pre{background:#0c0f0a;border-radius:14px;grid-column:1/-1;overflow:auto;padding:14px;white-space:pre-wrap}@media(max-width:760px){main{padding:20px}.event{grid-template-columns:1fr}}</style></head><body><main><section class="hero"><p class="eyebrow">Self-hosted evidence</p><h1>${escapeHtml(title)}</h1><p>${report.summary.total} event(s), ${report.summary.errors} error(s). Latest ${report.summary.latestAt ? escapeHtml(new Date(report.summary.latestAt).toLocaleString()) : "never"}.</p><div class="chips">${chips}</div></section><section class="events">${rows || "<p>No audit events match this filter.</p>"}</section></main></body></html>`;
10014
+ };
10015
+ var resolveVoiceAuditTrailFilter = (query = {}, base = {}) => ({
10016
+ ...base,
10017
+ actorId: getString(query.actorId) ?? base.actorId,
10018
+ after: getNumber(query.after) ?? base.after,
10019
+ afterOrAt: getNumber(query.afterOrAt) ?? base.afterOrAt,
10020
+ before: getNumber(query.before) ?? base.before,
10021
+ beforeOrAt: getNumber(query.beforeOrAt) ?? base.beforeOrAt,
10022
+ limit: getNumber(query.limit) ?? base.limit,
10023
+ outcome: parseOutcome(query.outcome) ?? base.outcome,
10024
+ resourceId: getString(query.resourceId) ?? base.resourceId,
10025
+ resourceType: getString(query.resourceType) ?? base.resourceType,
10026
+ sessionId: getString(query.sessionId) ?? base.sessionId,
10027
+ traceId: getString(query.traceId) ?? base.traceId,
10028
+ type: parseType(query.type) ?? base.type
10029
+ });
10030
+ var summarizeVoiceAuditTrail = (events) => {
10031
+ const byActor = new Map;
10032
+ const byOutcome = new Map;
10033
+ const byResourceType = new Map;
10034
+ const byType = new Map;
10035
+ for (const event of events) {
10036
+ increment(byActor, event.actor?.id);
10037
+ increment(byOutcome, event.outcome);
10038
+ increment(byResourceType, event.resource?.type);
10039
+ increment(byType, event.type);
10040
+ }
10041
+ return {
10042
+ byActor: sortedCounts(byActor),
10043
+ byOutcome: sortedCounts(byOutcome),
10044
+ byResourceType: sortedCounts(byResourceType),
10045
+ byType: sortedCounts(byType),
10046
+ errors: events.filter((event) => event.outcome === "error").length,
10047
+ latestAt: events.at(-1)?.at,
10048
+ total: events.length
10049
+ };
10050
+ };
10054
10051
 
10055
10052
  // src/core/auditExport.ts
10056
10053
  var normalizeRedactionKey2 = (key) => key.trim().toLowerCase().replace(/[^a-z0-9]/g, "");
@@ -10090,6 +10087,17 @@ var redactAuditValue = (value, config, options, path) => {
10090
10087
  }
10091
10088
  return value;
10092
10089
  };
10090
+ var exportVoiceAuditTrail = async (input) => {
10091
+ const events = await input.store.list(input.filter);
10092
+ const exportedEvents = input.redact ? redactVoiceAuditEvents(events, input.redact) : events;
10093
+ return {
10094
+ events: exportedEvents,
10095
+ exportedAt: Date.now(),
10096
+ filter: input.filter,
10097
+ redacted: Boolean(input.redact),
10098
+ summary: summarizeVoiceAuditTrail(exportedEvents)
10099
+ };
10100
+ };
10093
10101
  var redactVoiceAuditEvent = (event, options = {}) => {
10094
10102
  const resolved = resolveVoiceTraceRedactionOptions(options);
10095
10103
  return {
@@ -10109,17 +10117,6 @@ var redactVoiceAuditEvent = (event, options = {}) => {
10109
10117
  };
10110
10118
  };
10111
10119
  var redactVoiceAuditEvents = (events, options = {}) => events.map((event) => redactVoiceAuditEvent(event, options));
10112
- var exportVoiceAuditTrail = async (input) => {
10113
- const events = await input.store.list(input.filter);
10114
- const exportedEvents = input.redact ? redactVoiceAuditEvents(events, input.redact) : events;
10115
- return {
10116
- events: exportedEvents,
10117
- exportedAt: Date.now(),
10118
- filter: input.filter,
10119
- redacted: Boolean(input.redact),
10120
- summary: summarizeVoiceAuditTrail(exportedEvents)
10121
- };
10122
- };
10123
10120
  var formatAuditValue = (value) => {
10124
10121
  if (value === undefined || value === null || value === "") {
10125
10122
  return "";
@@ -10144,6 +10141,23 @@ var renderAuditEventMarkdown = (event) => {
10144
10141
  ].filter(Boolean).join(" ");
10145
10142
  return `- ${new Date(event.at).toISOString()} [${event.type}] ${event.action} ${event.outcome ?? "recorded"} ${detail} ${formatAuditValue(event.payload)}`.trim();
10146
10143
  };
10144
+ var buildVoiceAuditExport = (events, options = {}) => {
10145
+ const exportEvents = options.redact ? redactVoiceAuditEvents(events, options.redact) : events;
10146
+ return {
10147
+ events: exportEvents,
10148
+ html: renderVoiceAuditHTML(events, options),
10149
+ markdown: renderVoiceAuditMarkdown(events, options),
10150
+ summary: summarizeVoiceAuditTrail(exportEvents)
10151
+ };
10152
+ };
10153
+ var renderVoiceAuditHTML = (events, options = {}) => {
10154
+ const title = options.title ?? "Voice Audit Trail";
10155
+ const markdown = renderVoiceAuditMarkdown(events, options);
10156
+ const renderEvents = options.redact ? redactVoiceAuditEvents(events, options.redact) : events;
10157
+ const summary = summarizeVoiceAuditTrail(renderEvents);
10158
+ const rows = renderEvents.map((event) => `<tr><td>${escapeHtml(new Date(event.at).toISOString())}</td><td>${escapeHtml(event.type)}</td><td>${escapeHtml(event.action)}</td><td>${escapeHtml(event.outcome ?? "")}</td><td><code>${escapeHtml(JSON.stringify(event.payload ?? {}))}</code></td></tr>`).join("");
10159
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml(title)}</title><style>body{font-family:ui-sans-serif,system-ui,sans-serif;margin:2rem;line-height:1.45;background:#f8f7f2;color:#181713}main{max-width:1100px;margin:auto}.summary{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:.75rem;margin:1rem 0}.card{background:white;border:1px solid #ded9cc;border-radius:12px;padding:1rem}table{border-collapse:collapse;width:100%;background:white;border:1px solid #ded9cc}th,td{border-bottom:1px solid #eee8dc;padding:.65rem;text-align:left;vertical-align:top}code{white-space:pre-wrap;word-break:break-word}pre{background:#181713;color:#f8f7f2;padding:1rem;border-radius:12px;overflow:auto}</style></head><body><main><h1>${escapeHtml(title)}</h1><section class="summary"><div class="card"><strong>Events</strong><br>${summary.total}</div><div class="card"><strong>Errors</strong><br>${summary.errors}</div><div class="card"><strong>Latest</strong><br>${summary.latestAt ? escapeHtml(new Date(summary.latestAt).toLocaleString()) : "never"}</div></section><table><thead><tr><th>At</th><th>Type</th><th>Action</th><th>Outcome</th><th>Payload</th></tr></thead><tbody>${rows}</tbody></table><h2>Markdown Export</h2><pre>${escapeHtml(markdown)}</pre></main></body></html>`;
10160
+ };
10147
10161
  var renderVoiceAuditMarkdown = (events, options = {}) => {
10148
10162
  const renderEvents = options.redact ? redactVoiceAuditEvents(events, options.redact) : events;
10149
10163
  const summary = summarizeVoiceAuditTrail(renderEvents);
@@ -10165,23 +10179,6 @@ var renderVoiceAuditMarkdown = (events, options = {}) => {
10165
10179
  return lines.join(`
10166
10180
  `);
10167
10181
  };
10168
- var renderVoiceAuditHTML = (events, options = {}) => {
10169
- const title = options.title ?? "Voice Audit Trail";
10170
- const markdown = renderVoiceAuditMarkdown(events, options);
10171
- const renderEvents = options.redact ? redactVoiceAuditEvents(events, options.redact) : events;
10172
- const summary = summarizeVoiceAuditTrail(renderEvents);
10173
- const rows = renderEvents.map((event) => `<tr><td>${escapeHtml(new Date(event.at).toISOString())}</td><td>${escapeHtml(event.type)}</td><td>${escapeHtml(event.action)}</td><td>${escapeHtml(event.outcome ?? "")}</td><td><code>${escapeHtml(JSON.stringify(event.payload ?? {}))}</code></td></tr>`).join("");
10174
- return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml(title)}</title><style>body{font-family:ui-sans-serif,system-ui,sans-serif;margin:2rem;line-height:1.45;background:#f8f7f2;color:#181713}main{max-width:1100px;margin:auto}.summary{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:.75rem;margin:1rem 0}.card{background:white;border:1px solid #ded9cc;border-radius:12px;padding:1rem}table{border-collapse:collapse;width:100%;background:white;border:1px solid #ded9cc}th,td{border-bottom:1px solid #eee8dc;padding:.65rem;text-align:left;vertical-align:top}code{white-space:pre-wrap;word-break:break-word}pre{background:#181713;color:#f8f7f2;padding:1rem;border-radius:12px;overflow:auto}</style></head><body><main><h1>${escapeHtml(title)}</h1><section class="summary"><div class="card"><strong>Events</strong><br>${summary.total}</div><div class="card"><strong>Errors</strong><br>${summary.errors}</div><div class="card"><strong>Latest</strong><br>${summary.latestAt ? escapeHtml(new Date(summary.latestAt).toLocaleString()) : "never"}</div></section><table><thead><tr><th>At</th><th>Type</th><th>Action</th><th>Outcome</th><th>Payload</th></tr></thead><tbody>${rows}</tbody></table><h2>Markdown Export</h2><pre>${escapeHtml(markdown)}</pre></main></body></html>`;
10175
- };
10176
- var buildVoiceAuditExport = (events, options = {}) => {
10177
- const exportEvents = options.redact ? redactVoiceAuditEvents(events, options.redact) : events;
10178
- return {
10179
- events: exportEvents,
10180
- html: renderVoiceAuditHTML(events, options),
10181
- markdown: renderVoiceAuditMarkdown(events, options),
10182
- summary: summarizeVoiceAuditTrail(exportEvents)
10183
- };
10184
- };
10185
10182
 
10186
10183
  // src/core/sessionReplay.ts
10187
10184
  import { Elysia as Elysia2 } from "elysia";
@@ -10274,6 +10271,89 @@ var buildReplayTurns = (events) => {
10274
10271
  }
10275
10272
  return [...turns.values()];
10276
10273
  };
10274
+ var createVoiceSessionListRoutes = (options = {}) => {
10275
+ const path = options.path ?? "/api/voice-sessions";
10276
+ const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
10277
+ const routes = new Elysia2({
10278
+ name: options.name ?? "absolutejs-voice-session-list"
10279
+ }).get(path, createVoiceSessionsJSONHandler(options));
10280
+ if (htmlPath) {
10281
+ routes.get(htmlPath, createVoiceSessionsHTMLHandler(options));
10282
+ }
10283
+ return routes;
10284
+ };
10285
+ var createVoiceSessionReplayHTMLHandler = (options) => async ({ params }) => {
10286
+ const replay = await summarizeVoiceSessionReplay({
10287
+ ...options,
10288
+ sessionId: params.sessionId ?? ""
10289
+ });
10290
+ const body = await (options.render?.(replay) ?? replay.html);
10291
+ return new Response(body, {
10292
+ headers: {
10293
+ "Content-Type": "text/html; charset=utf-8",
10294
+ ...options.headers
10295
+ }
10296
+ });
10297
+ };
10298
+ var createVoiceSessionReplayJSONHandler = (options) => async ({ params }) => summarizeVoiceSessionReplay({
10299
+ ...options,
10300
+ sessionId: params.sessionId ?? ""
10301
+ });
10302
+ var createVoiceSessionReplayRoutes = (options) => {
10303
+ const path = options.path ?? "/api/voice-sessions/:sessionId/replay";
10304
+ const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
10305
+ const routes = new Elysia2({
10306
+ name: options.name ?? "absolutejs-voice-session-replay"
10307
+ }).get(path, createVoiceSessionReplayJSONHandler(options));
10308
+ if (htmlPath) {
10309
+ routes.get(htmlPath, createVoiceSessionReplayHTMLHandler(options));
10310
+ }
10311
+ return routes;
10312
+ };
10313
+ var createVoiceSessionsHTMLHandler = (options = {}) => async ({ query }) => {
10314
+ const sessions = await summarizeVoiceSessions({
10315
+ ...options,
10316
+ limit: typeof query?.limit === "string" ? Number(query.limit) : options.limit,
10317
+ provider: query?.provider ?? options.provider,
10318
+ q: query?.q ?? options.q,
10319
+ status: query?.status === "failed" || query?.status === "healthy" || query?.status === "all" ? query.status : options.status
10320
+ });
10321
+ const body = await (options.render?.(sessions) ?? renderVoiceSessionsHTML(sessions));
10322
+ return new Response(body, {
10323
+ headers: {
10324
+ "Content-Type": "text/html; charset=utf-8",
10325
+ ...options.headers
10326
+ }
10327
+ });
10328
+ };
10329
+ var createVoiceSessionsJSONHandler = (options = {}) => async ({ query }) => summarizeVoiceSessions({
10330
+ ...options,
10331
+ limit: typeof query?.limit === "string" ? Number(query.limit) : options.limit,
10332
+ provider: query?.provider ?? options.provider,
10333
+ q: query?.q ?? options.q,
10334
+ status: query?.status === "failed" || query?.status === "healthy" || query?.status === "all" ? query.status : options.status
10335
+ });
10336
+ var renderVoiceSessionsHTML = (sessions) => sessions.length === 0 ? '<p class="voice-sessions-empty">No voice sessions found.</p>' : [
10337
+ '<div class="voice-sessions-list">',
10338
+ ...sessions.map((session) => [
10339
+ `<article class="voice-session-card ${escapeHtml(session.status)}">`,
10340
+ '<div class="voice-session-card-header">',
10341
+ `<strong>${escapeHtml(session.sessionId)}</strong>`,
10342
+ `<span>${escapeHtml(session.status)}</span>`,
10343
+ "</div>",
10344
+ "<dl>",
10345
+ `<div><dt>Events</dt><dd>${String(session.eventCount)}</dd></div>`,
10346
+ `<div><dt>Turns</dt><dd>${String(session.turnCount)}</dd></div>`,
10347
+ `<div><dt>Transcripts</dt><dd>${String(session.transcriptCount)}</dd></div>`,
10348
+ `<div><dt>Errors</dt><dd>${String(session.errorCount)}</dd></div>`,
10349
+ "</dl>",
10350
+ session.latestOutcome ? `<p>Outcome: ${escapeHtml(session.latestOutcome)}</p>` : "",
10351
+ session.providers.length ? `<p>Providers: ${session.providers.map(escapeHtml).join(", ")}</p>` : "",
10352
+ session.replayHref ? `<p>${session.operationsRecordHref ? `<a href="${escapeHtml(session.operationsRecordHref)}">Open operations record</a> \xB7 ` : ""}<a href="${escapeHtml(session.replayHref)}">Open replay</a></p>` : "",
10353
+ "</article>"
10354
+ ].join("")),
10355
+ "</div>"
10356
+ ].join("");
10277
10357
  var summarizeVoiceSessionReplay = async (options) => {
10278
10358
  const sourceEvents = options.events ?? await options.store?.list({ sessionId: options.sessionId }) ?? [];
10279
10359
  const events = filterVoiceTraceEvents(sourceEvents, {
@@ -10284,7 +10364,7 @@ var summarizeVoiceSessionReplay = async (options) => {
10284
10364
  redact: options.redact,
10285
10365
  title: options.title ?? `Voice Session ${options.sessionId}`
10286
10366
  });
10287
- const startedAt = replay.summary.startedAt;
10367
+ const { startedAt } = replay.summary;
10288
10368
  return {
10289
10369
  evaluation: replay.evaluation,
10290
10370
  events,
@@ -10313,14 +10393,14 @@ var summarizeVoiceSessions = async (options = {}) => {
10313
10393
  }
10314
10394
  const sessions = [...grouped.entries()].map(([sessionId, sessionEvents]) => {
10315
10395
  const sorted = filterVoiceTraceEvents(sessionEvents);
10316
- const summary = buildVoiceTraceReplay(sorted, {
10396
+ const { summary } = buildVoiceTraceReplay(sorted, {
10317
10397
  evaluation: {
10318
10398
  requireAssistantReply: false,
10319
10399
  requireCompletedCall: false,
10320
10400
  requireTranscript: false,
10321
10401
  requireTurn: false
10322
10402
  }
10323
- }).summary;
10403
+ });
10324
10404
  const providerErrors = {};
10325
10405
  const providers = new Set;
10326
10406
  let latestOutcome;
@@ -10337,130 +10417,47 @@ var summarizeVoiceSessions = async (options = {}) => {
10337
10417
  }
10338
10418
  const outcome = getString2(event.payload.outcome);
10339
10419
  if (outcome) {
10340
- latestOutcome = outcome;
10341
- }
10342
- }
10343
- const item = {
10344
- endedAt: summary.endedAt,
10345
- errorCount,
10346
- eventCount: summary.eventCount,
10347
- latestOutcome,
10348
- providerErrors,
10349
- providers: [...providers].sort(),
10350
- sessionId,
10351
- startedAt: summary.startedAt,
10352
- status: errorCount > 0 ? "failed" : "healthy",
10353
- transcriptCount: summary.transcriptCount,
10354
- turnCount: summary.turnCount
10355
- };
10356
- const replayHref = options.replayHref === false ? "" : typeof options.replayHref === "function" ? options.replayHref(item) : `${options.replayHref ?? "/api/voice-sessions"}/${encodeURIComponent(sessionId)}/replay/htmx`;
10357
- return {
10358
- ...item,
10359
- operationsRecordHref: resolveSessionHref(options.operationsRecordHref, item),
10360
- replayHref
10361
- };
10362
- });
10363
- const search = options.q?.trim().toLowerCase();
10364
- return sessions.filter((session) => {
10365
- if (options.status && options.status !== "all" && session.status !== options.status) {
10366
- return false;
10367
- }
10368
- if (options.provider && !session.providers.includes(options.provider)) {
10369
- return false;
10370
- }
10371
- if (!search) {
10372
- return true;
10373
- }
10374
- return [
10375
- session.sessionId,
10376
- session.latestOutcome,
10377
- session.status,
10378
- ...session.providers
10379
- ].some((value) => value?.toLowerCase().includes(search));
10380
- }).sort((left, right) => (right.endedAt ?? right.startedAt ?? 0) - (left.endedAt ?? left.startedAt ?? 0)).slice(0, options.limit ?? 50);
10381
- };
10382
- var renderVoiceSessionsHTML = (sessions) => sessions.length === 0 ? '<p class="voice-sessions-empty">No voice sessions found.</p>' : [
10383
- '<div class="voice-sessions-list">',
10384
- ...sessions.map((session) => [
10385
- `<article class="voice-session-card ${escapeHtml(session.status)}">`,
10386
- '<div class="voice-session-card-header">',
10387
- `<strong>${escapeHtml(session.sessionId)}</strong>`,
10388
- `<span>${escapeHtml(session.status)}</span>`,
10389
- "</div>",
10390
- "<dl>",
10391
- `<div><dt>Events</dt><dd>${String(session.eventCount)}</dd></div>`,
10392
- `<div><dt>Turns</dt><dd>${String(session.turnCount)}</dd></div>`,
10393
- `<div><dt>Transcripts</dt><dd>${String(session.transcriptCount)}</dd></div>`,
10394
- `<div><dt>Errors</dt><dd>${String(session.errorCount)}</dd></div>`,
10395
- "</dl>",
10396
- session.latestOutcome ? `<p>Outcome: ${escapeHtml(session.latestOutcome)}</p>` : "",
10397
- session.providers.length ? `<p>Providers: ${session.providers.map(escapeHtml).join(", ")}</p>` : "",
10398
- session.replayHref ? `<p>${session.operationsRecordHref ? `<a href="${escapeHtml(session.operationsRecordHref)}">Open operations record</a> \xB7 ` : ""}<a href="${escapeHtml(session.replayHref)}">Open replay</a></p>` : "",
10399
- "</article>"
10400
- ].join("")),
10401
- "</div>"
10402
- ].join("");
10403
- var createVoiceSessionsJSONHandler = (options = {}) => async ({ query }) => summarizeVoiceSessions({
10404
- ...options,
10405
- limit: typeof query?.limit === "string" ? Number(query.limit) : options.limit,
10406
- provider: query?.provider ?? options.provider,
10407
- q: query?.q ?? options.q,
10408
- status: query?.status === "failed" || query?.status === "healthy" || query?.status === "all" ? query.status : options.status
10409
- });
10410
- var createVoiceSessionsHTMLHandler = (options = {}) => async ({ query }) => {
10411
- const sessions = await summarizeVoiceSessions({
10412
- ...options,
10413
- limit: typeof query?.limit === "string" ? Number(query.limit) : options.limit,
10414
- provider: query?.provider ?? options.provider,
10415
- q: query?.q ?? options.q,
10416
- status: query?.status === "failed" || query?.status === "healthy" || query?.status === "all" ? query.status : options.status
10417
- });
10418
- const body = await (options.render?.(sessions) ?? renderVoiceSessionsHTML(sessions));
10419
- return new Response(body, {
10420
- headers: {
10421
- "Content-Type": "text/html; charset=utf-8",
10422
- ...options.headers
10420
+ latestOutcome = outcome;
10421
+ }
10423
10422
  }
10423
+ const item = {
10424
+ endedAt: summary.endedAt,
10425
+ errorCount,
10426
+ eventCount: summary.eventCount,
10427
+ latestOutcome,
10428
+ providerErrors,
10429
+ providers: [...providers].sort(),
10430
+ sessionId,
10431
+ startedAt: summary.startedAt,
10432
+ status: errorCount > 0 ? "failed" : "healthy",
10433
+ transcriptCount: summary.transcriptCount,
10434
+ turnCount: summary.turnCount
10435
+ };
10436
+ const replayHref = options.replayHref === false ? "" : typeof options.replayHref === "function" ? options.replayHref(item) : `${options.replayHref ?? "/api/voice-sessions"}/${encodeURIComponent(sessionId)}/replay/htmx`;
10437
+ return {
10438
+ ...item,
10439
+ operationsRecordHref: resolveSessionHref(options.operationsRecordHref, item),
10440
+ replayHref
10441
+ };
10424
10442
  });
10425
- };
10426
- var createVoiceSessionListRoutes = (options = {}) => {
10427
- const path = options.path ?? "/api/voice-sessions";
10428
- const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
10429
- const routes = new Elysia2({
10430
- name: options.name ?? "absolutejs-voice-session-list"
10431
- }).get(path, createVoiceSessionsJSONHandler(options));
10432
- if (htmlPath) {
10433
- routes.get(htmlPath, createVoiceSessionsHTMLHandler(options));
10434
- }
10435
- return routes;
10436
- };
10437
- var createVoiceSessionReplayJSONHandler = (options) => async ({ params }) => summarizeVoiceSessionReplay({
10438
- ...options,
10439
- sessionId: params.sessionId ?? ""
10440
- });
10441
- var createVoiceSessionReplayHTMLHandler = (options) => async ({ params }) => {
10442
- const replay = await summarizeVoiceSessionReplay({
10443
- ...options,
10444
- sessionId: params.sessionId ?? ""
10445
- });
10446
- const body = await (options.render?.(replay) ?? replay.html);
10447
- return new Response(body, {
10448
- headers: {
10449
- "Content-Type": "text/html; charset=utf-8",
10450
- ...options.headers
10443
+ const search = options.q?.trim().toLowerCase();
10444
+ return sessions.filter((session) => {
10445
+ if (options.status && options.status !== "all" && session.status !== options.status) {
10446
+ return false;
10451
10447
  }
10452
- });
10453
- };
10454
- var createVoiceSessionReplayRoutes = (options) => {
10455
- const path = options.path ?? "/api/voice-sessions/:sessionId/replay";
10456
- const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
10457
- const routes = new Elysia2({
10458
- name: options.name ?? "absolutejs-voice-session-replay"
10459
- }).get(path, createVoiceSessionReplayJSONHandler(options));
10460
- if (htmlPath) {
10461
- routes.get(htmlPath, createVoiceSessionReplayHTMLHandler(options));
10462
- }
10463
- return routes;
10448
+ if (options.provider && !session.providers.includes(options.provider)) {
10449
+ return false;
10450
+ }
10451
+ if (!search) {
10452
+ return true;
10453
+ }
10454
+ return [
10455
+ session.sessionId,
10456
+ session.latestOutcome,
10457
+ session.status,
10458
+ ...session.providers
10459
+ ].some((value) => value?.toLowerCase().includes(search));
10460
+ }).sort((left, right) => (right.endedAt ?? right.startedAt ?? 0) - (left.endedAt ?? left.startedAt ?? 0)).slice(0, options.limit ?? 50);
10464
10461
  };
10465
10462
 
10466
10463
  // src/core/traceTimeline.ts
@@ -10659,35 +10656,6 @@ var renderVoiceTraceTimelineSessionHTML = (session, options = {}) => {
10659
10656
  };
10660
10657
  var renderSessionRows = (report) => report.sessions.length === 0 ? '<tr><td colspan="7">No trace events recorded yet.</td></tr>' : report.sessions.map((session) => `<tr class="${escapeHtml(session.status)}"><td>${session.operationsRecordHref ? `<a href="${escapeHtml(session.operationsRecordHref)}">${escapeHtml(session.sessionId)}</a>` : `<a href="/traces/${encodeURIComponent(session.sessionId)}">${escapeHtml(session.sessionId)}</a>`}</td><td>${escapeHtml(session.status)}</td><td>${String(session.summary.eventCount)}</td><td>${String(session.summary.turnCount)}</td><td>${String(session.summary.errorCount)}</td><td>${formatMs(session.summary.callDurationMs)}</td><td>${session.providers.map((provider) => escapeHtml(provider.provider)).join(", ")}</td></tr>`).join("");
10661
10658
  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}}";
10662
- var renderVoiceTraceTimelineHTML = (report, options = {}) => {
10663
- const snippet = escapeHtml(`const traceStore = createVoiceTraceSinkStore({
10664
- store: runtimeStorage.traces,
10665
- sinks: [
10666
- createVoiceTraceHTTPSink({
10667
- endpoint: process.env.VOICE_TRACE_WEBHOOK_URL
10668
- })
10669
- ]
10670
- });
10671
-
10672
- app.use(
10673
- createVoiceTraceTimelineRoutes({
10674
- htmlPath: '/traces',
10675
- path: '/api/voice-traces',
10676
- redact: {
10677
- keys: ['authorization', 'apiKey', 'token']
10678
- },
10679
- store: traceStore
10680
- })
10681
- );
10682
-
10683
- app.use(
10684
- createVoiceProductionReadinessRoutes({
10685
- store: traceStore,
10686
- traceDeliveries: runtimeStorage.traceDeliveries
10687
- })
10688
- );`);
10689
- return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml(options.title ?? "Voice Trace Timelines")}</title><style>${timelineCSS}.primitive{background:#181f27;border:1px solid #334155;border-radius:20px;margin:20px 0;padding:18px}.primitive p{line-height:1.55}.primitive pre{background:#0b1118;border:1px solid #2b3642;border-radius:16px;color:#dbeafe;overflow:auto;padding:14px}.primitive code{color:#bfdbfe}</style></head><body><main><header><p class="eyebrow">Self-hosted voice debugging</p><h1>${escapeHtml(options.title ?? "Voice Trace Timelines")}</h1><p class="muted">Per-call event timelines with provider latency, fallback, timeout, handoff, and error context.</p></header><section class="metrics"><article><span>Sessions</span><strong>${String(report.total)}</strong></article><article><span>Failed</span><strong>${String(report.failed)}</strong></article><article><span>Warnings</span><strong>${String(report.warnings)}</strong></article></section><section class="primitive"><p class="eyebrow">Copy into your app</p><h2><code>createVoiceTraceTimelineRoutes(...)</code> makes traces the proof backbone</h2><p class="muted">Mount trace timelines from the same trace store used by readiness, simulations, provider recovery, delivery sinks, and phone-agent smoke proof.</p><pre><code>${snippet}</code></pre></section><table><thead><tr><th>Session</th><th>Status</th><th>Events</th><th>Turns</th><th>Errors</th><th>Duration</th><th>Providers</th></tr></thead><tbody>${renderSessionRows(report)}</tbody></table></main></body></html>`;
10690
- };
10691
10659
  var createVoiceTraceTimelineRoutes = (options) => {
10692
10660
  const path = options.path ?? "/api/voice-traces";
10693
10661
  const htmlPath = options.htmlPath ?? "/traces";
@@ -10740,6 +10708,35 @@ var createVoiceTraceTimelineRoutes = (options) => {
10740
10708
  });
10741
10709
  return routes;
10742
10710
  };
10711
+ var renderVoiceTraceTimelineHTML = (report, options = {}) => {
10712
+ const snippet = escapeHtml(`const traceStore = createVoiceTraceSinkStore({
10713
+ store: runtimeStorage.traces,
10714
+ sinks: [
10715
+ createVoiceTraceHTTPSink({
10716
+ endpoint: process.env.VOICE_TRACE_WEBHOOK_URL
10717
+ })
10718
+ ]
10719
+ });
10720
+
10721
+ app.use(
10722
+ createVoiceTraceTimelineRoutes({
10723
+ htmlPath: '/traces',
10724
+ path: '/api/voice-traces',
10725
+ redact: {
10726
+ keys: ['authorization', 'apiKey', 'token']
10727
+ },
10728
+ store: traceStore
10729
+ })
10730
+ );
10731
+
10732
+ app.use(
10733
+ createVoiceProductionReadinessRoutes({
10734
+ store: traceStore,
10735
+ traceDeliveries: runtimeStorage.traceDeliveries
10736
+ })
10737
+ );`);
10738
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml(options.title ?? "Voice Trace Timelines")}</title><style>${timelineCSS}.primitive{background:#181f27;border:1px solid #334155;border-radius:20px;margin:20px 0;padding:18px}.primitive p{line-height:1.55}.primitive pre{background:#0b1118;border:1px solid #2b3642;border-radius:16px;color:#dbeafe;overflow:auto;padding:14px}.primitive code{color:#bfdbfe}</style></head><body><main><header><p class="eyebrow">Self-hosted voice debugging</p><h1>${escapeHtml(options.title ?? "Voice Trace Timelines")}</h1><p class="muted">Per-call event timelines with provider latency, fallback, timeout, handoff, and error context.</p></header><section class="metrics"><article><span>Sessions</span><strong>${String(report.total)}</strong></article><article><span>Failed</span><strong>${String(report.failed)}</strong></article><article><span>Warnings</span><strong>${String(report.warnings)}</strong></article></section><section class="primitive"><p class="eyebrow">Copy into your app</p><h2><code>createVoiceTraceTimelineRoutes(...)</code> makes traces the proof backbone</h2><p class="muted">Mount trace timelines from the same trace store used by readiness, simulations, provider recovery, delivery sinks, and phone-agent smoke proof.</p><pre><code>${snippet}</code></pre></section><table><thead><tr><th>Session</th><th>Status</th><th>Events</th><th>Turns</th><th>Errors</th><th>Duration</th><th>Providers</th></tr></thead><tbody>${renderSessionRows(report)}</tbody></table></main></body></html>`;
10739
+ };
10743
10740
 
10744
10741
  // src/core/operationsRecord.ts
10745
10742
  var getString4 = (value) => typeof value === "string" ? value : undefined;
@@ -10918,8 +10915,8 @@ var toProviderDecision = (event) => {
10918
10915
  selectedProvider: getString4(event.payload.selectedProvider),
10919
10916
  status,
10920
10917
  surface: getString4(event.payload.surface),
10921
- type: event.type,
10922
- turnId: event.turnId
10918
+ turnId: event.turnId,
10919
+ type: event.type
10923
10920
  };
10924
10921
  };
10925
10922
  var summarizeProviderDecisions = (decisions) => {
@@ -10963,6 +10960,20 @@ var resolveOutcome = (events) => {
10963
10960
  voicemail: agentResults.some((event) => event.payload.voicemail === true)
10964
10961
  };
10965
10962
  };
10963
+ var assertVoiceOperationsRecordGuardrails = (record, input = {}) => {
10964
+ const report = evaluateVoiceOperationsRecordGuardrails(record, input);
10965
+ if (!report.ok) {
10966
+ throw new Error(`Voice operations record guardrail assertion failed for ${record.sessionId}: ${report.issues.join(" ")}`);
10967
+ }
10968
+ return report;
10969
+ };
10970
+ var assertVoiceOperationsRecordProviderRecovery = (record, input = {}) => {
10971
+ const report = evaluateVoiceOperationsRecordProviderRecovery(record, input);
10972
+ if (!report.ok) {
10973
+ throw new Error(`Voice operations record provider recovery assertion failed for ${record.sessionId}: ${report.issues.join(" ")}`);
10974
+ }
10975
+ return report;
10976
+ };
10966
10977
  var buildVoiceOperationsRecord = async (options) => {
10967
10978
  const sourceEvents = options.events ?? await options.store?.list({ sessionId: options.sessionId }) ?? [];
10968
10979
  const rawTraceEvents = filterVoiceTraceEvents(sourceEvents, {
@@ -11039,7 +11050,7 @@ var buildVoiceOperationsRecord = async (options) => {
11039
11050
  };
11040
11051
  var evaluateVoiceOperationsRecordGuardrails = (record, input = {}) => {
11041
11052
  const issues = [];
11042
- const decisions = record.guardrails.decisions;
11053
+ const { decisions } = record.guardrails;
11043
11054
  const proofs = uniqueSorted(decisions.map((decision) => decision.proof));
11044
11055
  const ruleIds = uniqueSorted(decisions.flatMap((decision) => decision.findings.map((finding) => finding.ruleId)));
11045
11056
  const stages = uniqueSorted(decisions.map((decision) => decision.stage));
@@ -11102,13 +11113,6 @@ var evaluateVoiceOperationsRecordGuardrails = (record, input = {}) => {
11102
11113
  warned: record.guardrails.warned
11103
11114
  };
11104
11115
  };
11105
- var assertVoiceOperationsRecordGuardrails = (record, input = {}) => {
11106
- const report = evaluateVoiceOperationsRecordGuardrails(record, input);
11107
- if (!report.ok) {
11108
- throw new Error(`Voice operations record guardrail assertion failed for ${record.sessionId}: ${report.issues.join(" ")}`);
11109
- }
11110
- return report;
11111
- };
11112
11116
  var evaluateVoiceOperationsRecordProviderRecovery = (record, input = {}) => {
11113
11117
  const issues = [];
11114
11118
  const summary = record.providerDecisionSummary;
@@ -11195,13 +11199,6 @@ var evaluateVoiceOperationsRecordProviderRecovery = (record, input = {}) => {
11195
11199
  total: summary.total
11196
11200
  };
11197
11201
  };
11198
- var assertVoiceOperationsRecordProviderRecovery = (record, input = {}) => {
11199
- const report = evaluateVoiceOperationsRecordProviderRecovery(record, input);
11200
- if (!report.ok) {
11201
- throw new Error(`Voice operations record provider recovery assertion failed for ${record.sessionId}: ${report.issues.join(" ")}`);
11202
- }
11203
- return report;
11204
- };
11205
11202
  var getAssistantRepliesForTurn = (record, turnId) => turnId ? record.transcript.find((turn) => turn.id === turnId)?.assistantReplies ?? [] : [];
11206
11203
  var mediaIssueForStep = (step) => {
11207
11204
  const event = step.event.toLowerCase();
@@ -11356,14 +11353,141 @@ var renderVoiceFailureReplayMarkdown = (report) => {
11356
11353
  ].join(`
11357
11354
  `);
11358
11355
  };
11359
- var formatMs2 = (value) => value === undefined ? "n/a" : `${String(value)}ms`;
11360
- var outcomeLabels = (outcome) => [
11361
- outcome.complete ? "complete" : undefined,
11362
- outcome.escalated ? "escalated" : undefined,
11363
- outcome.transferred ? "transferred" : undefined,
11364
- outcome.voicemail ? "voicemail" : undefined,
11365
- outcome.noAnswer ? "no-answer" : undefined
11366
- ].filter((label) => label !== undefined);
11356
+ var formatMs2 = (value) => value === undefined ? "n/a" : `${String(value)}ms`;
11357
+ var outcomeLabels = (outcome) => [
11358
+ outcome.complete ? "complete" : undefined,
11359
+ outcome.escalated ? "escalated" : undefined,
11360
+ outcome.transferred ? "transferred" : undefined,
11361
+ outcome.voicemail ? "voicemail" : undefined,
11362
+ outcome.noAnswer ? "no-answer" : undefined
11363
+ ].filter((label) => label !== undefined);
11364
+ var createVoiceOperationsRecordRoutes = (options) => {
11365
+ const path = options.path ?? "/api/voice-operations/:sessionId";
11366
+ const htmlPath = options.htmlPath === undefined ? "/voice-operations/:sessionId" : options.htmlPath;
11367
+ const incidentPath = options.incidentPath === undefined ? `${path}/incident.md` : options.incidentPath;
11368
+ const incidentHtmlPath = options.incidentHtmlPath === undefined && htmlPath ? `${htmlPath}/incident.md` : options.incidentHtmlPath;
11369
+ const routes = new Elysia4({
11370
+ name: options.name ?? "absolutejs-voice-operations-record"
11371
+ });
11372
+ const resolveMediaPipeline = async (sessionId) => {
11373
+ if (options.mediaPipeline === undefined)
11374
+ return;
11375
+ return typeof options.mediaPipeline === "function" ? await options.mediaPipeline({ sessionId }) : options.mediaPipeline;
11376
+ };
11377
+ const buildRecord = async (sessionId) => buildVoiceOperationsRecord({
11378
+ audit: options.audit,
11379
+ evaluation: options.evaluation,
11380
+ events: options.events,
11381
+ integrationEvents: options.integrationEvents,
11382
+ mediaPipeline: await resolveMediaPipeline(sessionId),
11383
+ redact: options.redact,
11384
+ reviews: options.reviews,
11385
+ sessionId,
11386
+ store: options.store,
11387
+ tasks: options.tasks
11388
+ });
11389
+ const getSessionId = (params) => params.sessionId ?? "";
11390
+ routes.get(path, async ({ params }) => Response.json(await buildRecord(getSessionId(params))));
11391
+ const incidentHandler = async ({
11392
+ params
11393
+ }) => {
11394
+ const record = await buildRecord(getSessionId(params));
11395
+ const body = await (options.renderIncidentMarkdown ?? renderVoiceOperationsRecordIncidentMarkdown)(record);
11396
+ return new Response(body, {
11397
+ headers: {
11398
+ "Content-Type": "text/markdown; charset=utf-8",
11399
+ ...options.headers
11400
+ }
11401
+ });
11402
+ };
11403
+ if (incidentPath) {
11404
+ routes.get(incidentPath, incidentHandler);
11405
+ }
11406
+ if (htmlPath) {
11407
+ routes.get(htmlPath, async ({ params }) => {
11408
+ const record = await buildRecord(getSessionId(params));
11409
+ const body = await (options.render ?? ((input) => renderVoiceOperationsRecordHTML(input, {
11410
+ incidentHref: incidentHtmlPath ? resolveRoutePath(incidentHtmlPath, input.sessionId) : undefined,
11411
+ title: options.title
11412
+ })))(record);
11413
+ return new Response(body, {
11414
+ headers: {
11415
+ "Content-Type": "text/html; charset=utf-8",
11416
+ ...options.headers
11417
+ }
11418
+ });
11419
+ });
11420
+ }
11421
+ if (incidentHtmlPath && incidentHtmlPath !== incidentPath) {
11422
+ routes.get(incidentHtmlPath, incidentHandler);
11423
+ }
11424
+ return routes;
11425
+ };
11426
+ var renderVoiceOperationsRecordGuardrailMarkdown = (record) => {
11427
+ if (record.guardrails.total === 0) {
11428
+ return [
11429
+ "## Guardrail evidence",
11430
+ "",
11431
+ "- No assistant.guardrail events were recorded for this session."
11432
+ ].join(`
11433
+ `);
11434
+ }
11435
+ return [
11436
+ "## Guardrail evidence",
11437
+ "",
11438
+ ...record.guardrails.decisions.map((decision) => {
11439
+ const findings = decision.findings.map((finding) => [finding.action, finding.ruleId, finding.label].filter((value) => typeof value === "string").join(":")).filter(Boolean).join(", ");
11440
+ return `- assistant.guardrail ${decision.stage ?? "unknown"}: ${decision.status ?? "unknown"}; allowed=${String(decision.allowed ?? "unknown")}; proof=${decision.proof ?? "runtime"}; findings=${findings || "none"}`;
11441
+ })
11442
+ ].join(`
11443
+ `);
11444
+ };
11445
+ var renderVoiceOperationsRecordHTML = (record, options = {}) => {
11446
+ const providers = record.providers.length ? record.providers.map((provider) => `<article><strong>${escapeHtml(provider.provider)}</strong><span>${String(provider.eventCount)} events</span><span>${formatMs2(provider.averageElapsedMs)} avg</span><span>${String(provider.errorCount)} errors</span></article>`).join("") : '<p class="muted">No provider events recorded.</p>';
11447
+ const transcript = record.transcript.length ? record.transcript.map((turn) => `<li><strong>${escapeHtml(turn.id)}</strong>${turn.committedText ? `<p><span class="label">Caller</span>${escapeHtml(turn.committedText)}</p>` : ""}${turn.assistantReplies.map((reply) => `<p><span class="label">Assistant</span>${escapeHtml(reply)}</p>`).join("")}${turn.errors.map((error) => `<p class="error"><span class="label">Error</span>${escapeHtml(error)}</p>`).join("")}</li>`).join("") : "<li>No transcript turns recorded.</li>";
11448
+ const providerDecisions = record.providerDecisions.length ? record.providerDecisions.map((decision) => `<li><strong>${escapeHtml(decision.provider ?? decision.selectedProvider ?? decision.fallbackProvider ?? "provider")}</strong> <span>${escapeHtml(decision.status ?? decision.type)}</span> ${formatMs2(decision.elapsedMs)}${decision.surface ? `<p><span class="label">Surface</span>${escapeHtml(decision.surface)}</p>` : ""}${decision.kind ? `<p><span class="label">Kind</span>${escapeHtml(decision.kind)}</p>` : ""}${decision.selectedProvider ? `<p>Selected: ${escapeHtml(decision.selectedProvider)}</p>` : ""}${decision.fallbackProvider ? `<p>Fallback: ${escapeHtml(decision.fallbackProvider)}</p>` : ""}${decision.error ? `<p class="error">${escapeHtml(decision.error)}</p>` : ""}${decision.reason ? `<p>${escapeHtml(decision.reason)}</p>` : ""}</li>`).join("") : "<li>No provider decisions recorded.</li>";
11449
+ const { providerDecisionSummary } = record;
11450
+ const handoffs = record.handoffs.length ? record.handoffs.map((handoff) => `<li><strong>${escapeHtml(handoff.fromAgentId ?? "unknown")}</strong> to <strong>${escapeHtml(handoff.targetAgentId ?? "unknown")}</strong> <span>${escapeHtml(handoff.status ?? "")}</span><p>${escapeHtml(handoff.summary ?? handoff.reason ?? "")}</p></li>`).join("") : "<li>No agent handoffs recorded.</li>";
11451
+ const tools = record.tools.length ? record.tools.map((tool) => `<li><strong>${escapeHtml(tool.toolName ?? "tool")}</strong> <span>${escapeHtml(tool.status ?? "")}</span> ${formatMs2(tool.elapsedMs)} ${tool.error ? `<p>${escapeHtml(tool.error)}</p>` : ""}</li>`).join("") : "<li>No tool calls recorded.</li>";
11452
+ const reviews = record.reviews?.reviews.length ? record.reviews.reviews.map((review) => `<li><strong>${escapeHtml(review.title)}</strong> <span>${escapeHtml(review.summary.outcome ?? "")}</span><p>${escapeHtml(review.postCall?.summary ?? review.transcript.actual)}</p></li>`).join("") : "<li>No call reviews recorded.</li>";
11453
+ const tasks = record.tasks?.tasks.length ? record.tasks.tasks.map((task) => `<li><strong>${escapeHtml(task.title)}</strong> <span>${escapeHtml(task.status)}</span><p>${escapeHtml(task.recommendedAction)}</p></li>`).join("") : "<li>No ops tasks recorded.</li>";
11454
+ const integrationEvents = record.integrationEvents?.events.length ? record.integrationEvents.events.map((event) => `<li><strong>${escapeHtml(event.type)}</strong> <span>${escapeHtml(event.deliveryStatus ?? "local")}</span><p>${escapeHtml(event.deliveryError ?? event.deliveredTo ?? "")}</p></li>`).join("") : "<li>No integration events recorded.</li>";
11455
+ const guardrails = record.guardrails.total ? record.guardrails.decisions.map((decision) => {
11456
+ const findings = decision.findings.map((finding) => finding.label ?? finding.ruleId ?? finding.action).filter((value) => typeof value === "string").join(", ") || "none";
11457
+ return `<li><strong>assistant.guardrail ${escapeHtml(decision.stage ?? "unknown")}</strong> <span>${escapeHtml(decision.status ?? "")}</span><p>Allowed: ${escapeHtml(String(decision.allowed ?? "unknown"))} \xB7 Proof: ${escapeHtml(decision.proof ?? "runtime")}${decision.turnId ? ` \xB7 Turn: ${escapeHtml(decision.turnId)}` : ""}</p><p>${escapeHtml(findings)}</p></li>`;
11458
+ }).join("") : "<li>No assistant.guardrail events recorded.</li>";
11459
+ const telephonyMedia = record.telephonyMedia.events.length ? record.telephonyMedia.events.slice(0, 50).map((event) => {
11460
+ const details = [
11461
+ event.carrier ? `Carrier: ${event.carrier}` : undefined,
11462
+ event.streamId ? `Stream: ${event.streamId}` : undefined,
11463
+ event.callSid ? `Call: ${event.callSid}` : undefined,
11464
+ event.direction ? `Direction: ${event.direction}` : undefined,
11465
+ event.sequenceNumber ? `Seq: ${event.sequenceNumber}` : undefined,
11466
+ `Audio bytes: ${String(event.audioBytes)}`
11467
+ ].filter((detail) => typeof detail === "string");
11468
+ return `<li><strong>${escapeHtml(event.event)}</strong> <span>${escapeHtml(new Date(event.at).toLocaleString())}</span><p>${escapeHtml(details.join(" \xB7 "))}</p></li>`;
11469
+ }).join("") : "<li>No telephony media trace events recorded.</li>";
11470
+ const mediaPipelineSection = record.mediaPipeline ? `<section id="media-pipeline"><h2>Media Pipeline</h2><p class="muted">Surface: ${escapeHtml(record.mediaPipeline.surface)} \xB7 Status: ${escapeHtml(record.mediaPipeline.status)} \xB7 Quality: ${escapeHtml(record.mediaPipeline.qualityStatus)} \xB7 Transport: ${escapeHtml(record.mediaPipeline.transportStatus ?? "n/a")} \xB7 Graph: ${escapeHtml(record.mediaPipeline.processorGraphStatus ?? "n/a")} \xB7 Frames: ${String(record.mediaPipeline.frames)} \xB7 Jitter: ${record.mediaPipeline.jitterMs === undefined ? "n/a" : `${String(record.mediaPipeline.jitterMs)}ms`}</p><ul>${record.mediaPipeline.issueCodes.length ? record.mediaPipeline.issueCodes.map((code) => `<li><strong>${escapeHtml(code)}</strong></li>`).join("") : "<li>No media pipeline issue codes.</li>"}</ul></section>` : "";
11471
+ const mediaPipelineCard = record.mediaPipeline ? `<div class="card"><span>Media pipeline</span><strong>${escapeHtml(record.mediaPipeline.status)}</strong><span>${String(record.mediaPipeline.issueCodes.length)} issue code(s)</span></div>` : "";
11472
+ const mediaPipelineNavLink = record.mediaPipeline ? '<a href="#media-pipeline">Media pipeline</a>' : "";
11473
+ const snippet = escapeHtml(`app.use(
11474
+ createVoiceOperationsRecordRoutes({
11475
+ audit: auditStore,
11476
+ integrationEvents: opsEvents,
11477
+ htmlPath: '/voice-ops/:sessionId',
11478
+ path: '/api/voice-ops/:sessionId',
11479
+ redact: {
11480
+ keys: ['authorization', 'apiKey', 'token']
11481
+ },
11482
+ reviews: callReviews,
11483
+ store: traceStore,
11484
+ tasks: opsTasks
11485
+ })
11486
+ );`);
11487
+ const incidentMarkdown = escapeHtml(renderVoiceOperationsRecordIncidentMarkdown(record));
11488
+ const incidentLink = options.incidentHref ? `<a href="${escapeHtml(options.incidentHref)}">Download incident.md</a>` : "";
11489
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml(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>${escapeHtml(options.title ?? "Voice Operations Record")}</h1><p class="status ${escapeHtml(record.status)}">${escapeHtml(record.status)}</p><div class="hero-actions"><a href="#transcript">Transcript</a><a href="#provider-decisions">Provider decisions</a><a href="#telephony-media">Telephony media</a>${mediaPipelineNavLink}<a href="#guardrails">Guardrails</a><a href="#incident-handoff">Incident handoff</a>${incidentLink}</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>${formatMs2(record.summary.callDurationMs)}</strong></div><div class="card"><span>Provider recovery</span><strong>${escapeHtml(providerDecisionSummary.recoveryStatus)}</strong><span>${String(providerDecisionSummary.fallbacks)} fallback / ${String(providerDecisionSummary.degraded)} degraded / ${String(providerDecisionSummary.errors)} errors</span></div><div class="card"><span>Telephony media</span><strong>${String(record.telephonyMedia.media)}</strong><span>${String(record.telephonyMedia.inbound)} inbound / ${String(record.telephonyMedia.outbound)} outbound / ${String(record.telephonyMedia.clears)} clears</span></div><div class="card"><span>Guardrails</span><strong>${String(record.guardrails.blocked)}</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>${mediaPipelineCard}</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="telephony-media"><h2>Telephony Media</h2><p class="muted">Live <code>client.telephony_media</code> stream lifecycle evidence attached to this session. Carriers: ${escapeHtml(record.telephonyMedia.carriers.join(", ") || "none")}. Streams: ${escapeHtml(record.telephonyMedia.streamIds.join(", ") || "none")}. Inbound: ${String(record.telephonyMedia.inbound)}. Outbound: ${String(record.telephonyMedia.outbound)}. Marks: ${String(record.telephonyMedia.marks)}. Clears: ${String(record.telephonyMedia.clears)}.</p><ul>${telephonyMedia}</ul></section>${mediaPipelineSection}<section id="guardrails"><h2>Guardrail Evidence</h2><p class="muted">Live <code>assistant.guardrail</code> decisions attached to this session.</p><ul>${guardrails}</ul></section><section id="incident-handoff"><h2>Copyable Incident Handoff</h2><p class="muted">Paste this into Slack, Linear, Zendesk, or an incident review. ${incidentLink}</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, guardrails, audit, latency, replay, reviews, tasks, media streams, 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>`;
11490
+ };
11367
11491
  var renderVoiceOperationsRecordIncidentMarkdown = (record) => {
11368
11492
  const outcomes = outcomeLabels(record.outcome);
11369
11493
  const topErrors = record.traceEvents.filter((event) => event.type === "session.error").map((event) => getString4(event.payload.error)).filter((error) => typeof error === "string").slice(0, 3);
@@ -11380,7 +11504,7 @@ var renderVoiceOperationsRecordIncidentMarkdown = (record) => {
11380
11504
  ].filter((part) => typeof part === "string");
11381
11505
  return `- ${provider}: ${parts.join("; ") || "decision recorded"}`;
11382
11506
  }) : ["- none recorded"];
11383
- const providerDecisionSummary = record.providerDecisionSummary;
11507
+ const { providerDecisionSummary } = record;
11384
11508
  const providerRecoveryLine = [
11385
11509
  `status=${providerDecisionSummary.recoveryStatus}`,
11386
11510
  `selected=${String(providerDecisionSummary.selected)}`,
@@ -11461,133 +11585,6 @@ var renderVoiceOperationsRecordIncidentMarkdown = (record) => {
11461
11585
  ].join(`
11462
11586
  `);
11463
11587
  };
11464
- var renderVoiceOperationsRecordGuardrailMarkdown = (record) => {
11465
- if (record.guardrails.total === 0) {
11466
- return [
11467
- "## Guardrail evidence",
11468
- "",
11469
- "- No assistant.guardrail events were recorded for this session."
11470
- ].join(`
11471
- `);
11472
- }
11473
- return [
11474
- "## Guardrail evidence",
11475
- "",
11476
- ...record.guardrails.decisions.map((decision) => {
11477
- const findings = decision.findings.map((finding) => [finding.action, finding.ruleId, finding.label].filter((value) => typeof value === "string").join(":")).filter(Boolean).join(", ");
11478
- return `- assistant.guardrail ${decision.stage ?? "unknown"}: ${decision.status ?? "unknown"}; allowed=${String(decision.allowed ?? "unknown")}; proof=${decision.proof ?? "runtime"}; findings=${findings || "none"}`;
11479
- })
11480
- ].join(`
11481
- `);
11482
- };
11483
- var renderVoiceOperationsRecordHTML = (record, options = {}) => {
11484
- const providers = record.providers.length ? record.providers.map((provider) => `<article><strong>${escapeHtml(provider.provider)}</strong><span>${String(provider.eventCount)} events</span><span>${formatMs2(provider.averageElapsedMs)} avg</span><span>${String(provider.errorCount)} errors</span></article>`).join("") : '<p class="muted">No provider events recorded.</p>';
11485
- const transcript = record.transcript.length ? record.transcript.map((turn) => `<li><strong>${escapeHtml(turn.id)}</strong>${turn.committedText ? `<p><span class="label">Caller</span>${escapeHtml(turn.committedText)}</p>` : ""}${turn.assistantReplies.map((reply) => `<p><span class="label">Assistant</span>${escapeHtml(reply)}</p>`).join("")}${turn.errors.map((error) => `<p class="error"><span class="label">Error</span>${escapeHtml(error)}</p>`).join("")}</li>`).join("") : "<li>No transcript turns recorded.</li>";
11486
- const providerDecisions = record.providerDecisions.length ? record.providerDecisions.map((decision) => `<li><strong>${escapeHtml(decision.provider ?? decision.selectedProvider ?? decision.fallbackProvider ?? "provider")}</strong> <span>${escapeHtml(decision.status ?? decision.type)}</span> ${formatMs2(decision.elapsedMs)}${decision.surface ? `<p><span class="label">Surface</span>${escapeHtml(decision.surface)}</p>` : ""}${decision.kind ? `<p><span class="label">Kind</span>${escapeHtml(decision.kind)}</p>` : ""}${decision.selectedProvider ? `<p>Selected: ${escapeHtml(decision.selectedProvider)}</p>` : ""}${decision.fallbackProvider ? `<p>Fallback: ${escapeHtml(decision.fallbackProvider)}</p>` : ""}${decision.error ? `<p class="error">${escapeHtml(decision.error)}</p>` : ""}${decision.reason ? `<p>${escapeHtml(decision.reason)}</p>` : ""}</li>`).join("") : "<li>No provider decisions recorded.</li>";
11487
- const providerDecisionSummary = record.providerDecisionSummary;
11488
- const handoffs = record.handoffs.length ? record.handoffs.map((handoff) => `<li><strong>${escapeHtml(handoff.fromAgentId ?? "unknown")}</strong> to <strong>${escapeHtml(handoff.targetAgentId ?? "unknown")}</strong> <span>${escapeHtml(handoff.status ?? "")}</span><p>${escapeHtml(handoff.summary ?? handoff.reason ?? "")}</p></li>`).join("") : "<li>No agent handoffs recorded.</li>";
11489
- const tools = record.tools.length ? record.tools.map((tool) => `<li><strong>${escapeHtml(tool.toolName ?? "tool")}</strong> <span>${escapeHtml(tool.status ?? "")}</span> ${formatMs2(tool.elapsedMs)} ${tool.error ? `<p>${escapeHtml(tool.error)}</p>` : ""}</li>`).join("") : "<li>No tool calls recorded.</li>";
11490
- const reviews = record.reviews?.reviews.length ? record.reviews.reviews.map((review) => `<li><strong>${escapeHtml(review.title)}</strong> <span>${escapeHtml(review.summary.outcome ?? "")}</span><p>${escapeHtml(review.postCall?.summary ?? review.transcript.actual)}</p></li>`).join("") : "<li>No call reviews recorded.</li>";
11491
- const tasks = record.tasks?.tasks.length ? record.tasks.tasks.map((task) => `<li><strong>${escapeHtml(task.title)}</strong> <span>${escapeHtml(task.status)}</span><p>${escapeHtml(task.recommendedAction)}</p></li>`).join("") : "<li>No ops tasks recorded.</li>";
11492
- const integrationEvents = record.integrationEvents?.events.length ? record.integrationEvents.events.map((event) => `<li><strong>${escapeHtml(event.type)}</strong> <span>${escapeHtml(event.deliveryStatus ?? "local")}</span><p>${escapeHtml(event.deliveryError ?? event.deliveredTo ?? "")}</p></li>`).join("") : "<li>No integration events recorded.</li>";
11493
- const guardrails = record.guardrails.total ? record.guardrails.decisions.map((decision) => {
11494
- const findings = decision.findings.map((finding) => finding.label ?? finding.ruleId ?? finding.action).filter((value) => typeof value === "string").join(", ") || "none";
11495
- return `<li><strong>assistant.guardrail ${escapeHtml(decision.stage ?? "unknown")}</strong> <span>${escapeHtml(decision.status ?? "")}</span><p>Allowed: ${escapeHtml(String(decision.allowed ?? "unknown"))} \xB7 Proof: ${escapeHtml(decision.proof ?? "runtime")}${decision.turnId ? ` \xB7 Turn: ${escapeHtml(decision.turnId)}` : ""}</p><p>${escapeHtml(findings)}</p></li>`;
11496
- }).join("") : "<li>No assistant.guardrail events recorded.</li>";
11497
- const telephonyMedia = record.telephonyMedia.events.length ? record.telephonyMedia.events.slice(0, 50).map((event) => {
11498
- const details = [
11499
- event.carrier ? `Carrier: ${event.carrier}` : undefined,
11500
- event.streamId ? `Stream: ${event.streamId}` : undefined,
11501
- event.callSid ? `Call: ${event.callSid}` : undefined,
11502
- event.direction ? `Direction: ${event.direction}` : undefined,
11503
- event.sequenceNumber ? `Seq: ${event.sequenceNumber}` : undefined,
11504
- `Audio bytes: ${String(event.audioBytes)}`
11505
- ].filter((detail) => typeof detail === "string");
11506
- return `<li><strong>${escapeHtml(event.event)}</strong> <span>${escapeHtml(new Date(event.at).toLocaleString())}</span><p>${escapeHtml(details.join(" \xB7 "))}</p></li>`;
11507
- }).join("") : "<li>No telephony media trace events recorded.</li>";
11508
- const mediaPipelineSection = record.mediaPipeline ? `<section id="media-pipeline"><h2>Media Pipeline</h2><p class="muted">Surface: ${escapeHtml(record.mediaPipeline.surface)} \xB7 Status: ${escapeHtml(record.mediaPipeline.status)} \xB7 Quality: ${escapeHtml(record.mediaPipeline.qualityStatus)} \xB7 Transport: ${escapeHtml(record.mediaPipeline.transportStatus ?? "n/a")} \xB7 Graph: ${escapeHtml(record.mediaPipeline.processorGraphStatus ?? "n/a")} \xB7 Frames: ${String(record.mediaPipeline.frames)} \xB7 Jitter: ${record.mediaPipeline.jitterMs === undefined ? "n/a" : `${String(record.mediaPipeline.jitterMs)}ms`}</p><ul>${record.mediaPipeline.issueCodes.length ? record.mediaPipeline.issueCodes.map((code) => `<li><strong>${escapeHtml(code)}</strong></li>`).join("") : "<li>No media pipeline issue codes.</li>"}</ul></section>` : "";
11509
- const mediaPipelineCard = record.mediaPipeline ? `<div class="card"><span>Media pipeline</span><strong>${escapeHtml(record.mediaPipeline.status)}</strong><span>${String(record.mediaPipeline.issueCodes.length)} issue code(s)</span></div>` : "";
11510
- const mediaPipelineNavLink = record.mediaPipeline ? '<a href="#media-pipeline">Media pipeline</a>' : "";
11511
- const snippet = escapeHtml(`app.use(
11512
- createVoiceOperationsRecordRoutes({
11513
- audit: auditStore,
11514
- integrationEvents: opsEvents,
11515
- htmlPath: '/voice-ops/:sessionId',
11516
- path: '/api/voice-ops/:sessionId',
11517
- redact: {
11518
- keys: ['authorization', 'apiKey', 'token']
11519
- },
11520
- reviews: callReviews,
11521
- store: traceStore,
11522
- tasks: opsTasks
11523
- })
11524
- );`);
11525
- const incidentMarkdown = escapeHtml(renderVoiceOperationsRecordIncidentMarkdown(record));
11526
- const incidentLink = options.incidentHref ? `<a href="${escapeHtml(options.incidentHref)}">Download incident.md</a>` : "";
11527
- return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml(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>${escapeHtml(options.title ?? "Voice Operations Record")}</h1><p class="status ${escapeHtml(record.status)}">${escapeHtml(record.status)}</p><div class="hero-actions"><a href="#transcript">Transcript</a><a href="#provider-decisions">Provider decisions</a><a href="#telephony-media">Telephony media</a>${mediaPipelineNavLink}<a href="#guardrails">Guardrails</a><a href="#incident-handoff">Incident handoff</a>${incidentLink}</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>${formatMs2(record.summary.callDurationMs)}</strong></div><div class="card"><span>Provider recovery</span><strong>${escapeHtml(providerDecisionSummary.recoveryStatus)}</strong><span>${String(providerDecisionSummary.fallbacks)} fallback / ${String(providerDecisionSummary.degraded)} degraded / ${String(providerDecisionSummary.errors)} errors</span></div><div class="card"><span>Telephony media</span><strong>${String(record.telephonyMedia.media)}</strong><span>${String(record.telephonyMedia.inbound)} inbound / ${String(record.telephonyMedia.outbound)} outbound / ${String(record.telephonyMedia.clears)} clears</span></div><div class="card"><span>Guardrails</span><strong>${String(record.guardrails.blocked)}</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>${mediaPipelineCard}</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="telephony-media"><h2>Telephony Media</h2><p class="muted">Live <code>client.telephony_media</code> stream lifecycle evidence attached to this session. Carriers: ${escapeHtml(record.telephonyMedia.carriers.join(", ") || "none")}. Streams: ${escapeHtml(record.telephonyMedia.streamIds.join(", ") || "none")}. Inbound: ${String(record.telephonyMedia.inbound)}. Outbound: ${String(record.telephonyMedia.outbound)}. Marks: ${String(record.telephonyMedia.marks)}. Clears: ${String(record.telephonyMedia.clears)}.</p><ul>${telephonyMedia}</ul></section>${mediaPipelineSection}<section id="guardrails"><h2>Guardrail Evidence</h2><p class="muted">Live <code>assistant.guardrail</code> decisions attached to this session.</p><ul>${guardrails}</ul></section><section id="incident-handoff"><h2>Copyable Incident Handoff</h2><p class="muted">Paste this into Slack, Linear, Zendesk, or an incident review. ${incidentLink}</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, guardrails, audit, latency, replay, reviews, tasks, media streams, 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>`;
11528
- };
11529
- var createVoiceOperationsRecordRoutes = (options) => {
11530
- const path = options.path ?? "/api/voice-operations/:sessionId";
11531
- const htmlPath = options.htmlPath === undefined ? "/voice-operations/:sessionId" : options.htmlPath;
11532
- const incidentPath = options.incidentPath === undefined ? `${path}/incident.md` : options.incidentPath;
11533
- const incidentHtmlPath = options.incidentHtmlPath === undefined && htmlPath ? `${htmlPath}/incident.md` : options.incidentHtmlPath;
11534
- const routes = new Elysia4({
11535
- name: options.name ?? "absolutejs-voice-operations-record"
11536
- });
11537
- const resolveMediaPipeline = async (sessionId) => {
11538
- if (options.mediaPipeline === undefined)
11539
- return;
11540
- return typeof options.mediaPipeline === "function" ? await options.mediaPipeline({ sessionId }) : options.mediaPipeline;
11541
- };
11542
- const buildRecord = async (sessionId) => buildVoiceOperationsRecord({
11543
- audit: options.audit,
11544
- evaluation: options.evaluation,
11545
- events: options.events,
11546
- integrationEvents: options.integrationEvents,
11547
- mediaPipeline: await resolveMediaPipeline(sessionId),
11548
- redact: options.redact,
11549
- reviews: options.reviews,
11550
- sessionId,
11551
- store: options.store,
11552
- tasks: options.tasks
11553
- });
11554
- const getSessionId = (params) => params.sessionId ?? "";
11555
- routes.get(path, async ({ params }) => Response.json(await buildRecord(getSessionId(params))));
11556
- const incidentHandler = async ({
11557
- params
11558
- }) => {
11559
- const record = await buildRecord(getSessionId(params));
11560
- const body = await (options.renderIncidentMarkdown ?? renderVoiceOperationsRecordIncidentMarkdown)(record);
11561
- return new Response(body, {
11562
- headers: {
11563
- "Content-Type": "text/markdown; charset=utf-8",
11564
- ...options.headers
11565
- }
11566
- });
11567
- };
11568
- if (incidentPath) {
11569
- routes.get(incidentPath, incidentHandler);
11570
- }
11571
- if (htmlPath) {
11572
- routes.get(htmlPath, async ({ params }) => {
11573
- const record = await buildRecord(getSessionId(params));
11574
- const body = await (options.render ?? ((input) => renderVoiceOperationsRecordHTML(input, {
11575
- incidentHref: incidentHtmlPath ? resolveRoutePath(incidentHtmlPath, input.sessionId) : undefined,
11576
- title: options.title
11577
- })))(record);
11578
- return new Response(body, {
11579
- headers: {
11580
- "Content-Type": "text/html; charset=utf-8",
11581
- ...options.headers
11582
- }
11583
- });
11584
- });
11585
- }
11586
- if (incidentHtmlPath && incidentHtmlPath !== incidentPath) {
11587
- routes.get(incidentHtmlPath, incidentHandler);
11588
- }
11589
- return routes;
11590
- };
11591
11588
 
11592
11589
  // src/telephony/twilio.ts
11593
11590
  import { Buffer as Buffer3 } from "buffer";
@@ -11672,6 +11669,7 @@ var createMemoryVoiceTelephonyWebhookIdempotencyStore = () => {
11672
11669
  var isTelephonyWebhookProvider = (value) => value === "generic" || value === "plivo" || value === "telnyx" || value === "twilio";
11673
11670
  var isTelephonyOutcomeAction = (value) => value === "complete" || value === "escalate" || value === "ignore" || value === "no-answer" || value === "transfer" || value === "voicemail";
11674
11671
  var isCallDisposition = (value) => value === "completed" || value === "escalated" || value === "failed" || value === "no-answer" || value === "transferred" || value === "voicemail";
11672
+ var assertVoiceTelephonyWebhookNormalizationEvidence = (input = {}) => assertVoiceEvidence("Voice telephony webhook normalization evidence assertion failed", evaluateVoiceTelephonyWebhookNormalizationEvidence(input));
11675
11673
  var evaluateVoiceTelephonyWebhookNormalizationEvidence = (input = {}) => {
11676
11674
  const issues = [];
11677
11675
  const decisions = input.decisions ?? [];
@@ -11765,9 +11763,6 @@ var evaluateVoiceTelephonyWebhookNormalizationEvidence = (input = {}) => {
11765
11763
  verificationAttempts: verificationAttempts.length
11766
11764
  };
11767
11765
  };
11768
- var assertVoiceTelephonyWebhookNormalizationEvidence = (input = {}) => {
11769
- return assertVoiceEvidence("Voice telephony webhook normalization evidence assertion failed", evaluateVoiceTelephonyWebhookNormalizationEvidence(input));
11770
- };
11771
11766
  var normalizeToken = (value) => typeof value === "string" ? value.trim().toLowerCase().replace(/\s+/g, "-").replace(/_+/g, "-") : undefined;
11772
11767
  var firstString2 = (source, keys) => {
11773
11768
  for (const key of keys) {
@@ -11921,6 +11916,45 @@ var buildDecision = (action, input) => ({
11921
11916
  source: input.source,
11922
11917
  target: action === "transfer" ? resolveTransferTarget(input.event, input.policy) : undefined
11923
11918
  });
11919
+ var applyVoiceTelephonyOutcome = async (api, decision, result) => {
11920
+ switch (decision.action) {
11921
+ case "complete":
11922
+ await api.complete(result);
11923
+ break;
11924
+ case "escalate":
11925
+ await api.escalate({
11926
+ metadata: decision.metadata,
11927
+ reason: decision.reason ?? "telephony-escalation",
11928
+ result
11929
+ });
11930
+ break;
11931
+ case "no-answer":
11932
+ await api.markNoAnswer({
11933
+ metadata: decision.metadata,
11934
+ result
11935
+ });
11936
+ break;
11937
+ case "transfer":
11938
+ if (!decision.target) {
11939
+ return;
11940
+ }
11941
+ await api.transfer({
11942
+ metadata: decision.metadata,
11943
+ reason: decision.reason,
11944
+ result,
11945
+ target: decision.target
11946
+ });
11947
+ break;
11948
+ case "voicemail":
11949
+ await api.markVoicemail({
11950
+ metadata: decision.metadata,
11951
+ result
11952
+ });
11953
+ break;
11954
+ default:
11955
+ break;
11956
+ }
11957
+ };
11924
11958
  var createVoiceTelephonyOutcomePolicy = (policy = {}) => ({
11925
11959
  completedStatuses: policy.completedStatuses ?? DEFAULT_COMPLETED_STATUSES,
11926
11960
  escalationStatuses: policy.escalationStatuses ?? DEFAULT_ESCALATION_STATUSES,
@@ -12033,48 +12067,9 @@ var voiceTelephonyOutcomeToRouteResult = (decision, result) => {
12033
12067
  voicemail: {
12034
12068
  metadata: decision.metadata
12035
12069
  }
12036
- };
12037
- default:
12038
- return { result };
12039
- }
12040
- };
12041
- var applyVoiceTelephonyOutcome = async (api, decision, result) => {
12042
- switch (decision.action) {
12043
- case "complete":
12044
- await api.complete(result);
12045
- break;
12046
- case "escalate":
12047
- await api.escalate({
12048
- metadata: decision.metadata,
12049
- reason: decision.reason ?? "telephony-escalation",
12050
- result
12051
- });
12052
- break;
12053
- case "no-answer":
12054
- await api.markNoAnswer({
12055
- metadata: decision.metadata,
12056
- result
12057
- });
12058
- break;
12059
- case "transfer":
12060
- if (!decision.target) {
12061
- return;
12062
- }
12063
- await api.transfer({
12064
- metadata: decision.metadata,
12065
- reason: decision.reason,
12066
- result,
12067
- target: decision.target
12068
- });
12069
- break;
12070
- case "voicemail":
12071
- await api.markVoicemail({
12072
- metadata: decision.metadata,
12073
- result
12074
- });
12075
- break;
12070
+ };
12076
12071
  default:
12077
- break;
12072
+ return { result };
12078
12073
  }
12079
12074
  };
12080
12075
  var parseRequestBodyText = (input) => {
@@ -12694,11 +12689,6 @@ var encodeTwilioMulawBase64 = (samples) => {
12694
12689
  }
12695
12690
  return Buffer3.from(bytes).toString("base64");
12696
12691
  };
12697
- var transcodeTwilioInboundPayloadToPCM16 = (payload) => {
12698
- const narrowband = decodeTwilioMulawBase64(payload);
12699
- const wideband = linearResample(narrowband, TWILIO_MULAW_SAMPLE_RATE, VOICE_PCM_SAMPLE_RATE);
12700
- return int16ArrayToBytes(wideband);
12701
- };
12702
12692
  var transcodePCMToTwilioOutboundPayload = (chunk, format) => {
12703
12693
  if (format.container === "raw" && format.encoding === "mulaw" && format.channels === 1 && format.sampleRateHz === TWILIO_MULAW_SAMPLE_RATE) {
12704
12694
  return Buffer3.from(chunk).toString("base64");
@@ -12715,6 +12705,11 @@ var transcodePCMToTwilioOutboundPayload = (chunk, format) => {
12715
12705
  const telephony = linearResample(mono, format.sampleRateHz, TWILIO_MULAW_SAMPLE_RATE);
12716
12706
  return encodeTwilioMulawBase64(telephony);
12717
12707
  };
12708
+ var transcodeTwilioInboundPayloadToPCM16 = (payload) => {
12709
+ const narrowband = decodeTwilioMulawBase64(payload);
12710
+ const wideband = linearResample(narrowband, TWILIO_MULAW_SAMPLE_RATE, VOICE_PCM_SAMPLE_RATE);
12711
+ return int16ArrayToBytes(wideband);
12712
+ };
12718
12713
  var parseTwilioMessage = (raw) => {
12719
12714
  if (typeof raw !== "string") {
12720
12715
  return raw;
@@ -12811,10 +12806,6 @@ var createTwilioSocketAdapter = (socket, getState) => ({
12811
12806
  }
12812
12807
  }
12813
12808
  });
12814
- var createTwilioVoiceResponse = (options) => {
12815
- const parameters = Object.entries(options.parameters ?? {}).filter((entry) => entry[1] !== undefined).map(([name, value]) => `<Parameter name="${escapeXml2(name)}" value="${escapeXml2(String(value))}" />`).join("");
12816
- return `<?xml version="1.0" encoding="UTF-8"?><Response><Connect><Stream url="${escapeXml2(options.streamUrl)}"${options.track ? ` track="${escapeXml2(options.track)}"` : ""}${options.streamName ? ` name="${escapeXml2(options.streamName)}"` : ""}>${parameters}</Stream></Connect></Response>`;
12817
- };
12818
12809
  var createTwilioMediaStreamBridge = (socket, options) => {
12819
12810
  const runtimePreset = resolveVoiceRuntimePreset(options.preset);
12820
12811
  const turnDetection = resolveTurnDetectionConfig({
@@ -12857,7 +12848,7 @@ var createTwilioMediaStreamBridge = (socket, options) => {
12857
12848
  let reviewArtifactDelivered = false;
12858
12849
  const telephonyMediaCarrier = bridgeState.carrier;
12859
12850
  const appendTelephonyMediaTrace = async (message, override) => {
12860
- const trace = options.trace;
12851
+ const { trace } = options;
12861
12852
  const sessionId = override?.sessionId ?? bridgeState.sessionId ?? (message.event === "start" ? message.start.customParameters?.sessionId : undefined) ?? (message.event === "start" ? message.start.streamSid : "telephony-media");
12862
12853
  const streamSid = override?.streamSid ?? (message.event === "start" ? message.start.streamSid : ("streamSid" in message) ? message.streamSid : undefined);
12863
12854
  await trace?.append({
@@ -12909,15 +12900,15 @@ var createTwilioMediaStreamBridge = (socket, options) => {
12909
12900
  onSession: options.onSession,
12910
12901
  onTurn: async (input) => {
12911
12902
  bridgeState.reviewRecorder?.recordVoiceMessage({
12912
- type: "turn",
12913
- turn: input.turn
12903
+ turn: input.turn,
12904
+ type: "turn"
12914
12905
  });
12915
12906
  const result = await normalizedOnTurn(input);
12916
12907
  if (result?.assistantText) {
12917
12908
  bridgeState.reviewRecorder?.recordVoiceMessage({
12918
- type: "assistant",
12919
12909
  text: result.assistantText,
12920
- turnId: input.turn.id
12910
+ turnId: input.turn.id,
12911
+ type: "assistant"
12921
12912
  });
12922
12913
  }
12923
12914
  return result;
@@ -13036,11 +13027,14 @@ var createTwilioMediaStreamBridge = (socket, options) => {
13036
13027
  streamSid: bridgeState.streamSid ?? undefined
13037
13028
  });
13038
13029
  await sessionHandle?.close("twilio-stop");
13039
- return;
13040
13030
  }
13041
13031
  }
13042
13032
  };
13043
13033
  };
13034
+ var createTwilioVoiceResponse = (options) => {
13035
+ const parameters = Object.entries(options.parameters ?? {}).filter((entry) => entry[1] !== undefined).map(([name, value]) => `<Parameter name="${escapeXml2(name)}" value="${escapeXml2(String(value))}" />`).join("");
13036
+ return `<?xml version="1.0" encoding="UTF-8"?><Response><Connect><Stream url="${escapeXml2(options.streamUrl)}"${options.track ? ` track="${escapeXml2(options.track)}"` : ""}${options.streamName ? ` name="${escapeXml2(options.streamName)}"` : ""}>${parameters}</Stream></Connect></Response>`;
13037
+ };
13044
13038
  var createTwilioVoiceRoutes = (options) => {
13045
13039
  const streamPath = options.streamPath ?? "/api/voice/twilio/stream";
13046
13040
  const twimlPath = options.twiml?.path ?? "/api/voice/twilio";
@@ -13275,8 +13269,8 @@ var createFakeSTTAdapter = (inputSpy, sttDelayMs) => ({
13275
13269
  }
13276
13270
  for (const handler of listeners.endOfTurn) {
13277
13271
  handler({
13278
- receivedAt,
13279
13272
  reason: "vendor",
13273
+ receivedAt,
13280
13274
  type: "endOfTurn"
13281
13275
  });
13282
13276
  }
@@ -13331,101 +13325,18 @@ var defaultOperationsRecordHref = ({
13331
13325
  sessionId
13332
13326
  }) => `/voice-operations/${encodeURIComponent(sessionId)}`;
13333
13327
  var resolveOperationsRecordHref2 = (href, input) => typeof href === "function" ? href(input) : href === undefined ? defaultOperationsRecordHref(input) : href;
13334
- var runVoiceTelephonyMediaOperationsSmoke = async (options = {}) => {
13335
- const timeoutMs = options.timeoutMs ?? 5000;
13336
- const sessionId = options.sessionId ?? `telephony-media-ops-${Date.now()}`;
13337
- const streamSid = options.streamSid ?? "telephony-media-ops-stream";
13338
- const callSid = options.callSid ?? "telephony-media-ops-call";
13339
- const scenarioId = options.scenarioId ?? "telephony-media-operations-smoke";
13340
- const trace = options.store ?? createVoiceMemoryTraceEventStore();
13341
- const sentEvents = [];
13342
- const receivedAudio = [];
13343
- const bridge = createTwilioMediaStreamBridge({
13344
- close: () => {},
13345
- send: (data) => {
13346
- sentEvents.push(JSON.parse(data));
13347
- }
13348
- }, {
13349
- context: {},
13350
- onComplete: async () => {},
13351
- onTurn: async () => ({
13352
- assistantText: "Confirmed. Two way carrier media is visible."
13353
- }),
13354
- session: createVoiceMemoryStore(),
13355
- stt: createFakeSTTAdapter(receivedAudio, 0),
13356
- trace,
13357
- tts: createFakeTTSAdapter(1, 0),
13358
- turnDetection: {
13359
- transcriptStabilityMs: 0
13360
- }
13361
- });
13362
- const payload = encodeTwilioMulawBase64(new Int16Array([500, -500, 1500, -1500, 2500, -2500]));
13363
- await bridge.handleMessage({
13364
- event: "start",
13365
- start: {
13366
- callSid,
13367
- customParameters: {
13368
- scenarioId,
13369
- sessionId
13370
- },
13371
- streamSid
13372
- },
13373
- streamSid
13374
- });
13375
- await bridge.handleMessage({
13376
- event: "media",
13377
- media: {
13378
- payload,
13379
- track: "inbound"
13380
- },
13381
- streamSid
13382
- });
13383
- await waitFor(() => sentEvents.some((message) => message.event === "media"), timeoutMs);
13384
- await bridge.handleMessage({
13385
- event: "media",
13386
- media: {
13387
- payload,
13388
- track: "inbound"
13389
- },
13390
- streamSid
13391
- });
13392
- await waitFor(() => sentEvents.some((message) => message.event === "clear"), timeoutMs);
13393
- await bridge.handleMessage({
13394
- event: "stop",
13395
- stop: {
13396
- callSid
13397
- },
13398
- streamSid
13399
- });
13400
- await bridge.close("telephony-media-operations-smoke-complete");
13401
- const operationsRecord = await buildVoiceOperationsRecord({
13402
- sessionId,
13403
- store: trace
13404
- });
13405
- const media = operationsRecord.telephonyMedia;
13406
- const issues = [
13407
- media.starts < 1 ? "Missing telephony media start event." : undefined,
13408
- media.stops < 1 ? "Missing telephony media stop event." : undefined,
13409
- media.inbound < 2 ? "Expected at least two inbound telephony media events." : undefined,
13410
- media.outbound < 2 ? "Expected outbound assistant media/control evidence." : undefined,
13411
- media.media < 3 ? "Expected inbound and outbound telephony media packet evidence." : undefined,
13412
- media.clears < 1 ? "Missing outbound clear evidence." : undefined,
13413
- media.audioBytes <= 0 ? "Missing telephony media audio bytes." : undefined,
13414
- media.carriers.includes("twilio") ? undefined : "Missing Twilio carrier evidence.",
13415
- media.streamIds.includes(streamSid) ? undefined : "Missing telephony media stream ID evidence."
13416
- ].filter((issue) => typeof issue === "string");
13328
+ var getDefaultVoiceTelephonyBenchmarkScenarios = () => DEFAULT_SCENARIOS2.map((scenario) => ({ ...scenario }));
13329
+ var runVoiceTelephonyBenchmark = async (scenarios = getDefaultVoiceTelephonyBenchmarkScenarios(), options = {}) => {
13330
+ const fixtures = [];
13331
+ for (const scenario of scenarios) {
13332
+ fixtures.push(await runVoiceTelephonyBenchmarkScenario(scenario, options));
13333
+ }
13417
13334
  return {
13418
- issues,
13419
- ok: issues.length === 0,
13420
- operationsRecord,
13421
- operationsRecordHref: resolveOperationsRecordHref2(options.operationsRecordHref, { sessionId, streamSid }),
13422
- sentEvents: sentEvents.map((message) => message.event).filter((event) => typeof event === "string"),
13423
- sessionId,
13424
- streamSid,
13425
- telephonyMedia: media
13335
+ fixtures,
13336
+ generatedAt: Date.now(),
13337
+ summary: summarizeVoiceTelephonyBenchmark(fixtures)
13426
13338
  };
13427
13339
  };
13428
- var getDefaultVoiceTelephonyBenchmarkScenarios = () => DEFAULT_SCENARIOS2.map((scenario) => ({ ...scenario }));
13429
13340
  var runVoiceTelephonyBenchmarkScenario = async (scenario, options = {}) => {
13430
13341
  const timeoutMs = options.timeoutMs ?? 1000;
13431
13342
  const sentEvents = [];
@@ -13517,6 +13428,100 @@ var runVoiceTelephonyBenchmarkScenario = async (scenario, options = {}) => {
13517
13428
  title: scenario.title
13518
13429
  };
13519
13430
  };
13431
+ var runVoiceTelephonyMediaOperationsSmoke = async (options = {}) => {
13432
+ const timeoutMs = options.timeoutMs ?? 5000;
13433
+ const sessionId = options.sessionId ?? `telephony-media-ops-${Date.now()}`;
13434
+ const streamSid = options.streamSid ?? "telephony-media-ops-stream";
13435
+ const callSid = options.callSid ?? "telephony-media-ops-call";
13436
+ const scenarioId = options.scenarioId ?? "telephony-media-operations-smoke";
13437
+ const trace = options.store ?? createVoiceMemoryTraceEventStore();
13438
+ const sentEvents = [];
13439
+ const receivedAudio = [];
13440
+ const bridge = createTwilioMediaStreamBridge({
13441
+ close: () => {},
13442
+ send: (data) => {
13443
+ sentEvents.push(JSON.parse(data));
13444
+ }
13445
+ }, {
13446
+ context: {},
13447
+ onComplete: async () => {},
13448
+ onTurn: async () => ({
13449
+ assistantText: "Confirmed. Two way carrier media is visible."
13450
+ }),
13451
+ session: createVoiceMemoryStore(),
13452
+ stt: createFakeSTTAdapter(receivedAudio, 0),
13453
+ trace,
13454
+ tts: createFakeTTSAdapter(1, 0),
13455
+ turnDetection: {
13456
+ transcriptStabilityMs: 0
13457
+ }
13458
+ });
13459
+ const payload = encodeTwilioMulawBase64(new Int16Array([500, -500, 1500, -1500, 2500, -2500]));
13460
+ await bridge.handleMessage({
13461
+ event: "start",
13462
+ start: {
13463
+ callSid,
13464
+ customParameters: {
13465
+ scenarioId,
13466
+ sessionId
13467
+ },
13468
+ streamSid
13469
+ },
13470
+ streamSid
13471
+ });
13472
+ await bridge.handleMessage({
13473
+ event: "media",
13474
+ media: {
13475
+ payload,
13476
+ track: "inbound"
13477
+ },
13478
+ streamSid
13479
+ });
13480
+ await waitFor(() => sentEvents.some((message) => message.event === "media"), timeoutMs);
13481
+ await bridge.handleMessage({
13482
+ event: "media",
13483
+ media: {
13484
+ payload,
13485
+ track: "inbound"
13486
+ },
13487
+ streamSid
13488
+ });
13489
+ await waitFor(() => sentEvents.some((message) => message.event === "clear"), timeoutMs);
13490
+ await bridge.handleMessage({
13491
+ event: "stop",
13492
+ stop: {
13493
+ callSid
13494
+ },
13495
+ streamSid
13496
+ });
13497
+ await bridge.close("telephony-media-operations-smoke-complete");
13498
+ const operationsRecord = await buildVoiceOperationsRecord({
13499
+ sessionId,
13500
+ store: trace
13501
+ });
13502
+ const media = operationsRecord.telephonyMedia;
13503
+ const issues = [
13504
+ media.starts < 1 ? "Missing telephony media start event." : undefined,
13505
+ media.stops < 1 ? "Missing telephony media stop event." : undefined,
13506
+ media.inbound < 2 ? "Expected at least two inbound telephony media events." : undefined,
13507
+ media.outbound < 2 ? "Expected outbound assistant media/control evidence." : undefined,
13508
+ media.media < 3 ? "Expected inbound and outbound telephony media packet evidence." : undefined,
13509
+ media.clears < 1 ? "Missing outbound clear evidence." : undefined,
13510
+ media.audioBytes <= 0 ? "Missing telephony media audio bytes." : undefined,
13511
+ media.carriers.includes("twilio") ? undefined : "Missing Twilio carrier evidence.",
13512
+ media.streamIds.includes(streamSid) ? undefined : "Missing telephony media stream ID evidence."
13513
+ ].filter((issue) => typeof issue === "string");
13514
+ return {
13515
+ issues,
13516
+ ok: issues.length === 0,
13517
+ operationsRecord,
13518
+ operationsRecordHref: resolveOperationsRecordHref2(options.operationsRecordHref, { sessionId, streamSid }),
13519
+ sentEvents: sentEvents.map((message) => message.event).filter((event) => typeof event === "string"),
13520
+ sessionId,
13521
+ streamSid,
13522
+ telephonyMedia: media
13523
+ };
13524
+ };
13520
13525
  var summarizeVoiceTelephonyBenchmark = (fixtures) => {
13521
13526
  const scenarioCount = fixtures.length;
13522
13527
  const firstMediaSamples = fixtures.filter((fixture) => typeof fixture.firstOutboundMediaLatencyMs === "number");
@@ -13534,17 +13539,6 @@ var summarizeVoiceTelephonyBenchmark = (fixtures) => {
13534
13539
  totalOutboundMediaCount: fixtures.reduce((sum, fixture) => sum + fixture.outboundMediaCount, 0)
13535
13540
  };
13536
13541
  };
13537
- var runVoiceTelephonyBenchmark = async (scenarios = getDefaultVoiceTelephonyBenchmarkScenarios(), options = {}) => {
13538
- const fixtures = [];
13539
- for (const scenario of scenarios) {
13540
- fixtures.push(await runVoiceTelephonyBenchmarkScenario(scenario, options));
13541
- }
13542
- return {
13543
- fixtures,
13544
- generatedAt: Date.now(),
13545
- summary: summarizeVoiceTelephonyBenchmark(fixtures)
13546
- };
13547
- };
13548
13542
  // src/testing/tts.ts
13549
13543
  var DEFAULT_REALTIME_FORMAT2 = {
13550
13544
  channels: 1,
@@ -13579,6 +13573,19 @@ var getAudioDurationMs = (chunk, format) => {
13579
13573
  return chunk.byteLength / (format.sampleRateHz * format.channels * 2) * 1000;
13580
13574
  };
13581
13575
  var getDefaultTTSBenchmarkFixtures = () => DEFAULT_TTS_BENCHMARK_FIXTURES.map((fixture) => ({ ...fixture }));
13576
+ var runTTSAdapterBenchmark = async (adapterId, adapter, fixtures = getDefaultTTSBenchmarkFixtures(), options = {}) => {
13577
+ const results = [];
13578
+ for (const fixture of fixtures) {
13579
+ results.push(await runTTSAdapterFixture(adapter, fixture, options));
13580
+ }
13581
+ return {
13582
+ adapterId,
13583
+ fixtures: results,
13584
+ generatedAt: Date.now(),
13585
+ profileId: options.interruptAfterFirstAudioMs !== undefined ? "interrupt" : "default",
13586
+ summary: summarizeTTSBenchmark(adapterId, results)
13587
+ };
13588
+ };
13582
13589
  var runTTSAdapterFixture = async (adapter, fixture, options = {}) => {
13583
13590
  const openOptions = typeof options.openOptions === "function" ? options.openOptions(fixture) : options.openOptions;
13584
13591
  const settleMs = options.settleMs ?? 300;
@@ -13702,19 +13709,6 @@ var runTTSAdapterFixture = async (adapter, fixture, options = {}) => {
13702
13709
  totalAudioBytes
13703
13710
  };
13704
13711
  };
13705
- var runTTSAdapterBenchmark = async (adapterId, adapter, fixtures = getDefaultTTSBenchmarkFixtures(), options = {}) => {
13706
- const results = [];
13707
- for (const fixture of fixtures) {
13708
- results.push(await runTTSAdapterFixture(adapter, fixture, options));
13709
- }
13710
- return {
13711
- adapterId,
13712
- fixtures: results,
13713
- generatedAt: Date.now(),
13714
- profileId: options.interruptAfterFirstAudioMs !== undefined ? "interrupt" : "default",
13715
- summary: summarizeTTSBenchmark(adapterId, results)
13716
- };
13717
- };
13718
13712
  var summarizeTTSBenchmark = (adapterId, fixtures) => {
13719
13713
  const fixtureCount = fixtures.length;
13720
13714
  const totalAudioBytes = fixtures.reduce((sum, fixture) => sum + fixture.totalAudioBytes, 0);