@absolutejs/voice 0.0.22-beta.27 → 0.0.22-beta.270

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 (225) hide show
  1. package/README.md +3234 -228
  2. package/dist/agent.d.ts +62 -0
  3. package/dist/agentSquadContract.d.ts +98 -0
  4. package/dist/angular/index.d.ts +15 -0
  5. package/dist/angular/index.js +3402 -1150
  6. package/dist/angular/voice-agent-squad-status.service.d.ts +12 -0
  7. package/dist/angular/voice-campaign-dialer-proof.service.d.ts +14 -0
  8. package/dist/angular/voice-controller.service.d.ts +1 -0
  9. package/dist/angular/voice-delivery-runtime.component.d.ts +17 -0
  10. package/dist/angular/voice-delivery-runtime.service.d.ts +16 -0
  11. package/dist/angular/voice-live-ops.service.d.ts +11 -0
  12. package/dist/angular/voice-ops-action-center.service.d.ts +13 -0
  13. package/dist/angular/voice-ops-status.component.d.ts +15 -0
  14. package/dist/angular/voice-ops-status.service.d.ts +12 -0
  15. package/dist/angular/voice-platform-coverage.service.d.ts +12 -0
  16. package/dist/angular/voice-proof-trends.service.d.ts +12 -0
  17. package/dist/angular/voice-provider-capabilities.service.d.ts +12 -0
  18. package/dist/angular/voice-provider-contracts.service.d.ts +12 -0
  19. package/dist/angular/voice-routing-status.service.d.ts +11 -0
  20. package/dist/angular/voice-stream.service.d.ts +3 -0
  21. package/dist/angular/voice-trace-timeline.service.d.ts +12 -0
  22. package/dist/angular/voice-turn-latency.service.d.ts +13 -0
  23. package/dist/angular/voice-turn-quality.service.d.ts +12 -0
  24. package/dist/angular/voice-workflow-status.service.d.ts +12 -0
  25. package/dist/audit.d.ts +128 -0
  26. package/dist/auditDeliveryRoutes.d.ts +85 -0
  27. package/dist/auditExport.d.ts +34 -0
  28. package/dist/auditRoutes.d.ts +66 -0
  29. package/dist/auditSinks.d.ts +151 -0
  30. package/dist/bargeInRoutes.d.ts +56 -0
  31. package/dist/campaign.d.ts +768 -0
  32. package/dist/campaignDialers.d.ts +111 -0
  33. package/dist/client/actions.d.ts +83 -0
  34. package/dist/client/agentSquadStatus.d.ts +37 -0
  35. package/dist/client/agentSquadStatusWidget.d.ts +24 -0
  36. package/dist/client/bargeInMonitor.d.ts +7 -0
  37. package/dist/client/campaignDialerProof.d.ts +23 -0
  38. package/dist/client/deliveryRuntime.d.ts +34 -0
  39. package/dist/client/deliveryRuntimeWidget.d.ts +37 -0
  40. package/dist/client/duplex.d.ts +1 -1
  41. package/dist/client/htmxBootstrap.js +703 -13
  42. package/dist/client/index.d.ts +66 -0
  43. package/dist/client/index.js +5073 -19
  44. package/dist/client/liveOps.d.ts +22 -0
  45. package/dist/client/liveOpsWidget.d.ts +23 -0
  46. package/dist/client/liveTurnLatency.d.ts +41 -0
  47. package/dist/client/opsActionCenter.d.ts +54 -0
  48. package/dist/client/opsActionCenterWidget.d.ts +29 -0
  49. package/dist/client/opsActionHistory.d.ts +19 -0
  50. package/dist/client/opsActionHistoryWidget.d.ts +11 -0
  51. package/dist/client/opsStatus.d.ts +19 -0
  52. package/dist/client/opsStatusWidget.d.ts +40 -0
  53. package/dist/client/platformCoverage.d.ts +19 -0
  54. package/dist/client/platformCoverageWidget.d.ts +37 -0
  55. package/dist/client/proofTrends.d.ts +19 -0
  56. package/dist/client/proofTrendsWidget.d.ts +37 -0
  57. package/dist/client/providerCapabilities.d.ts +19 -0
  58. package/dist/client/providerCapabilitiesWidget.d.ts +32 -0
  59. package/dist/client/providerContracts.d.ts +19 -0
  60. package/dist/client/providerContractsWidget.d.ts +37 -0
  61. package/dist/client/providerSimulationControls.d.ts +33 -0
  62. package/dist/client/providerSimulationControlsWidget.d.ts +20 -0
  63. package/dist/client/providerStatusWidget.d.ts +32 -0
  64. package/dist/client/routingStatus.d.ts +19 -0
  65. package/dist/client/routingStatusWidget.d.ts +28 -0
  66. package/dist/client/traceTimeline.d.ts +19 -0
  67. package/dist/client/traceTimelineWidget.d.ts +36 -0
  68. package/dist/client/turnLatency.d.ts +22 -0
  69. package/dist/client/turnLatencyWidget.d.ts +33 -0
  70. package/dist/client/turnQuality.d.ts +19 -0
  71. package/dist/client/turnQualityWidget.d.ts +32 -0
  72. package/dist/client/workflowStatus.d.ts +19 -0
  73. package/dist/dataControl.d.ts +180 -0
  74. package/dist/deliveryRuntime.d.ts +158 -0
  75. package/dist/deliverySinkRoutes.d.ts +117 -0
  76. package/dist/demoReadyRoutes.d.ts +98 -0
  77. package/dist/diagnosticsRoutes.d.ts +44 -0
  78. package/dist/evalRoutes.d.ts +219 -0
  79. package/dist/fileStore.d.ts +14 -2
  80. package/dist/guardrails.d.ts +128 -0
  81. package/dist/handoff.d.ts +54 -0
  82. package/dist/handoffHealth.d.ts +94 -0
  83. package/dist/incidentBundle.d.ts +116 -0
  84. package/dist/index.d.ts +132 -13
  85. package/dist/index.js +25725 -5035
  86. package/dist/latencySlo.d.ts +56 -0
  87. package/dist/liveLatency.d.ts +78 -0
  88. package/dist/liveOps.d.ts +190 -0
  89. package/dist/modelAdapters.d.ts +23 -2
  90. package/dist/observabilityExport.d.ts +481 -0
  91. package/dist/openaiRealtime.d.ts +27 -0
  92. package/dist/openaiTTS.d.ts +18 -0
  93. package/dist/operationsRecord.d.ts +210 -0
  94. package/dist/opsActionAuditRoutes.d.ts +99 -0
  95. package/dist/opsConsoleRoutes.d.ts +80 -0
  96. package/dist/opsRecovery.d.ts +137 -0
  97. package/dist/opsStatus.d.ts +76 -0
  98. package/dist/opsStatusRoutes.d.ts +33 -0
  99. package/dist/outcomeContract.d.ts +146 -0
  100. package/dist/phoneAgent.d.ts +139 -0
  101. package/dist/phoneAgentProductionSmoke.d.ts +115 -0
  102. package/dist/platformCoverage.d.ts +91 -0
  103. package/dist/postCallAnalysis.d.ts +98 -0
  104. package/dist/postgresStore.d.ts +13 -2
  105. package/dist/productionReadiness.d.ts +484 -0
  106. package/dist/proofTrends.d.ts +133 -0
  107. package/dist/providerAdapters.d.ts +48 -0
  108. package/dist/providerCapabilities.d.ts +92 -0
  109. package/dist/providerHealth.d.ts +1 -0
  110. package/dist/providerRoutingContract.d.ts +71 -0
  111. package/dist/providerSlo.d.ts +142 -0
  112. package/dist/providerStackRecommendations.d.ts +187 -0
  113. package/dist/qualityRoutes.d.ts +76 -0
  114. package/dist/queue.d.ts +61 -0
  115. package/dist/react/VoiceAgentSquadStatus.d.ts +5 -0
  116. package/dist/react/VoiceDeliveryRuntime.d.ts +7 -0
  117. package/dist/react/VoiceOpsActionCenter.d.ts +5 -0
  118. package/dist/react/VoiceOpsStatus.d.ts +6 -0
  119. package/dist/react/VoicePlatformCoverage.d.ts +6 -0
  120. package/dist/react/VoiceProofTrends.d.ts +6 -0
  121. package/dist/react/VoiceProviderCapabilities.d.ts +6 -0
  122. package/dist/react/VoiceProviderContracts.d.ts +6 -0
  123. package/dist/react/VoiceProviderSimulationControls.d.ts +5 -0
  124. package/dist/react/VoiceProviderStatus.d.ts +6 -0
  125. package/dist/react/VoiceRoutingStatus.d.ts +6 -0
  126. package/dist/react/VoiceTraceTimeline.d.ts +6 -0
  127. package/dist/react/VoiceTurnLatency.d.ts +6 -0
  128. package/dist/react/VoiceTurnQuality.d.ts +6 -0
  129. package/dist/react/index.d.ts +30 -0
  130. package/dist/react/index.js +4773 -31
  131. package/dist/react/useVoiceAgentSquadStatus.d.ts +8 -0
  132. package/dist/react/useVoiceCampaignDialerProof.d.ts +10 -0
  133. package/dist/react/useVoiceController.d.ts +2 -0
  134. package/dist/react/useVoiceDeliveryRuntime.d.ts +13 -0
  135. package/dist/react/useVoiceLiveOps.d.ts +9 -0
  136. package/dist/react/useVoiceOpsActionCenter.d.ts +11 -0
  137. package/dist/react/useVoiceOpsStatus.d.ts +8 -0
  138. package/dist/react/useVoicePlatformCoverage.d.ts +8 -0
  139. package/dist/react/useVoiceProofTrends.d.ts +8 -0
  140. package/dist/react/useVoiceProviderCapabilities.d.ts +8 -0
  141. package/dist/react/useVoiceProviderContracts.d.ts +8 -0
  142. package/dist/react/useVoiceProviderSimulationControls.d.ts +10 -0
  143. package/dist/react/useVoiceRoutingStatus.d.ts +8 -0
  144. package/dist/react/useVoiceStream.d.ts +2 -0
  145. package/dist/react/useVoiceTraceTimeline.d.ts +8 -0
  146. package/dist/react/useVoiceTurnLatency.d.ts +9 -0
  147. package/dist/react/useVoiceTurnQuality.d.ts +8 -0
  148. package/dist/react/useVoiceWorkflowStatus.d.ts +8 -0
  149. package/dist/readinessProfiles.d.ts +37 -0
  150. package/dist/reconnectContract.d.ts +87 -0
  151. package/dist/resilienceRoutes.d.ts +143 -0
  152. package/dist/sessionReplay.d.ts +12 -0
  153. package/dist/simulationSuite.d.ts +143 -0
  154. package/dist/sqliteStore.d.ts +13 -2
  155. package/dist/svelte/createVoiceAgentSquadStatus.d.ts +9 -0
  156. package/dist/svelte/createVoiceCampaignDialerProof.d.ts +9 -0
  157. package/dist/svelte/createVoiceDeliveryRuntime.d.ts +11 -0
  158. package/dist/svelte/createVoiceLiveOps.d.ts +13 -0
  159. package/dist/svelte/createVoiceOpsActionCenter.d.ts +10 -0
  160. package/dist/svelte/createVoiceOpsStatus.d.ts +9 -0
  161. package/dist/svelte/createVoicePlatformCoverage.d.ts +7 -0
  162. package/dist/svelte/createVoiceProofTrends.d.ts +7 -0
  163. package/dist/svelte/createVoiceProviderCapabilities.d.ts +10 -0
  164. package/dist/svelte/createVoiceProviderContracts.d.ts +10 -0
  165. package/dist/svelte/createVoiceProviderSimulationControls.d.ts +11 -0
  166. package/dist/svelte/createVoiceProviderStatus.d.ts +4 -2
  167. package/dist/svelte/createVoiceRoutingStatus.d.ts +10 -0
  168. package/dist/svelte/createVoiceTraceTimeline.d.ts +10 -0
  169. package/dist/svelte/createVoiceTurnLatency.d.ts +11 -0
  170. package/dist/svelte/createVoiceTurnQuality.d.ts +10 -0
  171. package/dist/svelte/createVoiceWorkflowStatus.d.ts +8 -0
  172. package/dist/svelte/index.d.ts +16 -0
  173. package/dist/svelte/index.js +4838 -420
  174. package/dist/telephony/contract.d.ts +61 -0
  175. package/dist/telephony/matrix.d.ts +97 -0
  176. package/dist/telephony/plivo.d.ts +274 -0
  177. package/dist/telephony/telnyx.d.ts +247 -0
  178. package/dist/telephony/twilio.d.ts +135 -2
  179. package/dist/telephonyOutcome.d.ts +273 -0
  180. package/dist/testing/index.d.ts +1 -0
  181. package/dist/testing/index.js +2047 -42
  182. package/dist/testing/ioProviderSimulator.d.ts +41 -0
  183. package/dist/toolContract.d.ts +161 -0
  184. package/dist/toolRuntime.d.ts +50 -0
  185. package/dist/trace.d.ts +19 -1
  186. package/dist/traceDeliveryRoutes.d.ts +86 -0
  187. package/dist/traceTimeline.d.ts +97 -0
  188. package/dist/turnLatency.d.ts +95 -0
  189. package/dist/turnQuality.d.ts +94 -0
  190. package/dist/types.d.ts +158 -3
  191. package/dist/vue/VoiceDeliveryRuntime.d.ts +30 -0
  192. package/dist/vue/VoiceOpsActionCenter.d.ts +13 -0
  193. package/dist/vue/VoiceOpsStatus.d.ts +30 -0
  194. package/dist/vue/VoicePlatformCoverage.d.ts +23 -0
  195. package/dist/vue/VoiceProofTrends.d.ts +21 -0
  196. package/dist/vue/VoiceProviderCapabilities.d.ts +51 -0
  197. package/dist/vue/VoiceProviderContracts.d.ts +21 -0
  198. package/dist/vue/VoiceProviderSimulationControls.d.ts +88 -0
  199. package/dist/vue/VoiceProviderStatus.d.ts +51 -0
  200. package/dist/vue/VoiceRoutingStatus.d.ts +51 -0
  201. package/dist/vue/VoiceTurnLatency.d.ts +69 -0
  202. package/dist/vue/VoiceTurnQuality.d.ts +51 -0
  203. package/dist/vue/index.d.ts +28 -0
  204. package/dist/vue/index.js +4553 -55
  205. package/dist/vue/useVoiceAgentSquadStatus.d.ts +9 -0
  206. package/dist/vue/useVoiceCampaignDialerProof.d.ts +11 -0
  207. package/dist/vue/useVoiceController.d.ts +2 -1
  208. package/dist/vue/useVoiceDeliveryRuntime.d.ts +13 -0
  209. package/dist/vue/useVoiceLiveOps.d.ts +9 -0
  210. package/dist/vue/useVoiceOpsActionCenter.d.ts +11 -0
  211. package/dist/vue/useVoiceOpsStatus.d.ts +9 -0
  212. package/dist/vue/useVoicePlatformCoverage.d.ts +9 -0
  213. package/dist/vue/useVoiceProofTrends.d.ts +9 -0
  214. package/dist/vue/useVoiceProviderCapabilities.d.ts +9 -0
  215. package/dist/vue/useVoiceProviderContracts.d.ts +9 -0
  216. package/dist/vue/useVoiceProviderSimulationControls.d.ts +24 -0
  217. package/dist/vue/useVoiceProviderStatus.d.ts +1 -1
  218. package/dist/vue/useVoiceRoutingStatus.d.ts +8 -0
  219. package/dist/vue/useVoiceStream.d.ts +4 -1
  220. package/dist/vue/useVoiceTraceTimeline.d.ts +9 -0
  221. package/dist/vue/useVoiceTurnLatency.d.ts +10 -0
  222. package/dist/vue/useVoiceTurnQuality.d.ts +9 -0
  223. package/dist/vue/useVoiceWorkflowStatus.d.ts +9 -0
  224. package/dist/workflowContract.d.ts +91 -0
  225. package/package.json +1 -1
@@ -69,1287 +69,3522 @@ var __decorateElement = (array, flags, name, decorators, target, extra) => {
69
69
  return k || __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
70
70
  };
71
71
 
72
- // src/angular/voice-stream.service.ts
72
+ // src/angular/voice-ops-status.service.ts
73
73
  import { computed, Injectable, signal } from "@angular/core";
74
74
 
75
- // src/client/actions.ts
76
- var normalizeErrorMessage = (value) => {
77
- if (typeof value === "string" && value.trim()) {
78
- return value;
79
- }
80
- if (value instanceof Error && value.message.trim()) {
81
- return value.message;
75
+ // src/client/opsStatus.ts
76
+ var fetchVoiceOpsStatus = async (path = "/api/voice/ops-status", options = {}) => {
77
+ const fetchImpl = options.fetch ?? globalThis.fetch;
78
+ const response = await fetchImpl(path);
79
+ if (!response.ok) {
80
+ throw new Error(`Voice ops status failed: HTTP ${response.status}`);
82
81
  }
83
- if (value && typeof value === "object") {
84
- const record = value;
85
- for (const key of ["message", "reason", "description"]) {
86
- const candidate = record[key];
87
- if (typeof candidate === "string" && candidate.trim()) {
88
- return candidate;
89
- }
90
- }
91
- if ("error" in record) {
92
- return normalizeErrorMessage(record.error);
82
+ return await response.json();
83
+ };
84
+ var createVoiceOpsStatusStore = (path = "/api/voice/ops-status", options = {}) => {
85
+ const listeners = new Set;
86
+ let closed = false;
87
+ let timer;
88
+ let snapshot = {
89
+ error: null,
90
+ isLoading: false
91
+ };
92
+ const emit = () => {
93
+ for (const listener of listeners) {
94
+ listener();
93
95
  }
94
- if ("cause" in record) {
95
- return normalizeErrorMessage(record.cause);
96
+ };
97
+ const refresh = async () => {
98
+ if (closed) {
99
+ return snapshot.report;
96
100
  }
101
+ snapshot = {
102
+ ...snapshot,
103
+ error: null,
104
+ isLoading: true
105
+ };
106
+ emit();
97
107
  try {
98
- return JSON.stringify(value);
99
- } catch {}
100
- }
101
- return "Unexpected error";
102
- };
103
- var serverMessageToAction = (message) => {
104
- switch (message.type) {
105
- case "audio":
106
- return {
107
- chunk: Uint8Array.from(atob(message.chunkBase64), (char) => char.charCodeAt(0)),
108
- format: message.format,
109
- receivedAt: message.receivedAt,
110
- turnId: message.turnId,
111
- type: "audio"
112
- };
113
- case "assistant":
114
- return {
115
- text: message.text,
116
- type: "assistant"
117
- };
118
- case "complete":
119
- return {
120
- sessionId: message.sessionId,
121
- type: "complete"
122
- };
123
- case "call_lifecycle":
124
- return {
125
- event: message.event,
126
- sessionId: message.sessionId,
127
- type: "call_lifecycle"
128
- };
129
- case "error":
130
- return {
131
- message: normalizeErrorMessage(message.message),
132
- type: "error"
133
- };
134
- case "final":
135
- return {
136
- transcript: message.transcript,
137
- type: "final"
138
- };
139
- case "partial":
140
- return {
141
- transcript: message.transcript,
142
- type: "partial"
143
- };
144
- case "session":
145
- return {
146
- sessionId: message.sessionId,
147
- scenarioId: message.scenarioId,
148
- status: message.status,
149
- type: "session"
108
+ const report = await fetchVoiceOpsStatus(path, options);
109
+ snapshot = {
110
+ error: null,
111
+ isLoading: false,
112
+ report,
113
+ updatedAt: Date.now()
150
114
  };
151
- case "turn":
152
- return {
153
- turn: message.turn,
154
- type: "turn"
115
+ emit();
116
+ return report;
117
+ } catch (error) {
118
+ snapshot = {
119
+ ...snapshot,
120
+ error: error instanceof Error ? error.message : String(error),
121
+ isLoading: false
155
122
  };
156
- default:
157
- return null;
123
+ emit();
124
+ throw error;
125
+ }
126
+ };
127
+ const close = () => {
128
+ closed = true;
129
+ if (timer) {
130
+ clearInterval(timer);
131
+ timer = undefined;
132
+ }
133
+ listeners.clear();
134
+ };
135
+ if (typeof window !== "undefined" && options.intervalMs && options.intervalMs > 0) {
136
+ timer = setInterval(() => {
137
+ refresh().catch(() => {});
138
+ }, options.intervalMs);
158
139
  }
140
+ return {
141
+ close,
142
+ getServerSnapshot: () => snapshot,
143
+ getSnapshot: () => snapshot,
144
+ refresh,
145
+ subscribe: (listener) => {
146
+ listeners.add(listener);
147
+ return () => {
148
+ listeners.delete(listener);
149
+ };
150
+ }
151
+ };
159
152
  };
160
153
 
161
- // src/client/connection.ts
162
- var WS_OPEN = 1;
163
- var WS_CLOSED = 3;
164
- var WS_NORMAL_CLOSURE = 1000;
165
- var DEFAULT_MAX_RECONNECT_ATTEMPTS = 10;
166
- var DEFAULT_PING_INTERVAL = 30000;
167
- var RECONNECT_DELAY_MS = 500;
168
- var DEFAULT_SCENARIO_QUERY_PARAM = "scenarioId";
169
- var noop = () => {};
170
- var noopUnsubscribe = () => noop;
171
- var NOOP_CONNECTION = {
172
- callControl: noop,
173
- close: noop,
174
- endTurn: noop,
175
- getReadyState: () => WS_CLOSED,
176
- getScenarioId: () => "",
177
- getSessionId: () => "",
178
- send: noop,
179
- sendAudio: noop,
180
- start: () => {},
181
- subscribe: noopUnsubscribe
182
- };
183
- var createSessionId = () => crypto.randomUUID();
184
- var buildWsUrl = (path, sessionId, scenarioId) => {
185
- const { hostname, port, protocol } = window.location;
186
- const wsProtocol = protocol === "https:" ? "wss:" : "ws:";
187
- const portSuffix = port ? `:${port}` : "";
188
- const url = new URL(`${wsProtocol}//${hostname}${portSuffix}${path}`);
189
- url.searchParams.set("sessionId", sessionId);
190
- if (scenarioId) {
191
- url.searchParams.set(DEFAULT_SCENARIO_QUERY_PARAM, scenarioId);
192
- }
193
- return url.toString();
194
- };
195
- var isVoiceServerMessage = (value) => {
196
- if (!value || typeof value !== "object" || !("type" in value)) {
197
- return false;
198
- }
199
- switch (value.type) {
200
- case "audio":
201
- case "assistant":
202
- case "call_lifecycle":
203
- case "complete":
204
- case "error":
205
- case "final":
206
- case "partial":
207
- case "pong":
208
- case "session":
209
- case "turn":
210
- return true;
211
- default:
212
- return false;
213
- }
214
- };
215
- var parseServerMessage = (event) => {
216
- if (typeof event.data !== "string") {
217
- return null;
154
+ // src/angular/voice-ops-status.service.ts
155
+ var _dec = [
156
+ Injectable({ providedIn: "root" })
157
+ ];
158
+ var _init = __decoratorStart(undefined);
159
+
160
+ class VoiceOpsStatusService {
161
+ connect(path = "/api/voice/ops-status", options = {}) {
162
+ const store = createVoiceOpsStatusStore(path, options);
163
+ const errorSignal = signal(null);
164
+ const isLoadingSignal = signal(false);
165
+ const reportSignal = signal(undefined);
166
+ const updatedAtSignal = signal(undefined);
167
+ const sync = () => {
168
+ const snapshot = store.getSnapshot();
169
+ errorSignal.set(snapshot.error);
170
+ isLoadingSignal.set(snapshot.isLoading);
171
+ reportSignal.set(snapshot.report);
172
+ updatedAtSignal.set(snapshot.updatedAt);
173
+ };
174
+ const unsubscribe = store.subscribe(sync);
175
+ sync();
176
+ if (typeof window !== "undefined") {
177
+ store.refresh().catch(() => {});
178
+ }
179
+ return {
180
+ close: () => {
181
+ unsubscribe();
182
+ store.close();
183
+ },
184
+ error: computed(() => errorSignal()),
185
+ isLoading: computed(() => isLoadingSignal()),
186
+ refresh: store.refresh,
187
+ report: computed(() => reportSignal()),
188
+ updatedAt: computed(() => updatedAtSignal())
189
+ };
218
190
  }
219
- try {
220
- const parsed = JSON.parse(event.data);
221
- return isVoiceServerMessage(parsed) ? parsed : null;
222
- } catch {
223
- return null;
191
+ }
192
+ VoiceOpsStatusService = __decorateElement(_init, 0, "VoiceOpsStatusService", _dec, VoiceOpsStatusService);
193
+ __runInitializers(_init, 1, VoiceOpsStatusService);
194
+ __decoratorMetadata(_init, VoiceOpsStatusService);
195
+ let _VoiceOpsStatusService = VoiceOpsStatusService;
196
+ // src/angular/voice-platform-coverage.service.ts
197
+ import { computed as computed2, Injectable as Injectable2, signal as signal2 } from "@angular/core";
198
+
199
+ // src/client/platformCoverage.ts
200
+ var fetchVoicePlatformCoverage = async (path = "/api/voice/platform-coverage", options = {}) => {
201
+ const fetchImpl = options.fetch ?? globalThis.fetch;
202
+ const response = await fetchImpl(path);
203
+ if (!response.ok) {
204
+ throw new Error(`Voice platform coverage failed: HTTP ${response.status}`);
224
205
  }
206
+ return await response.json();
225
207
  };
226
- var createVoiceConnection = (path, options = {}) => {
227
- if (typeof window === "undefined") {
228
- return NOOP_CONNECTION;
229
- }
208
+ var createVoicePlatformCoverageStore = (path = "/api/voice/platform-coverage", options = {}) => {
230
209
  const listeners = new Set;
231
- const shouldReconnect = options.reconnect !== false;
232
- const maxReconnectAttempts = options.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS;
233
- const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
234
- const state = {
235
- isConnected: false,
236
- pendingMessages: [],
237
- scenarioId: options.scenarioId ?? null,
238
- pingInterval: null,
239
- reconnectAttempts: 0,
240
- reconnectTimeout: null,
241
- sessionId: options.sessionId ?? createSessionId(),
242
- ws: null
210
+ let closed = false;
211
+ let timer;
212
+ let snapshot = {
213
+ error: null,
214
+ isLoading: false
243
215
  };
244
- const clearTimers = () => {
245
- if (state.pingInterval) {
246
- clearInterval(state.pingInterval);
247
- state.pingInterval = null;
248
- }
249
- if (state.reconnectTimeout) {
250
- clearTimeout(state.reconnectTimeout);
251
- state.reconnectTimeout = null;
216
+ const emit = () => {
217
+ for (const listener of listeners) {
218
+ listener();
252
219
  }
253
220
  };
254
- const flushPendingMessages = () => {
255
- if (state.ws?.readyState !== WS_OPEN) {
256
- return;
221
+ const refresh = async () => {
222
+ if (closed) {
223
+ return snapshot.report;
257
224
  }
258
- while (state.pendingMessages.length > 0) {
259
- const next = state.pendingMessages.shift();
260
- if (next !== undefined) {
261
- state.ws.send(next);
262
- }
225
+ snapshot = {
226
+ ...snapshot,
227
+ error: null,
228
+ isLoading: true
229
+ };
230
+ emit();
231
+ try {
232
+ const report = await fetchVoicePlatformCoverage(path, options);
233
+ snapshot = {
234
+ error: null,
235
+ isLoading: false,
236
+ report,
237
+ updatedAt: Date.now()
238
+ };
239
+ emit();
240
+ return report;
241
+ } catch (error) {
242
+ snapshot = {
243
+ ...snapshot,
244
+ error: error instanceof Error ? error.message : String(error),
245
+ isLoading: false
246
+ };
247
+ emit();
248
+ throw error;
263
249
  }
264
250
  };
265
- const scheduleReconnect = () => {
266
- state.reconnectAttempts += 1;
267
- state.reconnectTimeout = setTimeout(() => {
268
- if (state.reconnectAttempts > maxReconnectAttempts) {
269
- return;
270
- }
271
- connect();
272
- }, RECONNECT_DELAY_MS);
251
+ const close = () => {
252
+ closed = true;
253
+ if (timer) {
254
+ clearInterval(timer);
255
+ timer = undefined;
256
+ }
257
+ listeners.clear();
273
258
  };
274
- const connect = () => {
275
- const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId));
276
- ws.binaryType = "arraybuffer";
277
- ws.onopen = () => {
278
- state.isConnected = true;
279
- state.reconnectAttempts = 0;
280
- flushPendingMessages();
281
- listeners.forEach((listener) => listener({
282
- scenarioId: state.scenarioId ?? undefined,
283
- sessionId: state.sessionId,
284
- status: "active",
285
- type: "session"
286
- }));
287
- state.pingInterval = setInterval(() => {
288
- if (ws.readyState === WS_OPEN) {
289
- ws.send(JSON.stringify({ type: "ping" }));
290
- }
291
- }, pingInterval);
292
- };
293
- ws.onmessage = (event) => {
294
- const parsed = parseServerMessage(event);
295
- if (!parsed) {
296
- return;
297
- }
298
- if (parsed.type === "session") {
299
- state.sessionId = parsed.sessionId;
300
- state.scenarioId = parsed.scenarioId ?? state.scenarioId;
301
- }
302
- listeners.forEach((listener) => listener(parsed));
259
+ if (typeof window !== "undefined" && options.intervalMs && options.intervalMs > 0) {
260
+ timer = setInterval(() => {
261
+ refresh().catch(() => {});
262
+ }, options.intervalMs);
263
+ }
264
+ return {
265
+ close,
266
+ getServerSnapshot: () => snapshot,
267
+ getSnapshot: () => snapshot,
268
+ refresh,
269
+ subscribe: (listener) => {
270
+ listeners.add(listener);
271
+ return () => {
272
+ listeners.delete(listener);
273
+ };
274
+ }
275
+ };
276
+ };
277
+
278
+ // src/angular/voice-platform-coverage.service.ts
279
+ var _dec = [
280
+ Injectable2({ providedIn: "root" })
281
+ ];
282
+ var _init = __decoratorStart(undefined);
283
+
284
+ class VoicePlatformCoverageService {
285
+ connect(path = "/api/voice/platform-coverage", options = {}) {
286
+ const store = createVoicePlatformCoverageStore(path, options);
287
+ const errorSignal = signal2(null);
288
+ const isLoadingSignal = signal2(false);
289
+ const reportSignal = signal2(undefined);
290
+ const updatedAtSignal = signal2(undefined);
291
+ const sync = () => {
292
+ const snapshot = store.getSnapshot();
293
+ errorSignal.set(snapshot.error);
294
+ isLoadingSignal.set(snapshot.isLoading);
295
+ reportSignal.set(snapshot.report);
296
+ updatedAtSignal.set(snapshot.updatedAt);
303
297
  };
304
- ws.onclose = (event) => {
305
- state.isConnected = false;
306
- clearTimers();
307
- const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && state.reconnectAttempts < maxReconnectAttempts;
308
- if (reconnectable) {
309
- scheduleReconnect();
310
- }
298
+ const unsubscribe = store.subscribe(sync);
299
+ sync();
300
+ if (typeof window !== "undefined") {
301
+ store.refresh().catch(() => {});
302
+ }
303
+ return {
304
+ close: () => {
305
+ unsubscribe();
306
+ store.close();
307
+ },
308
+ error: computed2(() => errorSignal()),
309
+ isLoading: computed2(() => isLoadingSignal()),
310
+ refresh: store.refresh,
311
+ report: computed2(() => reportSignal()),
312
+ updatedAt: computed2(() => updatedAtSignal())
311
313
  };
312
- state.ws = ws;
314
+ }
315
+ }
316
+ VoicePlatformCoverageService = __decorateElement(_init, 0, "VoicePlatformCoverageService", _dec, VoicePlatformCoverageService);
317
+ __runInitializers(_init, 1, VoicePlatformCoverageService);
318
+ __decoratorMetadata(_init, VoicePlatformCoverageService);
319
+ let _VoicePlatformCoverageService = VoicePlatformCoverageService;
320
+ // src/angular/voice-proof-trends.service.ts
321
+ import { computed as computed3, Injectable as Injectable3, signal as signal3 } from "@angular/core";
322
+
323
+ // src/client/proofTrends.ts
324
+ var fetchVoiceProofTrends = async (path = "/api/voice/proof-trends", options = {}) => {
325
+ const fetchImpl = options.fetch ?? globalThis.fetch;
326
+ const response = await fetchImpl(path);
327
+ if (!response.ok) {
328
+ throw new Error(`Voice proof trends failed: HTTP ${response.status}`);
329
+ }
330
+ return await response.json();
331
+ };
332
+ var createVoiceProofTrendsStore = (path = "/api/voice/proof-trends", options = {}) => {
333
+ const listeners = new Set;
334
+ let closed = false;
335
+ let timer;
336
+ let snapshot = {
337
+ error: null,
338
+ isLoading: false
313
339
  };
314
- const sendSerialized = (value) => {
315
- if (state.ws?.readyState === WS_OPEN) {
316
- state.ws.send(value);
317
- return;
340
+ const emit = () => {
341
+ for (const listener of listeners) {
342
+ listener();
318
343
  }
319
- state.pendingMessages.push(value);
320
- };
321
- const send = (message) => {
322
- sendSerialized(JSON.stringify(message));
323
344
  };
324
- const start = (input = {}) => {
325
- if (input.sessionId) {
326
- state.sessionId = input.sessionId;
345
+ const refresh = async () => {
346
+ if (closed) {
347
+ return snapshot.report;
327
348
  }
328
- if (input.scenarioId) {
329
- state.scenarioId = input.scenarioId;
349
+ snapshot = {
350
+ ...snapshot,
351
+ error: null,
352
+ isLoading: true
353
+ };
354
+ emit();
355
+ try {
356
+ const report = await fetchVoiceProofTrends(path, options);
357
+ snapshot = {
358
+ error: null,
359
+ isLoading: false,
360
+ report,
361
+ updatedAt: Date.now()
362
+ };
363
+ emit();
364
+ return report;
365
+ } catch (error) {
366
+ snapshot = {
367
+ ...snapshot,
368
+ error: error instanceof Error ? error.message : String(error),
369
+ isLoading: false
370
+ };
371
+ emit();
372
+ throw error;
330
373
  }
331
- send({
332
- type: "start",
333
- sessionId: state.sessionId,
334
- scenarioId: state.scenarioId ?? undefined
335
- });
336
- };
337
- const sendAudio = (audio) => {
338
- sendSerialized(audio);
339
- };
340
- const endTurn = () => {
341
- send({ type: "end_turn" });
342
- };
343
- const callControl = (message) => {
344
- send({
345
- ...message,
346
- type: "call_control"
347
- });
348
374
  };
349
375
  const close = () => {
350
- clearTimers();
351
- if (state.ws) {
352
- state.ws.close(WS_NORMAL_CLOSURE);
353
- state.ws = null;
376
+ closed = true;
377
+ if (timer) {
378
+ clearInterval(timer);
379
+ timer = undefined;
354
380
  }
355
- state.isConnected = false;
356
381
  listeners.clear();
357
382
  };
358
- const subscribe = (callback) => {
359
- listeners.add(callback);
360
- return () => {
361
- listeners.delete(callback);
362
- };
363
- };
364
- connect();
383
+ if (typeof window !== "undefined" && options.intervalMs && options.intervalMs > 0) {
384
+ timer = setInterval(() => {
385
+ refresh().catch(() => {});
386
+ }, options.intervalMs);
387
+ }
365
388
  return {
366
- callControl,
367
389
  close,
368
- endTurn,
369
- getReadyState: () => state.ws?.readyState ?? WS_CLOSED,
370
- getScenarioId: () => state.scenarioId ?? "",
371
- getSessionId: () => state.sessionId,
372
- send,
373
- sendAudio,
374
- start,
375
- subscribe
390
+ getServerSnapshot: () => snapshot,
391
+ getSnapshot: () => snapshot,
392
+ refresh,
393
+ subscribe: (listener) => {
394
+ listeners.add(listener);
395
+ return () => {
396
+ listeners.delete(listener);
397
+ };
398
+ }
376
399
  };
377
400
  };
378
401
 
379
- // src/client/store.ts
380
- var createInitialState = () => ({
381
- assistantAudio: [],
382
- assistantTexts: [],
383
- call: null,
384
- error: null,
385
- isConnected: false,
386
- scenarioId: null,
387
- partial: "",
388
- sessionId: null,
389
- status: "idle",
390
- turns: []
391
- });
392
- var createVoiceStreamStore = () => {
393
- let state = createInitialState();
394
- const subscribers = new Set;
395
- const notify = () => {
396
- subscribers.forEach((subscriber) => subscriber());
397
- };
398
- const dispatch = (action) => {
399
- switch (action.type) {
400
- case "audio":
401
- state = {
402
- ...state,
403
- assistantAudio: [
404
- ...state.assistantAudio,
405
- {
406
- chunk: action.chunk,
407
- format: action.format,
408
- receivedAt: action.receivedAt,
409
- turnId: action.turnId
410
- }
411
- ]
412
- };
413
- break;
414
- case "assistant":
415
- state = {
416
- ...state,
417
- assistantTexts: [...state.assistantTexts, action.text]
418
- };
419
- break;
420
- case "complete":
421
- state = {
422
- ...state,
423
- sessionId: action.sessionId,
424
- status: "completed"
425
- };
426
- break;
427
- case "call_lifecycle":
428
- state = {
429
- ...state,
430
- call: {
431
- ...state.call,
432
- disposition: action.event.type === "end" ? action.event.disposition : state.call?.disposition,
433
- endedAt: action.event.type === "end" ? action.event.at : state.call?.endedAt,
434
- events: [...state.call?.events ?? [], action.event],
435
- lastEventAt: action.event.at,
436
- startedAt: state.call?.startedAt ?? action.event.at
437
- },
438
- sessionId: action.sessionId
439
- };
440
- break;
441
- case "connected":
442
- state = {
443
- ...state,
444
- isConnected: true
445
- };
446
- break;
447
- case "disconnected":
448
- state = {
449
- ...state,
450
- isConnected: false
451
- };
452
- break;
453
- case "error":
454
- state = {
455
- ...state,
456
- error: action.message
457
- };
458
- break;
459
- case "final":
460
- state = {
461
- ...state,
462
- partial: action.transcript.text,
463
- turns: state.turns.map((turn) => turn)
464
- };
465
- break;
466
- case "partial":
467
- state = {
468
- ...state,
469
- partial: action.transcript.text
470
- };
471
- break;
472
- case "session":
473
- state = {
474
- ...state,
475
- error: null,
476
- scenarioId: action.scenarioId ?? state.scenarioId,
477
- isConnected: action.status === "active",
478
- sessionId: action.sessionId,
479
- status: action.status
480
- };
481
- break;
482
- case "turn":
483
- state = {
484
- ...state,
485
- partial: "",
486
- turns: [...state.turns, action.turn]
487
- };
488
- break;
489
- }
490
- notify();
491
- };
492
- return {
493
- dispatch,
494
- getServerSnapshot: () => state,
495
- getSnapshot: () => state,
496
- subscribe: (subscriber) => {
497
- subscribers.add(subscriber);
498
- return () => {
499
- subscribers.delete(subscriber);
500
- };
501
- }
502
- };
503
- };
504
-
505
- // src/client/createVoiceStream.ts
506
- var createVoiceStream = (path, options = {}) => {
507
- const connection = createVoiceConnection(path, options);
508
- const store = createVoiceStreamStore();
509
- const subscribers = new Set;
510
- const start = (input) => Promise.resolve().then(() => {
511
- if (!input?.sessionId && !input?.scenarioId) {
512
- return;
513
- }
514
- connection.start(input);
515
- });
516
- const notify = () => {
517
- subscribers.forEach((subscriber) => subscriber());
518
- };
519
- const unsubscribeConnection = connection.subscribe((message) => {
520
- const action = serverMessageToAction(message);
521
- if (action) {
522
- store.dispatch(action);
523
- notify();
524
- }
525
- });
526
- return {
527
- callControl(message) {
528
- connection.callControl(message);
529
- },
530
- close() {
531
- unsubscribeConnection();
532
- connection.close();
533
- store.dispatch({ type: "disconnected" });
534
- notify();
535
- },
536
- endTurn() {
537
- connection.endTurn();
538
- },
539
- get error() {
540
- return store.getSnapshot().error;
541
- },
542
- getServerSnapshot() {
543
- return store.getServerSnapshot();
544
- },
545
- getSnapshot() {
546
- return store.getSnapshot();
547
- },
548
- get isConnected() {
549
- return store.getSnapshot().isConnected;
550
- },
551
- get scenarioId() {
552
- return store.getSnapshot().scenarioId;
553
- },
554
- start,
555
- get partial() {
556
- return store.getSnapshot().partial;
557
- },
558
- get sessionId() {
559
- return connection.getSessionId();
560
- },
561
- get status() {
562
- return store.getSnapshot().status;
563
- },
564
- get turns() {
565
- return store.getSnapshot().turns;
566
- },
567
- get assistantTexts() {
568
- return store.getSnapshot().assistantTexts;
569
- },
570
- get assistantAudio() {
571
- return store.getSnapshot().assistantAudio;
572
- },
573
- get call() {
574
- return store.getSnapshot().call;
575
- },
576
- sendAudio(audio) {
577
- connection.sendAudio(audio);
578
- },
579
- subscribe(subscriber) {
580
- subscribers.add(subscriber);
581
- return () => {
582
- subscribers.delete(subscriber);
583
- };
584
- }
585
- };
586
- };
587
-
588
- // src/angular/voice-stream.service.ts
402
+ // src/angular/voice-proof-trends.service.ts
589
403
  var _dec = [
590
- Injectable({ providedIn: "root" })
404
+ Injectable3({ providedIn: "root" })
591
405
  ];
592
406
  var _init = __decoratorStart(undefined);
593
407
 
594
- class VoiceStreamService {
595
- connect(path, options = {}) {
596
- const stream = createVoiceStream(path, options);
597
- const assistantAudioSignal = signal([]);
598
- const assistantTextsSignal = signal([]);
599
- const errorSignal = signal(null);
600
- const isConnectedSignal = signal(false);
601
- const partialSignal = signal("");
602
- const sessionIdSignal = signal(stream.sessionId);
603
- const statusSignal = signal(stream.status);
604
- const turnsSignal = signal([]);
408
+ class VoiceProofTrendsService {
409
+ connect(path = "/api/voice/proof-trends", options = {}) {
410
+ const store = createVoiceProofTrendsStore(path, options);
411
+ const errorSignal = signal3(null);
412
+ const isLoadingSignal = signal3(false);
413
+ const reportSignal = signal3(undefined);
414
+ const updatedAtSignal = signal3(undefined);
605
415
  const sync = () => {
606
- assistantAudioSignal.set([...stream.assistantAudio]);
607
- assistantTextsSignal.set([...stream.assistantTexts]);
608
- errorSignal.set(stream.error);
609
- isConnectedSignal.set(stream.isConnected);
610
- partialSignal.set(stream.partial);
611
- sessionIdSignal.set(stream.sessionId);
612
- statusSignal.set(stream.status);
613
- turnsSignal.set([...stream.turns]);
416
+ const snapshot = store.getSnapshot();
417
+ errorSignal.set(snapshot.error);
418
+ isLoadingSignal.set(snapshot.isLoading);
419
+ reportSignal.set(snapshot.report);
420
+ updatedAtSignal.set(snapshot.updatedAt);
614
421
  };
615
- const unsubscribe = stream.subscribe(sync);
422
+ const unsubscribe = store.subscribe(sync);
616
423
  sync();
424
+ if (typeof window !== "undefined") {
425
+ store.refresh().catch(() => {});
426
+ }
617
427
  return {
618
- assistantAudio: computed(() => assistantAudioSignal()),
619
- assistantTexts: computed(() => assistantTextsSignal()),
620
428
  close: () => {
621
429
  unsubscribe();
622
- stream.close();
430
+ store.close();
623
431
  },
624
- endTurn: () => stream.endTurn(),
625
- error: computed(() => errorSignal()),
626
- isConnected: computed(() => isConnectedSignal()),
627
- partial: computed(() => partialSignal()),
628
- sendAudio: (audio) => stream.sendAudio(audio),
629
- sessionId: computed(() => sessionIdSignal()),
630
- status: computed(() => statusSignal()),
631
- turns: computed(() => turnsSignal())
432
+ error: computed3(() => errorSignal()),
433
+ isLoading: computed3(() => isLoadingSignal()),
434
+ refresh: store.refresh,
435
+ report: computed3(() => reportSignal()),
436
+ updatedAt: computed3(() => updatedAtSignal())
632
437
  };
633
438
  }
634
439
  }
635
- VoiceStreamService = __decorateElement(_init, 0, "VoiceStreamService", _dec, VoiceStreamService);
636
- __runInitializers(_init, 1, VoiceStreamService);
637
- __decoratorMetadata(_init, VoiceStreamService);
638
- let _VoiceStreamService = VoiceStreamService;
639
- // src/angular/voice-controller.service.ts
640
- import { computed as computed2, Injectable as Injectable2, signal as signal2 } from "@angular/core";
440
+ VoiceProofTrendsService = __decorateElement(_init, 0, "VoiceProofTrendsService", _dec, VoiceProofTrendsService);
441
+ __runInitializers(_init, 1, VoiceProofTrendsService);
442
+ __decoratorMetadata(_init, VoiceProofTrendsService);
443
+ let _VoiceProofTrendsService = VoiceProofTrendsService;
444
+ // src/angular/voice-ops-action-center.service.ts
445
+ import { computed as computed4, Injectable as Injectable4, signal as signal4 } from "@angular/core";
641
446
 
642
- // src/client/htmx.ts
643
- var DEFAULT_EVENT_NAME = "voice-refresh";
644
- var DEFAULT_QUERY_PARAM = "sessionId";
645
- var resolveElement = (input) => {
646
- if (typeof input !== "string") {
647
- return input;
447
+ // src/client/opsActionCenter.ts
448
+ var recordVoiceOpsActionResult = async (result, options = {}) => {
449
+ if (options.auditPath === false) {
450
+ return;
451
+ }
452
+ const path = options.auditPath ?? "/api/voice/ops-actions/audit";
453
+ const fetchImpl = options.fetch ?? globalThis.fetch;
454
+ const response = await fetchImpl(path, {
455
+ body: JSON.stringify(result),
456
+ headers: {
457
+ "Content-Type": "application/json"
458
+ },
459
+ method: "POST"
460
+ });
461
+ if (!response.ok) {
462
+ throw new Error(`Voice ops action audit failed: HTTP ${response.status}`);
648
463
  }
649
- return document.querySelector(input);
650
464
  };
651
- var buildRoute = (element, route, queryParam, sessionId) => {
652
- const baseRoute = route ?? element.getAttribute("hx-get") ?? "";
653
- if (!baseRoute) {
654
- return "";
465
+ var createVoiceOpsActionCenterActions = (options = {}) => {
466
+ const deliveryRuntimePath = options.deliveryRuntimePath ?? "/api/voice-delivery-runtime";
467
+ const actions = [];
468
+ if (options.includeProductionReadiness !== false) {
469
+ actions.push({
470
+ description: "Refresh the production readiness report.",
471
+ id: "production-readiness",
472
+ label: "Refresh readiness",
473
+ method: "GET",
474
+ path: options.productionReadinessPath ?? "/api/production-readiness"
475
+ });
655
476
  }
656
- const url = new URL(baseRoute, window.location.origin);
657
- if (sessionId) {
658
- url.searchParams.set(queryParam, sessionId);
659
- } else {
660
- url.searchParams.delete(queryParam);
477
+ if (options.includeDeliveryRuntime !== false) {
478
+ actions.push({
479
+ description: "Drain pending and failed audit/trace deliveries.",
480
+ id: "delivery-runtime.tick",
481
+ label: "Tick delivery workers",
482
+ method: "POST",
483
+ path: `${deliveryRuntimePath.replace(/\/$/, "")}/tick`
484
+ }, {
485
+ description: "Move reviewed dead letters back to live delivery queues.",
486
+ id: "delivery-runtime.requeue-dead-letters",
487
+ label: "Requeue dead letters",
488
+ method: "POST",
489
+ path: `${deliveryRuntimePath.replace(/\/$/, "")}/requeue-dead-letters`
490
+ });
661
491
  }
662
- return `${url.pathname}${url.search}${url.hash}`;
492
+ if (options.includeTurnLatencyProof !== false) {
493
+ actions.push({
494
+ description: "Run the synthetic turn latency proof.",
495
+ id: "turn-latency.proof",
496
+ label: "Run latency proof",
497
+ method: "POST",
498
+ path: options.turnLatencyProofPath ?? "/api/turn-latency/proof"
499
+ });
500
+ }
501
+ if (options.includeProviderSimulation !== false) {
502
+ const pathPrefix = options.providerSimulationPathPrefix ?? "/api/stt-simulate";
503
+ for (const provider of options.providers ?? []) {
504
+ actions.push({
505
+ description: `Simulate ${provider} provider failure.`,
506
+ id: `provider.${provider}.failure`,
507
+ label: `Simulate ${provider} failure`,
508
+ method: "POST",
509
+ path: `${pathPrefix}/failure?provider=${encodeURIComponent(provider)}`
510
+ }, {
511
+ description: `Mark ${provider} provider recovered.`,
512
+ id: `provider.${provider}.recovery`,
513
+ label: `Recover ${provider}`,
514
+ method: "POST",
515
+ path: `${pathPrefix}/recovery?provider=${encodeURIComponent(provider)}`
516
+ });
517
+ }
518
+ }
519
+ return actions;
663
520
  };
664
- var bindVoiceHTMX = (stream, options) => {
665
- if (typeof window === "undefined" || typeof document === "undefined") {
666
- return () => {};
667
- }
668
- const element = resolveElement(options.element);
669
- if (!element) {
670
- return () => {};
521
+ var runVoiceOpsAction = async (action, options = {}) => {
522
+ const fetchImpl = options.fetch ?? globalThis.fetch;
523
+ const response = await fetchImpl(action.path, {
524
+ method: action.method ?? "POST"
525
+ });
526
+ const body = await response.json().catch(() => null);
527
+ if (!response.ok) {
528
+ const message = body && typeof body === "object" && "error" in body ? String(body.error) : `Voice ops action "${action.id}" failed: HTTP ${response.status}`;
529
+ throw new Error(message);
671
530
  }
672
- const eventName = options.eventName ?? DEFAULT_EVENT_NAME;
673
- const queryParam = options.sessionQueryParam ?? DEFAULT_QUERY_PARAM;
674
- const sync = () => {
675
- const htmxWindow = window;
676
- const nextRoute = buildRoute(element, options.route, queryParam, stream.sessionId);
677
- if (nextRoute) {
678
- element.setAttribute("hx-get", nextRoute);
531
+ return {
532
+ actionId: action.id,
533
+ body,
534
+ ok: response.ok,
535
+ ranAt: Date.now(),
536
+ status: response.status
537
+ };
538
+ };
539
+ var createVoiceOpsActionCenterStore = (options = {}) => {
540
+ const listeners = new Set;
541
+ let closed = false;
542
+ let timer;
543
+ let snapshot = {
544
+ actions: options.actions ?? createVoiceOpsActionCenterActions(),
545
+ error: null,
546
+ isRunning: false
547
+ };
548
+ const emit = () => {
549
+ for (const listener of listeners) {
550
+ listener();
679
551
  }
680
- htmxWindow.htmx?.process?.(element);
681
- htmxWindow.htmx?.trigger?.(element, eventName);
682
552
  };
683
- const unsubscribe = stream.subscribe(sync);
684
- sync();
685
- return () => {
686
- unsubscribe();
553
+ const setActions = (actions) => {
554
+ snapshot = { ...snapshot, actions, updatedAt: Date.now() };
555
+ emit();
687
556
  };
688
- };
689
-
690
- // src/client/microphone.ts
691
- var clampSample = (value) => Math.max(-1, Math.min(1, value));
692
- var floatTo16BitPCM = (input) => {
693
- const output = new Int16Array(input.length);
694
- for (let index = 0;index < input.length; index += 1) {
695
- const sample = clampSample(input[index] ?? 0);
696
- output[index] = sample < 0 ? sample * 32768 : sample * 32767;
697
- }
698
- return new Uint8Array(output.buffer);
699
- };
700
- var getPcmLevel = (audio) => {
701
- const bytes = audio instanceof Uint8Array ? audio : new Uint8Array(audio);
702
- if (bytes.byteLength < 2) {
703
- return 0;
704
- }
705
- const samples = new Int16Array(bytes.buffer, bytes.byteOffset, Math.floor(bytes.byteLength / 2));
706
- if (samples.length === 0) {
707
- return 0;
708
- }
709
- let sumSquares = 0;
710
- for (const sample of samples) {
711
- const normalized = sample / 32768;
712
- sumSquares += normalized * normalized;
713
- }
714
- return Math.min(1, Math.max(0, Math.sqrt(sumSquares / samples.length) * 5.5));
715
- };
716
- var downsampleBuffer = (input, sourceRate, targetRate) => {
717
- if (sourceRate === targetRate) {
718
- return input;
719
- }
720
- const ratio = sourceRate / targetRate;
721
- const length = Math.round(input.length / ratio);
722
- const output = new Float32Array(length);
723
- let offsetResult = 0;
724
- let offsetBuffer = 0;
725
- while (offsetResult < output.length) {
726
- const nextOffsetBuffer = Math.round((offsetResult + 1) * ratio);
727
- let accum = 0;
728
- let count = 0;
729
- for (let index = offsetBuffer;index < nextOffsetBuffer && index < input.length; index += 1) {
730
- accum += input[index] ?? 0;
731
- count += 1;
557
+ const run = async (actionId) => {
558
+ if (closed) {
559
+ return snapshot.lastResult;
732
560
  }
733
- output[offsetResult] = count > 0 ? accum / count : 0;
734
- offsetResult += 1;
735
- offsetBuffer = nextOffsetBuffer;
736
- }
737
- return output;
738
- };
739
- var createMicrophoneCapture = (options) => {
740
- let audioContext = null;
741
- let sourceNode = null;
742
- let processorNode = null;
743
- let mediaStream = null;
744
- const start = async () => {
745
- if (typeof navigator === "undefined" || !navigator.mediaDevices?.getUserMedia) {
746
- throw new Error("Browser microphone capture requires navigator.mediaDevices.getUserMedia.");
561
+ const action = snapshot.actions.find((item) => item.id === actionId);
562
+ if (!action) {
563
+ throw new Error(`Voice ops action "${actionId}" is not configured.`);
747
564
  }
748
- const AudioContextCtor = (typeof window !== "undefined" ? window.AudioContext ?? window.webkitAudioContext : undefined) ?? AudioContext;
749
- if (!AudioContextCtor) {
750
- throw new Error("Browser microphone capture requires AudioContext support.");
565
+ if (action.disabled) {
566
+ throw new Error(`Voice ops action "${actionId}" is disabled.`);
751
567
  }
752
- mediaStream = await navigator.mediaDevices.getUserMedia({
753
- audio: {
754
- channelCount: options.channelCount ?? 1
755
- }
756
- });
757
- audioContext = new AudioContextCtor;
758
- sourceNode = audioContext.createMediaStreamSource(mediaStream);
759
- processorNode = audioContext.createScriptProcessor(4096, 1, 1);
760
- processorNode.onaudioprocess = (event) => {
761
- const channel = event.inputBuffer.getChannelData(0);
762
- const downsampled = downsampleBuffer(channel, audioContext?.sampleRate ?? 48000, options.sampleRateHz ?? 16000);
763
- const pcm = floatTo16BitPCM(downsampled);
764
- options.onLevel?.(getPcmLevel(pcm));
765
- options.onAudio(pcm);
568
+ snapshot = {
569
+ ...snapshot,
570
+ error: null,
571
+ isRunning: true,
572
+ runningActionId: action.id
766
573
  };
767
- sourceNode.connect(processorNode);
768
- processorNode.connect(audioContext.destination);
574
+ emit();
575
+ try {
576
+ const result = await runVoiceOpsAction(action, options);
577
+ await options.onActionResult?.(result);
578
+ await recordVoiceOpsActionResult(result, options);
579
+ snapshot = {
580
+ ...snapshot,
581
+ error: null,
582
+ isRunning: false,
583
+ lastResult: result,
584
+ runningActionId: undefined,
585
+ updatedAt: Date.now()
586
+ };
587
+ emit();
588
+ return result;
589
+ } catch (error) {
590
+ const result = {
591
+ actionId: action.id,
592
+ body: null,
593
+ error: error instanceof Error ? error.message : String(error),
594
+ ok: false,
595
+ ranAt: Date.now(),
596
+ status: 0
597
+ };
598
+ await options.onActionResult?.(result);
599
+ await recordVoiceOpsActionResult(result, options).catch(() => {});
600
+ snapshot = {
601
+ ...snapshot,
602
+ error: error instanceof Error ? error.message : String(error),
603
+ isRunning: false,
604
+ runningActionId: undefined
605
+ };
606
+ emit();
607
+ throw error;
608
+ }
769
609
  };
770
- const stop = () => {
771
- processorNode?.disconnect();
772
- sourceNode?.disconnect();
773
- mediaStream?.getTracks().forEach((track) => track.stop());
774
- audioContext?.close();
775
- options.onLevel?.(0);
776
- audioContext = null;
777
- mediaStream = null;
778
- processorNode = null;
779
- sourceNode = null;
610
+ const close = () => {
611
+ closed = true;
612
+ if (timer) {
613
+ clearInterval(timer);
614
+ timer = undefined;
615
+ }
616
+ listeners.clear();
780
617
  };
781
- return { start, stop };
782
- };
783
-
784
- // src/audioConditioning.ts
785
- var DEFAULT_TARGET_LEVEL = 0.08;
786
- var DEFAULT_MAX_GAIN = 3;
787
- var DEFAULT_NOISE_GATE_THRESHOLD = 0.006;
788
- var DEFAULT_NOISE_GATE_ATTENUATION = 0.15;
789
- var toInt16Array = (audio) => {
790
- if (audio instanceof ArrayBuffer) {
791
- return new Int16Array(audio, 0, Math.floor(audio.byteLength / 2));
792
- }
793
- return new Int16Array(audio.buffer, audio.byteOffset, Math.floor(audio.byteLength / 2));
794
- };
795
- var computeRms = (samples) => {
796
- if (samples.length === 0) {
797
- return 0;
798
- }
799
- let sumSquares = 0;
800
- for (const sample of samples) {
801
- const normalized = sample / 32768;
802
- sumSquares += normalized * normalized;
803
- }
804
- return Math.sqrt(sumSquares / samples.length);
805
- };
806
- var resolveAudioConditioningConfig = (config) => {
807
- if (!config || config.enabled === false) {
808
- return;
618
+ if (options.intervalMs && options.intervalMs > 0) {
619
+ timer = setInterval(() => {
620
+ emit();
621
+ }, options.intervalMs);
809
622
  }
810
623
  return {
811
- enabled: true,
812
- maxGain: config.maxGain ?? DEFAULT_MAX_GAIN,
813
- noiseGateAttenuation: config.noiseGateAttenuation ?? DEFAULT_NOISE_GATE_ATTENUATION,
814
- noiseGateThreshold: config.noiseGateThreshold ?? DEFAULT_NOISE_GATE_THRESHOLD,
815
- targetLevel: config.targetLevel ?? DEFAULT_TARGET_LEVEL
624
+ close,
625
+ getServerSnapshot: () => snapshot,
626
+ getSnapshot: () => snapshot,
627
+ run,
628
+ setActions,
629
+ subscribe: (listener) => {
630
+ listeners.add(listener);
631
+ return () => {
632
+ listeners.delete(listener);
633
+ };
634
+ }
816
635
  };
817
636
  };
818
- var conditionAudioChunk = (audio, config) => {
819
- if (!config) {
820
- return audio;
637
+
638
+ // src/angular/voice-ops-action-center.service.ts
639
+ var _dec = [
640
+ Injectable4({ providedIn: "root" })
641
+ ];
642
+ var _init = __decoratorStart(undefined);
643
+
644
+ class VoiceOpsActionCenterService {
645
+ connect(options = {}) {
646
+ const store = createVoiceOpsActionCenterStore(options);
647
+ const actionsSignal = signal4([]);
648
+ const errorSignal = signal4(null);
649
+ const isRunningSignal = signal4(false);
650
+ const lastResultSignal = signal4(undefined);
651
+ const runningActionIdSignal = signal4(undefined);
652
+ const sync = () => {
653
+ const snapshot = store.getSnapshot();
654
+ actionsSignal.set(snapshot.actions);
655
+ errorSignal.set(snapshot.error);
656
+ isRunningSignal.set(snapshot.isRunning);
657
+ lastResultSignal.set(snapshot.lastResult);
658
+ runningActionIdSignal.set(snapshot.runningActionId);
659
+ };
660
+ const unsubscribe = store.subscribe(sync);
661
+ sync();
662
+ return {
663
+ actions: computed4(() => actionsSignal()),
664
+ close: () => {
665
+ unsubscribe();
666
+ store.close();
667
+ },
668
+ error: computed4(() => errorSignal()),
669
+ isRunning: computed4(() => isRunningSignal()),
670
+ lastResult: computed4(() => lastResultSignal()),
671
+ run: store.run,
672
+ runningActionId: computed4(() => runningActionIdSignal()),
673
+ setActions: store.setActions
674
+ };
821
675
  }
822
- const source = toInt16Array(audio);
823
- if (source.length === 0) {
824
- return audio;
676
+ }
677
+ VoiceOpsActionCenterService = __decorateElement(_init, 0, "VoiceOpsActionCenterService", _dec, VoiceOpsActionCenterService);
678
+ __runInitializers(_init, 1, VoiceOpsActionCenterService);
679
+ __decoratorMetadata(_init, VoiceOpsActionCenterService);
680
+ let _VoiceOpsActionCenterService = VoiceOpsActionCenterService;
681
+ // src/angular/voice-live-ops.service.ts
682
+ import { computed as computed5, Injectable as Injectable5, signal as signal5 } from "@angular/core";
683
+
684
+ // src/client/liveOps.ts
685
+ var postVoiceLiveOpsAction = async (input, options = {}) => {
686
+ if (!input.sessionId) {
687
+ throw new Error("Start a voice session before running live ops actions.");
825
688
  }
826
- const rms = computeRms(source);
827
- const output = new Int16Array(source.length);
828
- const gateFactor = rms < config.noiseGateThreshold ? config.noiseGateAttenuation : 1;
829
- const baseLevel = Math.max(rms * gateFactor, 0.000001);
830
- const gain = Math.min(config.maxGain, config.targetLevel / baseLevel);
831
- const appliedGain = Math.max(0.25, gain) * gateFactor;
832
- for (let index = 0;index < source.length; index += 1) {
833
- const next = Math.round(source[index] * appliedGain);
834
- output[index] = Math.max(-32768, Math.min(32767, next));
689
+ const fetchImpl = options.fetch ?? globalThis.fetch;
690
+ const response = await fetchImpl(options.actionPath ?? "/api/voice/live-ops/action", {
691
+ body: JSON.stringify(input),
692
+ headers: {
693
+ "Content-Type": "application/json"
694
+ },
695
+ method: "POST"
696
+ });
697
+ const payload = await response.json().catch(() => null);
698
+ if (!response.ok || !payload?.ok) {
699
+ const message = payload && typeof payload === "object" && "error" in payload ? String(payload.error) : `Voice live ops action failed: HTTP ${response.status}`;
700
+ throw new Error(message);
835
701
  }
836
- return new Uint8Array(output.buffer);
702
+ return payload;
837
703
  };
838
-
839
- // src/turnProfiles.ts
840
- var TURN_PROFILE_DEFAULTS = {
841
- balanced: {
842
- qualityProfile: "general",
843
- silenceMs: 1400,
844
- speechThreshold: 0.012,
845
- transcriptStabilityMs: 1000
846
- },
847
- fast: {
848
- qualityProfile: "general",
849
- silenceMs: 700,
850
- speechThreshold: 0.015,
851
- transcriptStabilityMs: 450
852
- },
853
- "long-form": {
854
- qualityProfile: "general",
855
- silenceMs: 2200,
856
- speechThreshold: 0.01,
857
- transcriptStabilityMs: 1500
704
+ var createVoiceLiveOpsStore = (options = {}) => {
705
+ const listeners = new Set;
706
+ let closed = false;
707
+ let snapshot = {
708
+ error: null,
709
+ isRunning: false
710
+ };
711
+ const emit = () => {
712
+ for (const listener of listeners) {
713
+ listener();
714
+ }
715
+ };
716
+ const run = async (input) => {
717
+ if (closed) {
718
+ return snapshot.lastResult;
719
+ }
720
+ snapshot = {
721
+ ...snapshot,
722
+ error: null,
723
+ isRunning: true,
724
+ runningAction: input.action
725
+ };
726
+ emit();
727
+ try {
728
+ const result = await postVoiceLiveOpsAction(input, options);
729
+ await options.onControl?.(result);
730
+ snapshot = {
731
+ ...snapshot,
732
+ error: null,
733
+ isRunning: false,
734
+ lastResult: result,
735
+ runningAction: undefined,
736
+ updatedAt: Date.now()
737
+ };
738
+ emit();
739
+ return result;
740
+ } catch (error) {
741
+ snapshot = {
742
+ ...snapshot,
743
+ error: error instanceof Error ? error.message : String(error),
744
+ isRunning: false,
745
+ runningAction: undefined,
746
+ updatedAt: Date.now()
747
+ };
748
+ emit();
749
+ throw error;
750
+ }
751
+ };
752
+ const close = () => {
753
+ closed = true;
754
+ listeners.clear();
755
+ };
756
+ return {
757
+ close,
758
+ getServerSnapshot: () => snapshot,
759
+ getSnapshot: () => snapshot,
760
+ run,
761
+ subscribe: (listener) => {
762
+ listeners.add(listener);
763
+ return () => {
764
+ listeners.delete(listener);
765
+ };
766
+ }
767
+ };
768
+ };
769
+
770
+ // src/angular/voice-live-ops.service.ts
771
+ var _dec = [
772
+ Injectable5({ providedIn: "root" })
773
+ ];
774
+ var _init = __decoratorStart(undefined);
775
+
776
+ class VoiceLiveOpsService {
777
+ connect(options = {}) {
778
+ const store = createVoiceLiveOpsStore(options);
779
+ const errorSignal = signal5(null);
780
+ const isRunningSignal = signal5(false);
781
+ const lastResultSignal = signal5(undefined);
782
+ const runningActionSignal = signal5(undefined);
783
+ const sync = () => {
784
+ const snapshot = store.getSnapshot();
785
+ errorSignal.set(snapshot.error);
786
+ isRunningSignal.set(snapshot.isRunning);
787
+ lastResultSignal.set(snapshot.lastResult);
788
+ runningActionSignal.set(snapshot.runningAction);
789
+ };
790
+ const unsubscribe = store.subscribe(sync);
791
+ sync();
792
+ return {
793
+ close: () => {
794
+ unsubscribe();
795
+ store.close();
796
+ },
797
+ error: computed5(() => errorSignal()),
798
+ isRunning: computed5(() => isRunningSignal()),
799
+ lastResult: computed5(() => lastResultSignal()),
800
+ run: store.run,
801
+ runningAction: computed5(() => runningActionSignal())
802
+ };
803
+ }
804
+ }
805
+ VoiceLiveOpsService = __decorateElement(_init, 0, "VoiceLiveOpsService", _dec, VoiceLiveOpsService);
806
+ __runInitializers(_init, 1, VoiceLiveOpsService);
807
+ __decoratorMetadata(_init, VoiceLiveOpsService);
808
+ let _VoiceLiveOpsService = VoiceLiveOpsService;
809
+ // src/angular/voice-delivery-runtime.service.ts
810
+ import { computed as computed6, Injectable as Injectable6, signal as signal6 } from "@angular/core";
811
+
812
+ // src/client/deliveryRuntime.ts
813
+ var getDefaultActionPath = (path, action, options) => {
814
+ if (action === "tick") {
815
+ return options.tickPath ?? `${path.replace(/\/$/, "")}/tick`;
858
816
  }
817
+ return options.requeueDeadLettersPath ?? `${path.replace(/\/$/, "")}/requeue-dead-letters`;
859
818
  };
860
- var QUALITY_PROFILE_DEFAULTS = {
861
- general: {},
862
- "accent-heavy": {
863
- silenceMs: 1200,
864
- speechThreshold: 0.01,
865
- transcriptStabilityMs: 1200
866
- },
867
- "noisy-room": {
868
- silenceMs: 2000,
869
- speechThreshold: 0.02,
870
- transcriptStabilityMs: 1600
871
- },
872
- "short-command": {
873
- silenceMs: 500,
874
- speechThreshold: 0.016,
875
- transcriptStabilityMs: 420
819
+ var fetchVoiceDeliveryRuntime = async (path = "/api/voice-delivery-runtime", options = {}) => {
820
+ const fetchImpl = options.fetch ?? globalThis.fetch;
821
+ const response = await fetchImpl(path);
822
+ if (!response.ok) {
823
+ throw new Error(`Voice delivery runtime failed: HTTP ${response.status}`);
876
824
  }
825
+ return await response.json();
877
826
  };
878
- var DEFAULT_TURN_PROFILE = "fast";
879
- var DEFAULT_QUALITY_PROFILE = "general";
880
- var resolveTurnDetectionConfig = (config) => {
881
- const profile = config?.profile ?? DEFAULT_TURN_PROFILE;
882
- const qualityProfile = config?.qualityProfile ?? DEFAULT_QUALITY_PROFILE;
883
- const preset = TURN_PROFILE_DEFAULTS[profile];
884
- const quality = QUALITY_PROFILE_DEFAULTS[qualityProfile];
827
+ var runVoiceDeliveryRuntimeAction = async (action, path = "/api/voice-delivery-runtime", options = {}) => {
828
+ const fetchImpl = options.fetch ?? globalThis.fetch;
829
+ const response = await fetchImpl(getDefaultActionPath(path, action, options), {
830
+ method: "POST"
831
+ });
832
+ if (!response.ok) {
833
+ throw new Error(`Voice delivery runtime ${action} failed: HTTP ${response.status}`);
834
+ }
835
+ const body = await response.json();
885
836
  return {
886
- profile,
887
- qualityProfile,
888
- silenceMs: config?.silenceMs ?? quality.silenceMs ?? preset.silenceMs,
889
- speechThreshold: config?.speechThreshold ?? quality.speechThreshold ?? preset.speechThreshold,
890
- transcriptStabilityMs: config?.transcriptStabilityMs ?? quality.transcriptStabilityMs ?? preset.transcriptStabilityMs
837
+ action,
838
+ result: body.result,
839
+ summary: body.summary,
840
+ updatedAt: Date.now()
891
841
  };
892
842
  };
893
-
894
- // src/presets.ts
895
- var PRESET_INPUTS = {
896
- chat: {
897
- audioConditioning: {
898
- enabled: true,
899
- maxGain: 2.5,
900
- noiseGateAttenuation: 0,
901
- noiseGateThreshold: 0.004,
902
- targetLevel: 0.08
903
- },
904
- capture: {
905
- channelCount: 1,
906
- sampleRateHz: 16000
907
- },
908
- connection: {
909
- maxReconnectAttempts: 10,
910
- pingInterval: 30000,
911
- reconnect: true
912
- },
913
- sttLifecycle: "continuous",
914
- turnDetection: {
915
- qualityProfile: "short-command",
916
- profile: "balanced"
917
- }
918
- },
919
- default: {
920
- capture: {
921
- channelCount: 1,
922
- sampleRateHz: 16000
923
- },
924
- connection: {
925
- maxReconnectAttempts: 10,
926
- pingInterval: 30000,
927
- reconnect: true
928
- },
929
- sttLifecycle: "continuous",
930
- turnDetection: {
931
- qualityProfile: "general",
932
- profile: "fast"
843
+ var createVoiceDeliveryRuntimeStore = (path = "/api/voice-delivery-runtime", options = {}) => {
844
+ const listeners = new Set;
845
+ let closed = false;
846
+ let timer;
847
+ let snapshot = {
848
+ actionError: null,
849
+ actionStatus: "idle",
850
+ error: null,
851
+ isLoading: false
852
+ };
853
+ const emit = () => {
854
+ for (const listener of listeners) {
855
+ listener();
933
856
  }
934
- },
935
- dictation: {
936
- audioConditioning: {
937
- enabled: true,
938
- maxGain: 2.25,
939
- noiseGateAttenuation: 0.05,
940
- noiseGateThreshold: 0.003,
941
- targetLevel: 0.08
942
- },
943
- capture: {
944
- channelCount: 1,
945
- sampleRateHz: 16000
946
- },
947
- connection: {
948
- maxReconnectAttempts: 12,
949
- pingInterval: 30000,
950
- reconnect: true
951
- },
952
- sttLifecycle: "continuous",
953
- turnDetection: {
954
- qualityProfile: "accent-heavy",
955
- profile: "long-form"
857
+ };
858
+ const refresh = async () => {
859
+ if (closed) {
860
+ return snapshot.report;
956
861
  }
957
- },
958
- "guided-intake": {
959
- audioConditioning: {
960
- enabled: true,
961
- maxGain: 2.5,
962
- noiseGateAttenuation: 0,
963
- noiseGateThreshold: 0.004,
964
- targetLevel: 0.08
965
- },
966
- capture: {
967
- channelCount: 1,
968
- sampleRateHz: 16000
969
- },
970
- connection: {
971
- maxReconnectAttempts: 12,
972
- pingInterval: 30000,
973
- reconnect: true
974
- },
975
- sttLifecycle: "turn-scoped",
976
- turnDetection: {
977
- qualityProfile: "accent-heavy",
978
- profile: "long-form"
862
+ snapshot = {
863
+ ...snapshot,
864
+ error: null,
865
+ isLoading: true
866
+ };
867
+ emit();
868
+ try {
869
+ const report = await fetchVoiceDeliveryRuntime(path, options);
870
+ snapshot = {
871
+ ...snapshot,
872
+ error: null,
873
+ isLoading: false,
874
+ report,
875
+ updatedAt: Date.now()
876
+ };
877
+ emit();
878
+ return report;
879
+ } catch (error) {
880
+ snapshot = {
881
+ ...snapshot,
882
+ error: error instanceof Error ? error.message : String(error),
883
+ isLoading: false
884
+ };
885
+ emit();
886
+ throw error;
979
887
  }
980
- },
981
- "noisy-room": {
982
- audioConditioning: {
983
- enabled: true,
984
- maxGain: 3,
985
- noiseGateAttenuation: 0.12,
986
- noiseGateThreshold: 0.006,
987
- targetLevel: 0.085
988
- },
989
- capture: {
990
- channelCount: 1,
991
- sampleRateHz: 16000
992
- },
993
- connection: {
994
- maxReconnectAttempts: 14,
995
- pingInterval: 45000,
996
- reconnect: true
997
- },
998
- sttLifecycle: "continuous",
999
- turnDetection: {
1000
- qualityProfile: "noisy-room",
1001
- profile: "long-form",
1002
- silenceMs: 2100,
1003
- speechThreshold: 0.02,
1004
- transcriptStabilityMs: 1650
888
+ };
889
+ const runAction = async (action) => {
890
+ if (closed) {
891
+ return snapshot.lastAction;
1005
892
  }
1006
- },
1007
- "pstn-balanced": {
1008
- audioConditioning: {
1009
- enabled: true,
1010
- maxGain: 2.8,
893
+ snapshot = {
894
+ ...snapshot,
895
+ actionError: null,
896
+ actionStatus: "running"
897
+ };
898
+ emit();
899
+ try {
900
+ const result = await runVoiceDeliveryRuntimeAction(action, path, options);
901
+ snapshot = {
902
+ ...snapshot,
903
+ actionError: null,
904
+ actionStatus: "completed",
905
+ lastAction: result
906
+ };
907
+ emit();
908
+ await refresh();
909
+ return result;
910
+ } catch (error) {
911
+ snapshot = {
912
+ ...snapshot,
913
+ actionError: error instanceof Error ? error.message : String(error),
914
+ actionStatus: "failed"
915
+ };
916
+ emit();
917
+ throw error;
918
+ }
919
+ };
920
+ const close = () => {
921
+ closed = true;
922
+ if (timer) {
923
+ clearInterval(timer);
924
+ timer = undefined;
925
+ }
926
+ listeners.clear();
927
+ };
928
+ if (typeof window !== "undefined" && options.intervalMs && options.intervalMs > 0) {
929
+ timer = setInterval(() => {
930
+ refresh().catch(() => {});
931
+ }, options.intervalMs);
932
+ }
933
+ return {
934
+ close,
935
+ getServerSnapshot: () => snapshot,
936
+ getSnapshot: () => snapshot,
937
+ requeueDeadLetters: () => runAction("requeue-dead-letters"),
938
+ refresh,
939
+ tick: () => runAction("tick"),
940
+ subscribe: (listener) => {
941
+ listeners.add(listener);
942
+ return () => {
943
+ listeners.delete(listener);
944
+ };
945
+ }
946
+ };
947
+ };
948
+
949
+ // src/angular/voice-delivery-runtime.service.ts
950
+ var _dec = [
951
+ Injectable6({ providedIn: "root" })
952
+ ];
953
+ var _init = __decoratorStart(undefined);
954
+
955
+ class VoiceDeliveryRuntimeService {
956
+ connect(path = "/api/voice-delivery-runtime", options = {}) {
957
+ const store = createVoiceDeliveryRuntimeStore(path, options);
958
+ const actionErrorSignal = signal6(null);
959
+ const actionStatusSignal = signal6("idle");
960
+ const errorSignal = signal6(null);
961
+ const isLoadingSignal = signal6(false);
962
+ const reportSignal = signal6(undefined);
963
+ const updatedAtSignal = signal6(undefined);
964
+ const sync = () => {
965
+ const snapshot = store.getSnapshot();
966
+ actionErrorSignal.set(snapshot.actionError);
967
+ actionStatusSignal.set(snapshot.actionStatus);
968
+ errorSignal.set(snapshot.error);
969
+ isLoadingSignal.set(snapshot.isLoading);
970
+ reportSignal.set(snapshot.report);
971
+ updatedAtSignal.set(snapshot.updatedAt);
972
+ };
973
+ const unsubscribe = store.subscribe(sync);
974
+ sync();
975
+ if (typeof window !== "undefined") {
976
+ store.refresh().catch(() => {});
977
+ }
978
+ return {
979
+ close: () => {
980
+ unsubscribe();
981
+ store.close();
982
+ },
983
+ error: computed6(() => errorSignal()),
984
+ actionError: computed6(() => actionErrorSignal()),
985
+ actionStatus: computed6(() => actionStatusSignal()),
986
+ isLoading: computed6(() => isLoadingSignal()),
987
+ requeueDeadLetters: store.requeueDeadLetters,
988
+ refresh: store.refresh,
989
+ report: computed6(() => reportSignal()),
990
+ tick: store.tick,
991
+ updatedAt: computed6(() => updatedAtSignal())
992
+ };
993
+ }
994
+ }
995
+ VoiceDeliveryRuntimeService = __decorateElement(_init, 0, "VoiceDeliveryRuntimeService", _dec, VoiceDeliveryRuntimeService);
996
+ __runInitializers(_init, 1, VoiceDeliveryRuntimeService);
997
+ __decoratorMetadata(_init, VoiceDeliveryRuntimeService);
998
+ let _VoiceDeliveryRuntimeService = VoiceDeliveryRuntimeService;
999
+ // src/angular/voice-campaign-dialer-proof.service.ts
1000
+ import { computed as computed7, Injectable as Injectable7, signal as signal7 } from "@angular/core";
1001
+
1002
+ // src/client/campaignDialerProof.ts
1003
+ var fetchVoiceCampaignDialerProofStatus = async (path = "/api/voice/campaigns/dialer-proof", options = {}) => {
1004
+ const fetchImpl = options.fetch ?? globalThis.fetch;
1005
+ const response = await fetchImpl(path);
1006
+ if (!response.ok) {
1007
+ throw new Error(`Voice campaign dialer proof status failed: HTTP ${response.status}`);
1008
+ }
1009
+ return await response.json();
1010
+ };
1011
+ var runVoiceCampaignDialerProofAction = async (path = "/api/voice/campaigns/dialer-proof", options = {}) => {
1012
+ const fetchImpl = options.fetch ?? globalThis.fetch;
1013
+ const response = await fetchImpl(path, { method: "POST" });
1014
+ if (!response.ok) {
1015
+ throw new Error(`Voice campaign dialer proof failed: HTTP ${response.status}`);
1016
+ }
1017
+ return await response.json();
1018
+ };
1019
+ var createVoiceCampaignDialerProofStore = (path = "/api/voice/campaigns/dialer-proof", options = {}) => {
1020
+ const listeners = new Set;
1021
+ let closed = false;
1022
+ let timer;
1023
+ let snapshot = {
1024
+ error: null,
1025
+ isLoading: false
1026
+ };
1027
+ const emit = () => {
1028
+ for (const listener of listeners) {
1029
+ listener();
1030
+ }
1031
+ };
1032
+ const refresh = async () => {
1033
+ if (closed) {
1034
+ return snapshot.status;
1035
+ }
1036
+ snapshot = { ...snapshot, error: null, isLoading: true };
1037
+ emit();
1038
+ try {
1039
+ const status = await fetchVoiceCampaignDialerProofStatus(path, options);
1040
+ snapshot = {
1041
+ ...snapshot,
1042
+ error: null,
1043
+ isLoading: false,
1044
+ status,
1045
+ updatedAt: Date.now()
1046
+ };
1047
+ emit();
1048
+ return status;
1049
+ } catch (error) {
1050
+ snapshot = {
1051
+ ...snapshot,
1052
+ error: error instanceof Error ? error.message : String(error),
1053
+ isLoading: false
1054
+ };
1055
+ emit();
1056
+ throw error;
1057
+ }
1058
+ };
1059
+ const runProof = async () => {
1060
+ const runPath = options.runPath ?? snapshot.status?.runPath ?? path;
1061
+ snapshot = { ...snapshot, error: null, isLoading: true };
1062
+ emit();
1063
+ try {
1064
+ const report = await runVoiceCampaignDialerProofAction(runPath, options);
1065
+ snapshot = {
1066
+ ...snapshot,
1067
+ error: null,
1068
+ isLoading: false,
1069
+ report,
1070
+ status: {
1071
+ generatedAt: Date.now(),
1072
+ mode: report.mode,
1073
+ ok: report.ok,
1074
+ providers: report.providers.map((provider) => provider.provider),
1075
+ runPath,
1076
+ safe: true
1077
+ },
1078
+ updatedAt: Date.now()
1079
+ };
1080
+ emit();
1081
+ return report;
1082
+ } catch (error) {
1083
+ snapshot = {
1084
+ ...snapshot,
1085
+ error: error instanceof Error ? error.message : String(error),
1086
+ isLoading: false
1087
+ };
1088
+ emit();
1089
+ throw error;
1090
+ }
1091
+ };
1092
+ const close = () => {
1093
+ closed = true;
1094
+ if (timer) {
1095
+ clearInterval(timer);
1096
+ timer = undefined;
1097
+ }
1098
+ listeners.clear();
1099
+ };
1100
+ if (options.intervalMs && options.intervalMs > 0) {
1101
+ timer = setInterval(() => {
1102
+ refresh().catch(() => {});
1103
+ }, options.intervalMs);
1104
+ }
1105
+ return {
1106
+ close,
1107
+ getServerSnapshot: () => snapshot,
1108
+ getSnapshot: () => snapshot,
1109
+ refresh,
1110
+ runProof,
1111
+ subscribe: (listener) => {
1112
+ listeners.add(listener);
1113
+ return () => {
1114
+ listeners.delete(listener);
1115
+ };
1116
+ }
1117
+ };
1118
+ };
1119
+
1120
+ // src/angular/voice-campaign-dialer-proof.service.ts
1121
+ var _dec = [
1122
+ Injectable7({ providedIn: "root" })
1123
+ ];
1124
+ var _init = __decoratorStart(undefined);
1125
+
1126
+ class VoiceCampaignDialerProofService {
1127
+ connect(path = "/api/voice/campaigns/dialer-proof", options = {}) {
1128
+ const store = createVoiceCampaignDialerProofStore(path, options);
1129
+ const errorSignal = signal7(null);
1130
+ const isLoadingSignal = signal7(false);
1131
+ const reportSignal = signal7(undefined);
1132
+ const statusSignal = signal7(undefined);
1133
+ const updatedAtSignal = signal7(undefined);
1134
+ const sync = () => {
1135
+ const snapshot = store.getSnapshot();
1136
+ errorSignal.set(snapshot.error);
1137
+ isLoadingSignal.set(snapshot.isLoading);
1138
+ reportSignal.set(snapshot.report);
1139
+ statusSignal.set(snapshot.status);
1140
+ updatedAtSignal.set(snapshot.updatedAt);
1141
+ };
1142
+ const unsubscribe = store.subscribe(sync);
1143
+ sync();
1144
+ store.refresh().catch(() => {});
1145
+ return {
1146
+ close: () => {
1147
+ unsubscribe();
1148
+ store.close();
1149
+ },
1150
+ error: computed7(() => errorSignal()),
1151
+ isLoading: computed7(() => isLoadingSignal()),
1152
+ refresh: store.refresh,
1153
+ report: computed7(() => reportSignal()),
1154
+ runProof: store.runProof,
1155
+ status: computed7(() => statusSignal()),
1156
+ updatedAt: computed7(() => updatedAtSignal())
1157
+ };
1158
+ }
1159
+ }
1160
+ VoiceCampaignDialerProofService = __decorateElement(_init, 0, "VoiceCampaignDialerProofService", _dec, VoiceCampaignDialerProofService);
1161
+ __runInitializers(_init, 1, VoiceCampaignDialerProofService);
1162
+ __decoratorMetadata(_init, VoiceCampaignDialerProofService);
1163
+ let _VoiceCampaignDialerProofService = VoiceCampaignDialerProofService;
1164
+ // src/angular/voice-stream.service.ts
1165
+ import { computed as computed8, Injectable as Injectable8, signal as signal8 } from "@angular/core";
1166
+
1167
+ // src/client/actions.ts
1168
+ var normalizeErrorMessage = (value) => {
1169
+ if (typeof value === "string" && value.trim()) {
1170
+ return value;
1171
+ }
1172
+ if (value instanceof Error && value.message.trim()) {
1173
+ return value.message;
1174
+ }
1175
+ if (value && typeof value === "object") {
1176
+ const record = value;
1177
+ for (const key of ["message", "reason", "description"]) {
1178
+ const candidate = record[key];
1179
+ if (typeof candidate === "string" && candidate.trim()) {
1180
+ return candidate;
1181
+ }
1182
+ }
1183
+ if ("error" in record) {
1184
+ return normalizeErrorMessage(record.error);
1185
+ }
1186
+ if ("cause" in record) {
1187
+ return normalizeErrorMessage(record.cause);
1188
+ }
1189
+ try {
1190
+ return JSON.stringify(value);
1191
+ } catch {}
1192
+ }
1193
+ return "Unexpected error";
1194
+ };
1195
+ var serverMessageToAction = (message) => {
1196
+ switch (message.type) {
1197
+ case "audio":
1198
+ return {
1199
+ chunk: Uint8Array.from(atob(message.chunkBase64), (char) => char.charCodeAt(0)),
1200
+ format: message.format,
1201
+ receivedAt: message.receivedAt,
1202
+ turnId: message.turnId,
1203
+ type: "audio"
1204
+ };
1205
+ case "assistant":
1206
+ return {
1207
+ text: message.text,
1208
+ type: "assistant"
1209
+ };
1210
+ case "complete":
1211
+ return {
1212
+ sessionId: message.sessionId,
1213
+ type: "complete"
1214
+ };
1215
+ case "connection":
1216
+ return {
1217
+ reconnect: message.reconnect,
1218
+ type: "connection"
1219
+ };
1220
+ case "call_lifecycle":
1221
+ return {
1222
+ event: message.event,
1223
+ sessionId: message.sessionId,
1224
+ type: "call_lifecycle"
1225
+ };
1226
+ case "error":
1227
+ return {
1228
+ message: normalizeErrorMessage(message.message),
1229
+ type: "error"
1230
+ };
1231
+ case "final":
1232
+ return {
1233
+ transcript: message.transcript,
1234
+ type: "final"
1235
+ };
1236
+ case "partial":
1237
+ return {
1238
+ transcript: message.transcript,
1239
+ type: "partial"
1240
+ };
1241
+ case "replay":
1242
+ return {
1243
+ assistantTexts: message.assistantTexts,
1244
+ call: message.call,
1245
+ partial: message.partial,
1246
+ scenarioId: message.scenarioId,
1247
+ sessionId: message.sessionId,
1248
+ status: message.status,
1249
+ turns: message.turns,
1250
+ type: "replay"
1251
+ };
1252
+ case "session":
1253
+ return {
1254
+ sessionId: message.sessionId,
1255
+ scenarioId: message.scenarioId,
1256
+ status: message.status,
1257
+ type: "session"
1258
+ };
1259
+ case "turn":
1260
+ return {
1261
+ turn: message.turn,
1262
+ type: "turn"
1263
+ };
1264
+ default:
1265
+ return null;
1266
+ }
1267
+ };
1268
+
1269
+ // src/client/connection.ts
1270
+ var WS_OPEN = 1;
1271
+ var WS_CLOSED = 3;
1272
+ var WS_NORMAL_CLOSURE = 1000;
1273
+ var DEFAULT_MAX_RECONNECT_ATTEMPTS = 10;
1274
+ var DEFAULT_PING_INTERVAL = 30000;
1275
+ var RECONNECT_DELAY_MS = 500;
1276
+ var DEFAULT_SCENARIO_QUERY_PARAM = "scenarioId";
1277
+ var noop = () => {};
1278
+ var noopUnsubscribe = () => noop;
1279
+ var NOOP_CONNECTION = {
1280
+ callControl: noop,
1281
+ close: noop,
1282
+ endTurn: noop,
1283
+ getReadyState: () => WS_CLOSED,
1284
+ getScenarioId: () => "",
1285
+ getSessionId: () => "",
1286
+ send: noop,
1287
+ sendAudio: noop,
1288
+ start: () => {},
1289
+ subscribe: noopUnsubscribe
1290
+ };
1291
+ var createSessionId = () => crypto.randomUUID();
1292
+ var buildWsUrl = (path, sessionId, scenarioId) => {
1293
+ const { hostname, port, protocol } = window.location;
1294
+ const wsProtocol = protocol === "https:" ? "wss:" : "ws:";
1295
+ const portSuffix = port ? `:${port}` : "";
1296
+ const url = new URL(`${wsProtocol}//${hostname}${portSuffix}${path}`);
1297
+ url.searchParams.set("sessionId", sessionId);
1298
+ if (scenarioId) {
1299
+ url.searchParams.set(DEFAULT_SCENARIO_QUERY_PARAM, scenarioId);
1300
+ }
1301
+ return url.toString();
1302
+ };
1303
+ var isVoiceServerMessage = (value) => {
1304
+ if (!value || typeof value !== "object" || !("type" in value)) {
1305
+ return false;
1306
+ }
1307
+ switch (value.type) {
1308
+ case "audio":
1309
+ case "assistant":
1310
+ case "call_lifecycle":
1311
+ case "complete":
1312
+ case "connection":
1313
+ case "error":
1314
+ case "final":
1315
+ case "partial":
1316
+ case "pong":
1317
+ case "replay":
1318
+ case "session":
1319
+ case "turn":
1320
+ return true;
1321
+ default:
1322
+ return false;
1323
+ }
1324
+ };
1325
+ var parseServerMessage = (event) => {
1326
+ if (typeof event.data !== "string") {
1327
+ return null;
1328
+ }
1329
+ try {
1330
+ const parsed = JSON.parse(event.data);
1331
+ return isVoiceServerMessage(parsed) ? parsed : null;
1332
+ } catch {
1333
+ return null;
1334
+ }
1335
+ };
1336
+ var createVoiceConnection = (path, options = {}) => {
1337
+ if (typeof window === "undefined") {
1338
+ return NOOP_CONNECTION;
1339
+ }
1340
+ const listeners = new Set;
1341
+ const shouldReconnect = options.reconnect !== false;
1342
+ const maxReconnectAttempts = options.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS;
1343
+ const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
1344
+ const state = {
1345
+ isConnected: false,
1346
+ pendingMessages: [],
1347
+ scenarioId: options.scenarioId ?? null,
1348
+ pingInterval: null,
1349
+ reconnectAttempts: 0,
1350
+ reconnectTimeout: null,
1351
+ sessionId: options.sessionId ?? createSessionId(),
1352
+ ws: null
1353
+ };
1354
+ const emitConnection = (reconnect) => {
1355
+ listeners.forEach((listener) => listener(reconnect));
1356
+ };
1357
+ const clearTimers = () => {
1358
+ if (state.pingInterval) {
1359
+ clearInterval(state.pingInterval);
1360
+ state.pingInterval = null;
1361
+ }
1362
+ if (state.reconnectTimeout) {
1363
+ clearTimeout(state.reconnectTimeout);
1364
+ state.reconnectTimeout = null;
1365
+ }
1366
+ };
1367
+ const flushPendingMessages = () => {
1368
+ if (state.ws?.readyState !== WS_OPEN) {
1369
+ return;
1370
+ }
1371
+ while (state.pendingMessages.length > 0) {
1372
+ const next = state.pendingMessages.shift();
1373
+ if (next !== undefined) {
1374
+ state.ws.send(next);
1375
+ }
1376
+ }
1377
+ };
1378
+ const scheduleReconnect = () => {
1379
+ const nextAttemptAt = Date.now() + RECONNECT_DELAY_MS;
1380
+ state.reconnectAttempts += 1;
1381
+ emitConnection({
1382
+ reconnect: {
1383
+ attempts: state.reconnectAttempts,
1384
+ lastDisconnectAt: Date.now(),
1385
+ maxAttempts: maxReconnectAttempts,
1386
+ nextAttemptAt,
1387
+ status: "reconnecting"
1388
+ },
1389
+ type: "connection"
1390
+ });
1391
+ state.reconnectTimeout = setTimeout(() => {
1392
+ if (state.reconnectAttempts > maxReconnectAttempts) {
1393
+ emitConnection({
1394
+ reconnect: {
1395
+ attempts: state.reconnectAttempts,
1396
+ maxAttempts: maxReconnectAttempts,
1397
+ status: "exhausted"
1398
+ },
1399
+ type: "connection"
1400
+ });
1401
+ return;
1402
+ }
1403
+ connect();
1404
+ }, RECONNECT_DELAY_MS);
1405
+ };
1406
+ const connect = () => {
1407
+ const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId));
1408
+ ws.binaryType = "arraybuffer";
1409
+ ws.onopen = () => {
1410
+ const wasReconnecting = state.reconnectAttempts > 0;
1411
+ state.isConnected = true;
1412
+ flushPendingMessages();
1413
+ if (wasReconnecting) {
1414
+ emitConnection({
1415
+ reconnect: {
1416
+ attempts: state.reconnectAttempts,
1417
+ lastResumedAt: Date.now(),
1418
+ maxAttempts: maxReconnectAttempts,
1419
+ status: "resumed"
1420
+ },
1421
+ type: "connection"
1422
+ });
1423
+ state.reconnectAttempts = 0;
1424
+ }
1425
+ listeners.forEach((listener) => listener({
1426
+ scenarioId: state.scenarioId ?? undefined,
1427
+ sessionId: state.sessionId,
1428
+ status: "active",
1429
+ type: "session"
1430
+ }));
1431
+ state.pingInterval = setInterval(() => {
1432
+ if (ws.readyState === WS_OPEN) {
1433
+ ws.send(JSON.stringify({ type: "ping" }));
1434
+ }
1435
+ }, pingInterval);
1436
+ };
1437
+ ws.onmessage = (event) => {
1438
+ const parsed = parseServerMessage(event);
1439
+ if (!parsed) {
1440
+ return;
1441
+ }
1442
+ if (parsed.type === "session") {
1443
+ state.sessionId = parsed.sessionId;
1444
+ state.scenarioId = parsed.scenarioId ?? state.scenarioId;
1445
+ }
1446
+ listeners.forEach((listener) => listener(parsed));
1447
+ };
1448
+ ws.onclose = (event) => {
1449
+ state.isConnected = false;
1450
+ clearTimers();
1451
+ const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && state.reconnectAttempts < maxReconnectAttempts;
1452
+ if (reconnectable) {
1453
+ scheduleReconnect();
1454
+ } else if (shouldReconnect && event.code !== WS_NORMAL_CLOSURE) {
1455
+ emitConnection({
1456
+ reconnect: {
1457
+ attempts: state.reconnectAttempts,
1458
+ lastDisconnectAt: Date.now(),
1459
+ maxAttempts: maxReconnectAttempts,
1460
+ status: "exhausted"
1461
+ },
1462
+ type: "connection"
1463
+ });
1464
+ }
1465
+ };
1466
+ state.ws = ws;
1467
+ };
1468
+ const sendSerialized = (value) => {
1469
+ if (state.ws?.readyState === WS_OPEN) {
1470
+ state.ws.send(value);
1471
+ return;
1472
+ }
1473
+ state.pendingMessages.push(value);
1474
+ };
1475
+ const send = (message) => {
1476
+ sendSerialized(JSON.stringify(message));
1477
+ };
1478
+ const start = (input = {}) => {
1479
+ if (input.sessionId) {
1480
+ state.sessionId = input.sessionId;
1481
+ }
1482
+ if (input.scenarioId) {
1483
+ state.scenarioId = input.scenarioId;
1484
+ }
1485
+ send({
1486
+ type: "start",
1487
+ sessionId: state.sessionId,
1488
+ scenarioId: state.scenarioId ?? undefined
1489
+ });
1490
+ };
1491
+ const sendAudio = (audio) => {
1492
+ sendSerialized(audio);
1493
+ };
1494
+ const endTurn = () => {
1495
+ send({ type: "end_turn" });
1496
+ };
1497
+ const callControl = (message) => {
1498
+ send({
1499
+ ...message,
1500
+ type: "call_control"
1501
+ });
1502
+ };
1503
+ const close = () => {
1504
+ clearTimers();
1505
+ if (state.ws) {
1506
+ state.ws.close(WS_NORMAL_CLOSURE);
1507
+ state.ws = null;
1508
+ }
1509
+ state.isConnected = false;
1510
+ listeners.clear();
1511
+ };
1512
+ const subscribe = (callback) => {
1513
+ listeners.add(callback);
1514
+ return () => {
1515
+ listeners.delete(callback);
1516
+ };
1517
+ };
1518
+ connect();
1519
+ return {
1520
+ callControl,
1521
+ close,
1522
+ endTurn,
1523
+ getReadyState: () => state.ws?.readyState ?? WS_CLOSED,
1524
+ getScenarioId: () => state.scenarioId ?? "",
1525
+ getSessionId: () => state.sessionId,
1526
+ send,
1527
+ sendAudio,
1528
+ start,
1529
+ subscribe
1530
+ };
1531
+ };
1532
+
1533
+ // src/client/store.ts
1534
+ var createInitialReconnectState = () => ({
1535
+ attempts: 0,
1536
+ maxAttempts: 0,
1537
+ status: "idle"
1538
+ });
1539
+ var createInitialState = () => ({
1540
+ assistantAudio: [],
1541
+ assistantTexts: [],
1542
+ call: null,
1543
+ error: null,
1544
+ isConnected: false,
1545
+ scenarioId: null,
1546
+ partial: "",
1547
+ reconnect: createInitialReconnectState(),
1548
+ sessionId: null,
1549
+ status: "idle",
1550
+ turns: []
1551
+ });
1552
+ var createVoiceStreamStore = () => {
1553
+ let state = createInitialState();
1554
+ const subscribers = new Set;
1555
+ const notify = () => {
1556
+ subscribers.forEach((subscriber) => subscriber());
1557
+ };
1558
+ const dispatch = (action) => {
1559
+ switch (action.type) {
1560
+ case "audio":
1561
+ state = {
1562
+ ...state,
1563
+ assistantAudio: [
1564
+ ...state.assistantAudio,
1565
+ {
1566
+ chunk: action.chunk,
1567
+ format: action.format,
1568
+ receivedAt: action.receivedAt,
1569
+ turnId: action.turnId
1570
+ }
1571
+ ]
1572
+ };
1573
+ break;
1574
+ case "assistant":
1575
+ state = {
1576
+ ...state,
1577
+ assistantTexts: [...state.assistantTexts, action.text]
1578
+ };
1579
+ break;
1580
+ case "complete":
1581
+ state = {
1582
+ ...state,
1583
+ sessionId: action.sessionId,
1584
+ status: "completed"
1585
+ };
1586
+ break;
1587
+ case "call_lifecycle":
1588
+ state = {
1589
+ ...state,
1590
+ call: {
1591
+ ...state.call,
1592
+ disposition: action.event.type === "end" ? action.event.disposition : state.call?.disposition,
1593
+ endedAt: action.event.type === "end" ? action.event.at : state.call?.endedAt,
1594
+ events: [...state.call?.events ?? [], action.event],
1595
+ lastEventAt: action.event.at,
1596
+ startedAt: state.call?.startedAt ?? action.event.at
1597
+ },
1598
+ sessionId: action.sessionId
1599
+ };
1600
+ break;
1601
+ case "connected":
1602
+ state = {
1603
+ ...state,
1604
+ isConnected: true,
1605
+ reconnect: state.reconnect.status === "reconnecting" ? {
1606
+ ...state.reconnect,
1607
+ lastResumedAt: Date.now(),
1608
+ nextAttemptAt: undefined,
1609
+ status: "resumed"
1610
+ } : state.reconnect
1611
+ };
1612
+ break;
1613
+ case "connection":
1614
+ state = {
1615
+ ...state,
1616
+ reconnect: action.reconnect
1617
+ };
1618
+ break;
1619
+ case "disconnected":
1620
+ state = {
1621
+ ...state,
1622
+ isConnected: false
1623
+ };
1624
+ break;
1625
+ case "error":
1626
+ state = {
1627
+ ...state,
1628
+ error: action.message
1629
+ };
1630
+ break;
1631
+ case "final":
1632
+ state = {
1633
+ ...state,
1634
+ partial: action.transcript.text,
1635
+ turns: state.turns.map((turn) => turn)
1636
+ };
1637
+ break;
1638
+ case "partial":
1639
+ state = {
1640
+ ...state,
1641
+ partial: action.transcript.text
1642
+ };
1643
+ break;
1644
+ case "replay":
1645
+ state = {
1646
+ ...state,
1647
+ assistantTexts: [...action.assistantTexts],
1648
+ call: action.call ?? null,
1649
+ error: null,
1650
+ isConnected: action.status === "active",
1651
+ partial: action.partial,
1652
+ reconnect: state.reconnect.status === "reconnecting" ? {
1653
+ ...state.reconnect,
1654
+ lastResumedAt: Date.now(),
1655
+ nextAttemptAt: undefined,
1656
+ status: "resumed"
1657
+ } : state.reconnect,
1658
+ scenarioId: action.scenarioId ?? state.scenarioId,
1659
+ sessionId: action.sessionId,
1660
+ status: action.status,
1661
+ turns: [...action.turns]
1662
+ };
1663
+ break;
1664
+ case "session":
1665
+ state = {
1666
+ ...state,
1667
+ error: null,
1668
+ scenarioId: action.scenarioId ?? state.scenarioId,
1669
+ isConnected: action.status === "active",
1670
+ sessionId: action.sessionId,
1671
+ status: action.status
1672
+ };
1673
+ break;
1674
+ case "turn":
1675
+ state = {
1676
+ ...state,
1677
+ partial: "",
1678
+ turns: [...state.turns, action.turn]
1679
+ };
1680
+ break;
1681
+ }
1682
+ notify();
1683
+ };
1684
+ return {
1685
+ dispatch,
1686
+ getServerSnapshot: () => state,
1687
+ getSnapshot: () => state,
1688
+ subscribe: (subscriber) => {
1689
+ subscribers.add(subscriber);
1690
+ return () => {
1691
+ subscribers.delete(subscriber);
1692
+ };
1693
+ }
1694
+ };
1695
+ };
1696
+
1697
+ // src/client/createVoiceStream.ts
1698
+ var createVoiceStream = (path, options = {}) => {
1699
+ const connection = createVoiceConnection(path, options);
1700
+ const store = createVoiceStreamStore();
1701
+ const subscribers = new Set;
1702
+ const start = (input) => Promise.resolve().then(() => {
1703
+ if (!input?.sessionId && !input?.scenarioId) {
1704
+ return;
1705
+ }
1706
+ connection.start(input);
1707
+ });
1708
+ const notify = () => {
1709
+ subscribers.forEach((subscriber) => subscriber());
1710
+ };
1711
+ const reportReconnect = () => {
1712
+ if (!options.reconnectReportPath || typeof fetch === "undefined") {
1713
+ return;
1714
+ }
1715
+ const snapshot = store.getSnapshot();
1716
+ const body = JSON.stringify({
1717
+ at: Date.now(),
1718
+ reconnect: snapshot.reconnect,
1719
+ scenarioId: snapshot.scenarioId,
1720
+ sessionId: connection.getSessionId(),
1721
+ turnIds: snapshot.turns.map((turn) => turn.id)
1722
+ });
1723
+ fetch(options.reconnectReportPath, {
1724
+ body,
1725
+ headers: {
1726
+ "Content-Type": "application/json"
1727
+ },
1728
+ keepalive: true,
1729
+ method: "POST"
1730
+ }).catch(() => {});
1731
+ };
1732
+ const unsubscribeConnection = connection.subscribe((message) => {
1733
+ const action = serverMessageToAction(message);
1734
+ if (action) {
1735
+ store.dispatch(action);
1736
+ if (message.type === "connection") {
1737
+ reportReconnect();
1738
+ }
1739
+ notify();
1740
+ }
1741
+ });
1742
+ return {
1743
+ callControl(message) {
1744
+ connection.callControl(message);
1745
+ },
1746
+ close() {
1747
+ unsubscribeConnection();
1748
+ connection.close();
1749
+ store.dispatch({ type: "disconnected" });
1750
+ notify();
1751
+ },
1752
+ endTurn() {
1753
+ connection.endTurn();
1754
+ },
1755
+ get error() {
1756
+ return store.getSnapshot().error;
1757
+ },
1758
+ getServerSnapshot() {
1759
+ return store.getServerSnapshot();
1760
+ },
1761
+ getSnapshot() {
1762
+ return store.getSnapshot();
1763
+ },
1764
+ get isConnected() {
1765
+ return store.getSnapshot().isConnected;
1766
+ },
1767
+ get scenarioId() {
1768
+ return store.getSnapshot().scenarioId;
1769
+ },
1770
+ start,
1771
+ get partial() {
1772
+ return store.getSnapshot().partial;
1773
+ },
1774
+ get reconnect() {
1775
+ return store.getSnapshot().reconnect;
1776
+ },
1777
+ get sessionId() {
1778
+ return connection.getSessionId();
1779
+ },
1780
+ get status() {
1781
+ return store.getSnapshot().status;
1782
+ },
1783
+ get turns() {
1784
+ return store.getSnapshot().turns;
1785
+ },
1786
+ get assistantTexts() {
1787
+ return store.getSnapshot().assistantTexts;
1788
+ },
1789
+ get assistantAudio() {
1790
+ return store.getSnapshot().assistantAudio;
1791
+ },
1792
+ get call() {
1793
+ return store.getSnapshot().call;
1794
+ },
1795
+ sendAudio(audio) {
1796
+ connection.sendAudio(audio);
1797
+ },
1798
+ subscribe(subscriber) {
1799
+ subscribers.add(subscriber);
1800
+ return () => {
1801
+ subscribers.delete(subscriber);
1802
+ };
1803
+ }
1804
+ };
1805
+ };
1806
+
1807
+ // src/angular/voice-stream.service.ts
1808
+ var _dec = [
1809
+ Injectable8({ providedIn: "root" })
1810
+ ];
1811
+ var _init = __decoratorStart(undefined);
1812
+
1813
+ class VoiceStreamService {
1814
+ connect(path, options = {}) {
1815
+ const stream = createVoiceStream(path, options);
1816
+ const assistantAudioSignal = signal8([]);
1817
+ const assistantTextsSignal = signal8([]);
1818
+ const callSignal = signal8(null);
1819
+ const errorSignal = signal8(null);
1820
+ const isConnectedSignal = signal8(false);
1821
+ const partialSignal = signal8("");
1822
+ const reconnectSignal = signal8(stream.reconnect);
1823
+ const sessionIdSignal = signal8(stream.sessionId);
1824
+ const statusSignal = signal8(stream.status);
1825
+ const turnsSignal = signal8([]);
1826
+ const sync = () => {
1827
+ assistantAudioSignal.set([...stream.assistantAudio]);
1828
+ assistantTextsSignal.set([...stream.assistantTexts]);
1829
+ callSignal.set(stream.call);
1830
+ errorSignal.set(stream.error);
1831
+ isConnectedSignal.set(stream.isConnected);
1832
+ partialSignal.set(stream.partial);
1833
+ reconnectSignal.set(stream.reconnect);
1834
+ sessionIdSignal.set(stream.sessionId);
1835
+ statusSignal.set(stream.status);
1836
+ turnsSignal.set([...stream.turns]);
1837
+ };
1838
+ const unsubscribe = stream.subscribe(sync);
1839
+ sync();
1840
+ return {
1841
+ assistantAudio: computed8(() => assistantAudioSignal()),
1842
+ assistantTexts: computed8(() => assistantTextsSignal()),
1843
+ call: computed8(() => callSignal()),
1844
+ callControl: (message) => stream.callControl(message),
1845
+ close: () => {
1846
+ unsubscribe();
1847
+ stream.close();
1848
+ },
1849
+ endTurn: () => stream.endTurn(),
1850
+ error: computed8(() => errorSignal()),
1851
+ isConnected: computed8(() => isConnectedSignal()),
1852
+ partial: computed8(() => partialSignal()),
1853
+ reconnect: computed8(() => reconnectSignal()),
1854
+ sendAudio: (audio) => stream.sendAudio(audio),
1855
+ sessionId: computed8(() => sessionIdSignal()),
1856
+ status: computed8(() => statusSignal()),
1857
+ turns: computed8(() => turnsSignal())
1858
+ };
1859
+ }
1860
+ }
1861
+ VoiceStreamService = __decorateElement(_init, 0, "VoiceStreamService", _dec, VoiceStreamService);
1862
+ __runInitializers(_init, 1, VoiceStreamService);
1863
+ __decoratorMetadata(_init, VoiceStreamService);
1864
+ let _VoiceStreamService = VoiceStreamService;
1865
+ // src/angular/voice-controller.service.ts
1866
+ import { computed as computed9, Injectable as Injectable9, signal as signal9 } from "@angular/core";
1867
+
1868
+ // src/client/htmx.ts
1869
+ var DEFAULT_EVENT_NAME = "voice-refresh";
1870
+ var DEFAULT_QUERY_PARAM = "sessionId";
1871
+ var resolveElement = (input) => {
1872
+ if (typeof input !== "string") {
1873
+ return input;
1874
+ }
1875
+ return document.querySelector(input);
1876
+ };
1877
+ var buildRoute = (element, route, queryParam, sessionId) => {
1878
+ const baseRoute = route ?? element.getAttribute("hx-get") ?? "";
1879
+ if (!baseRoute) {
1880
+ return "";
1881
+ }
1882
+ const url = new URL(baseRoute, window.location.origin);
1883
+ if (sessionId) {
1884
+ url.searchParams.set(queryParam, sessionId);
1885
+ } else {
1886
+ url.searchParams.delete(queryParam);
1887
+ }
1888
+ return `${url.pathname}${url.search}${url.hash}`;
1889
+ };
1890
+ var bindVoiceHTMX = (stream, options) => {
1891
+ if (typeof window === "undefined" || typeof document === "undefined") {
1892
+ return () => {};
1893
+ }
1894
+ const element = resolveElement(options.element);
1895
+ if (!element) {
1896
+ return () => {};
1897
+ }
1898
+ const eventName = options.eventName ?? DEFAULT_EVENT_NAME;
1899
+ const queryParam = options.sessionQueryParam ?? DEFAULT_QUERY_PARAM;
1900
+ const sync = () => {
1901
+ const htmxWindow = window;
1902
+ const nextRoute = buildRoute(element, options.route, queryParam, stream.sessionId);
1903
+ if (nextRoute) {
1904
+ element.setAttribute("hx-get", nextRoute);
1905
+ }
1906
+ htmxWindow.htmx?.process?.(element);
1907
+ htmxWindow.htmx?.trigger?.(element, eventName);
1908
+ };
1909
+ const unsubscribe = stream.subscribe(sync);
1910
+ sync();
1911
+ return () => {
1912
+ unsubscribe();
1913
+ };
1914
+ };
1915
+
1916
+ // src/client/microphone.ts
1917
+ var clampSample = (value) => Math.max(-1, Math.min(1, value));
1918
+ var floatTo16BitPCM = (input) => {
1919
+ const output = new Int16Array(input.length);
1920
+ for (let index = 0;index < input.length; index += 1) {
1921
+ const sample = clampSample(input[index] ?? 0);
1922
+ output[index] = sample < 0 ? sample * 32768 : sample * 32767;
1923
+ }
1924
+ return new Uint8Array(output.buffer);
1925
+ };
1926
+ var getPcmLevel = (audio) => {
1927
+ const bytes = audio instanceof Uint8Array ? audio : new Uint8Array(audio);
1928
+ if (bytes.byteLength < 2) {
1929
+ return 0;
1930
+ }
1931
+ const samples = new Int16Array(bytes.buffer, bytes.byteOffset, Math.floor(bytes.byteLength / 2));
1932
+ if (samples.length === 0) {
1933
+ return 0;
1934
+ }
1935
+ let sumSquares = 0;
1936
+ for (const sample of samples) {
1937
+ const normalized = sample / 32768;
1938
+ sumSquares += normalized * normalized;
1939
+ }
1940
+ return Math.min(1, Math.max(0, Math.sqrt(sumSquares / samples.length) * 5.5));
1941
+ };
1942
+ var downsampleBuffer = (input, sourceRate, targetRate) => {
1943
+ if (sourceRate === targetRate) {
1944
+ return input;
1945
+ }
1946
+ const ratio = sourceRate / targetRate;
1947
+ const length = Math.round(input.length / ratio);
1948
+ const output = new Float32Array(length);
1949
+ let offsetResult = 0;
1950
+ let offsetBuffer = 0;
1951
+ while (offsetResult < output.length) {
1952
+ const nextOffsetBuffer = Math.round((offsetResult + 1) * ratio);
1953
+ let accum = 0;
1954
+ let count = 0;
1955
+ for (let index = offsetBuffer;index < nextOffsetBuffer && index < input.length; index += 1) {
1956
+ accum += input[index] ?? 0;
1957
+ count += 1;
1958
+ }
1959
+ output[offsetResult] = count > 0 ? accum / count : 0;
1960
+ offsetResult += 1;
1961
+ offsetBuffer = nextOffsetBuffer;
1962
+ }
1963
+ return output;
1964
+ };
1965
+ var createMicrophoneCapture = (options) => {
1966
+ let audioContext = null;
1967
+ let sourceNode = null;
1968
+ let processorNode = null;
1969
+ let mediaStream = null;
1970
+ const start = async () => {
1971
+ if (typeof navigator === "undefined" || !navigator.mediaDevices?.getUserMedia) {
1972
+ throw new Error("Browser microphone capture requires navigator.mediaDevices.getUserMedia.");
1973
+ }
1974
+ const AudioContextCtor = (typeof window !== "undefined" ? window.AudioContext ?? window.webkitAudioContext : undefined) ?? AudioContext;
1975
+ if (!AudioContextCtor) {
1976
+ throw new Error("Browser microphone capture requires AudioContext support.");
1977
+ }
1978
+ mediaStream = await navigator.mediaDevices.getUserMedia({
1979
+ audio: {
1980
+ channelCount: options.channelCount ?? 1
1981
+ }
1982
+ });
1983
+ audioContext = new AudioContextCtor;
1984
+ sourceNode = audioContext.createMediaStreamSource(mediaStream);
1985
+ processorNode = audioContext.createScriptProcessor(4096, 1, 1);
1986
+ processorNode.onaudioprocess = (event) => {
1987
+ const channel = event.inputBuffer.getChannelData(0);
1988
+ const downsampled = downsampleBuffer(channel, audioContext?.sampleRate ?? 48000, options.sampleRateHz ?? 16000);
1989
+ const pcm = floatTo16BitPCM(downsampled);
1990
+ options.onLevel?.(getPcmLevel(pcm));
1991
+ options.onAudio(pcm);
1992
+ };
1993
+ sourceNode.connect(processorNode);
1994
+ processorNode.connect(audioContext.destination);
1995
+ };
1996
+ const stop = () => {
1997
+ processorNode?.disconnect();
1998
+ sourceNode?.disconnect();
1999
+ mediaStream?.getTracks().forEach((track) => track.stop());
2000
+ audioContext?.close();
2001
+ options.onLevel?.(0);
2002
+ audioContext = null;
2003
+ mediaStream = null;
2004
+ processorNode = null;
2005
+ sourceNode = null;
2006
+ };
2007
+ return { start, stop };
2008
+ };
2009
+
2010
+ // src/audioConditioning.ts
2011
+ var DEFAULT_TARGET_LEVEL = 0.08;
2012
+ var DEFAULT_MAX_GAIN = 3;
2013
+ var DEFAULT_NOISE_GATE_THRESHOLD = 0.006;
2014
+ var DEFAULT_NOISE_GATE_ATTENUATION = 0.15;
2015
+ var toInt16Array = (audio) => {
2016
+ if (audio instanceof ArrayBuffer) {
2017
+ return new Int16Array(audio, 0, Math.floor(audio.byteLength / 2));
2018
+ }
2019
+ return new Int16Array(audio.buffer, audio.byteOffset, Math.floor(audio.byteLength / 2));
2020
+ };
2021
+ var computeRms = (samples) => {
2022
+ if (samples.length === 0) {
2023
+ return 0;
2024
+ }
2025
+ let sumSquares = 0;
2026
+ for (const sample of samples) {
2027
+ const normalized = sample / 32768;
2028
+ sumSquares += normalized * normalized;
2029
+ }
2030
+ return Math.sqrt(sumSquares / samples.length);
2031
+ };
2032
+ var resolveAudioConditioningConfig = (config) => {
2033
+ if (!config || config.enabled === false) {
2034
+ return;
2035
+ }
2036
+ return {
2037
+ enabled: true,
2038
+ maxGain: config.maxGain ?? DEFAULT_MAX_GAIN,
2039
+ noiseGateAttenuation: config.noiseGateAttenuation ?? DEFAULT_NOISE_GATE_ATTENUATION,
2040
+ noiseGateThreshold: config.noiseGateThreshold ?? DEFAULT_NOISE_GATE_THRESHOLD,
2041
+ targetLevel: config.targetLevel ?? DEFAULT_TARGET_LEVEL
2042
+ };
2043
+ };
2044
+ var conditionAudioChunk = (audio, config) => {
2045
+ if (!config) {
2046
+ return audio;
2047
+ }
2048
+ const source = toInt16Array(audio);
2049
+ if (source.length === 0) {
2050
+ return audio;
2051
+ }
2052
+ const rms = computeRms(source);
2053
+ const output = new Int16Array(source.length);
2054
+ const gateFactor = rms < config.noiseGateThreshold ? config.noiseGateAttenuation : 1;
2055
+ const baseLevel = Math.max(rms * gateFactor, 0.000001);
2056
+ const gain = Math.min(config.maxGain, config.targetLevel / baseLevel);
2057
+ const appliedGain = Math.max(0.25, gain) * gateFactor;
2058
+ for (let index = 0;index < source.length; index += 1) {
2059
+ const next = Math.round(source[index] * appliedGain);
2060
+ output[index] = Math.max(-32768, Math.min(32767, next));
2061
+ }
2062
+ return new Uint8Array(output.buffer);
2063
+ };
2064
+
2065
+ // src/turnProfiles.ts
2066
+ var TURN_PROFILE_DEFAULTS = {
2067
+ balanced: {
2068
+ qualityProfile: "general",
2069
+ silenceMs: 1400,
2070
+ speechThreshold: 0.012,
2071
+ transcriptStabilityMs: 1000
2072
+ },
2073
+ fast: {
2074
+ qualityProfile: "general",
2075
+ silenceMs: 700,
2076
+ speechThreshold: 0.015,
2077
+ transcriptStabilityMs: 450
2078
+ },
2079
+ "long-form": {
2080
+ qualityProfile: "general",
2081
+ silenceMs: 2200,
2082
+ speechThreshold: 0.01,
2083
+ transcriptStabilityMs: 1500
2084
+ }
2085
+ };
2086
+ var QUALITY_PROFILE_DEFAULTS = {
2087
+ general: {},
2088
+ "accent-heavy": {
2089
+ silenceMs: 1200,
2090
+ speechThreshold: 0.01,
2091
+ transcriptStabilityMs: 1200
2092
+ },
2093
+ "noisy-room": {
2094
+ silenceMs: 2000,
2095
+ speechThreshold: 0.02,
2096
+ transcriptStabilityMs: 1600
2097
+ },
2098
+ "short-command": {
2099
+ silenceMs: 500,
2100
+ speechThreshold: 0.016,
2101
+ transcriptStabilityMs: 420
2102
+ }
2103
+ };
2104
+ var DEFAULT_TURN_PROFILE = "fast";
2105
+ var DEFAULT_QUALITY_PROFILE = "general";
2106
+ var resolveTurnDetectionConfig = (config) => {
2107
+ const profile = config?.profile ?? DEFAULT_TURN_PROFILE;
2108
+ const qualityProfile = config?.qualityProfile ?? DEFAULT_QUALITY_PROFILE;
2109
+ const preset = TURN_PROFILE_DEFAULTS[profile];
2110
+ const quality = QUALITY_PROFILE_DEFAULTS[qualityProfile];
2111
+ return {
2112
+ profile,
2113
+ qualityProfile,
2114
+ silenceMs: config?.silenceMs ?? quality.silenceMs ?? preset.silenceMs,
2115
+ speechThreshold: config?.speechThreshold ?? quality.speechThreshold ?? preset.speechThreshold,
2116
+ transcriptStabilityMs: config?.transcriptStabilityMs ?? quality.transcriptStabilityMs ?? preset.transcriptStabilityMs
2117
+ };
2118
+ };
2119
+
2120
+ // src/presets.ts
2121
+ var PRESET_INPUTS = {
2122
+ chat: {
2123
+ audioConditioning: {
2124
+ enabled: true,
2125
+ maxGain: 2.5,
2126
+ noiseGateAttenuation: 0,
2127
+ noiseGateThreshold: 0.004,
2128
+ targetLevel: 0.08
2129
+ },
2130
+ capture: {
2131
+ channelCount: 1,
2132
+ sampleRateHz: 16000
2133
+ },
2134
+ connection: {
2135
+ maxReconnectAttempts: 10,
2136
+ pingInterval: 30000,
2137
+ reconnect: true
2138
+ },
2139
+ sttLifecycle: "continuous",
2140
+ turnDetection: {
2141
+ qualityProfile: "short-command",
2142
+ profile: "balanced"
2143
+ }
2144
+ },
2145
+ default: {
2146
+ capture: {
2147
+ channelCount: 1,
2148
+ sampleRateHz: 16000
2149
+ },
2150
+ connection: {
2151
+ maxReconnectAttempts: 10,
2152
+ pingInterval: 30000,
2153
+ reconnect: true
2154
+ },
2155
+ sttLifecycle: "continuous",
2156
+ turnDetection: {
2157
+ qualityProfile: "general",
2158
+ profile: "fast"
2159
+ }
2160
+ },
2161
+ dictation: {
2162
+ audioConditioning: {
2163
+ enabled: true,
2164
+ maxGain: 2.25,
2165
+ noiseGateAttenuation: 0.05,
2166
+ noiseGateThreshold: 0.003,
2167
+ targetLevel: 0.08
2168
+ },
2169
+ capture: {
2170
+ channelCount: 1,
2171
+ sampleRateHz: 16000
2172
+ },
2173
+ connection: {
2174
+ maxReconnectAttempts: 12,
2175
+ pingInterval: 30000,
2176
+ reconnect: true
2177
+ },
2178
+ sttLifecycle: "continuous",
2179
+ turnDetection: {
2180
+ qualityProfile: "accent-heavy",
2181
+ profile: "long-form"
2182
+ }
2183
+ },
2184
+ "guided-intake": {
2185
+ audioConditioning: {
2186
+ enabled: true,
2187
+ maxGain: 2.5,
2188
+ noiseGateAttenuation: 0,
2189
+ noiseGateThreshold: 0.004,
2190
+ targetLevel: 0.08
2191
+ },
2192
+ capture: {
2193
+ channelCount: 1,
2194
+ sampleRateHz: 16000
2195
+ },
2196
+ connection: {
2197
+ maxReconnectAttempts: 12,
2198
+ pingInterval: 30000,
2199
+ reconnect: true
2200
+ },
2201
+ sttLifecycle: "turn-scoped",
2202
+ turnDetection: {
2203
+ qualityProfile: "accent-heavy",
2204
+ profile: "long-form"
2205
+ }
2206
+ },
2207
+ "noisy-room": {
2208
+ audioConditioning: {
2209
+ enabled: true,
2210
+ maxGain: 3,
2211
+ noiseGateAttenuation: 0.12,
2212
+ noiseGateThreshold: 0.006,
2213
+ targetLevel: 0.085
2214
+ },
2215
+ capture: {
2216
+ channelCount: 1,
2217
+ sampleRateHz: 16000
2218
+ },
2219
+ connection: {
2220
+ maxReconnectAttempts: 14,
2221
+ pingInterval: 45000,
2222
+ reconnect: true
2223
+ },
2224
+ sttLifecycle: "continuous",
2225
+ turnDetection: {
2226
+ qualityProfile: "noisy-room",
2227
+ profile: "long-form",
2228
+ silenceMs: 2100,
2229
+ speechThreshold: 0.02,
2230
+ transcriptStabilityMs: 1650
2231
+ }
2232
+ },
2233
+ "pstn-balanced": {
2234
+ audioConditioning: {
2235
+ enabled: true,
2236
+ maxGain: 2.8,
1011
2237
  noiseGateAttenuation: 0.07,
1012
2238
  noiseGateThreshold: 0.005,
1013
2239
  targetLevel: 0.08
1014
2240
  },
1015
- capture: {
1016
- channelCount: 1,
1017
- sampleRateHz: 16000
2241
+ capture: {
2242
+ channelCount: 1,
2243
+ sampleRateHz: 16000
2244
+ },
2245
+ connection: {
2246
+ maxReconnectAttempts: 14,
2247
+ pingInterval: 45000,
2248
+ reconnect: true
2249
+ },
2250
+ sttLifecycle: "continuous",
2251
+ turnDetection: {
2252
+ qualityProfile: "noisy-room",
2253
+ profile: "long-form",
2254
+ silenceMs: 660,
2255
+ speechThreshold: 0.012,
2256
+ transcriptStabilityMs: 300
2257
+ }
2258
+ },
2259
+ "pstn-fast": {
2260
+ audioConditioning: {
2261
+ enabled: true,
2262
+ maxGain: 2.75,
2263
+ noiseGateAttenuation: 0.06,
2264
+ noiseGateThreshold: 0.005,
2265
+ targetLevel: 0.08
2266
+ },
2267
+ capture: {
2268
+ channelCount: 1,
2269
+ sampleRateHz: 16000
2270
+ },
2271
+ connection: {
2272
+ maxReconnectAttempts: 14,
2273
+ pingInterval: 45000,
2274
+ reconnect: true
2275
+ },
2276
+ sttLifecycle: "continuous",
2277
+ turnDetection: {
2278
+ qualityProfile: "noisy-room",
2279
+ profile: "long-form",
2280
+ silenceMs: 620,
2281
+ speechThreshold: 0.012,
2282
+ transcriptStabilityMs: 280
2283
+ }
2284
+ },
2285
+ reliability: {
2286
+ audioConditioning: {
2287
+ enabled: true,
2288
+ maxGain: 2.9,
2289
+ noiseGateAttenuation: 0.08,
2290
+ noiseGateThreshold: 0.005,
2291
+ targetLevel: 0.08
2292
+ },
2293
+ capture: {
2294
+ channelCount: 1,
2295
+ sampleRateHz: 16000
2296
+ },
2297
+ connection: {
2298
+ maxReconnectAttempts: 14,
2299
+ pingInterval: 45000,
2300
+ reconnect: true
2301
+ },
2302
+ sttLifecycle: "continuous",
2303
+ turnDetection: {
2304
+ qualityProfile: "noisy-room",
2305
+ profile: "long-form"
2306
+ }
2307
+ }
2308
+ };
2309
+ var resolveVoiceRuntimePreset = (name = "default") => {
2310
+ const preset = PRESET_INPUTS[name];
2311
+ return {
2312
+ audioConditioning: resolveAudioConditioningConfig(preset.audioConditioning),
2313
+ capture: {
2314
+ channelCount: preset.capture?.channelCount ?? 1,
2315
+ sampleRateHz: preset.capture?.sampleRateHz ?? 16000
2316
+ },
2317
+ connection: {
2318
+ ...preset.connection
2319
+ },
2320
+ name,
2321
+ sttLifecycle: preset.sttLifecycle ?? "continuous",
2322
+ turnDetection: resolveTurnDetectionConfig(preset.turnDetection)
2323
+ };
2324
+ };
2325
+
2326
+ // src/client/controller.ts
2327
+ var createInitialState2 = (stream) => ({
2328
+ assistantAudio: [...stream.assistantAudio],
2329
+ assistantTexts: [...stream.assistantTexts],
2330
+ call: stream.call,
2331
+ error: stream.error,
2332
+ isConnected: stream.isConnected,
2333
+ isRecording: false,
2334
+ partial: stream.partial,
2335
+ reconnect: stream.reconnect,
2336
+ recordingError: null,
2337
+ sessionId: stream.sessionId,
2338
+ scenarioId: stream.scenarioId,
2339
+ status: stream.status,
2340
+ turns: [...stream.turns]
2341
+ });
2342
+ var createVoiceController = (path, options = {}) => {
2343
+ const preset = resolveVoiceRuntimePreset(options.preset);
2344
+ const stream = createVoiceStream(path, {
2345
+ ...preset.connection,
2346
+ ...options.connection
2347
+ });
2348
+ let capture = null;
2349
+ let state = createInitialState2(stream);
2350
+ const subscribers = new Set;
2351
+ const notify = () => {
2352
+ for (const subscriber of subscribers) {
2353
+ subscriber();
2354
+ }
2355
+ };
2356
+ const sync = () => {
2357
+ state = {
2358
+ ...state,
2359
+ assistantAudio: [...stream.assistantAudio],
2360
+ assistantTexts: [...stream.assistantTexts],
2361
+ call: stream.call,
2362
+ error: stream.error,
2363
+ isConnected: stream.isConnected,
2364
+ partial: stream.partial,
2365
+ reconnect: stream.reconnect,
2366
+ sessionId: stream.sessionId,
2367
+ scenarioId: stream.scenarioId,
2368
+ status: stream.status,
2369
+ turns: [...stream.turns]
2370
+ };
2371
+ if (options.autoStopOnComplete !== false && state.status === "completed" && state.isRecording) {
2372
+ capture?.stop();
2373
+ capture = null;
2374
+ state = {
2375
+ ...state,
2376
+ isRecording: false
2377
+ };
2378
+ }
2379
+ notify();
2380
+ };
2381
+ const unsubscribeStream = stream.subscribe(sync);
2382
+ sync();
2383
+ const ensureCapture = () => {
2384
+ if (capture) {
2385
+ return capture;
2386
+ }
2387
+ capture = createMicrophoneCapture({
2388
+ channelCount: options.capture?.channelCount ?? preset.capture.channelCount,
2389
+ onLevel: options.capture?.onLevel,
2390
+ onAudio: (audio) => {
2391
+ if (options.capture?.onAudio) {
2392
+ options.capture.onAudio(audio, stream.sendAudio);
2393
+ return;
2394
+ }
2395
+ stream.sendAudio(audio);
2396
+ },
2397
+ sampleRateHz: options.capture?.sampleRateHz ?? preset.capture.sampleRateHz
2398
+ });
2399
+ return capture;
2400
+ };
2401
+ const stopRecording = () => {
2402
+ capture?.stop();
2403
+ capture = null;
2404
+ state = {
2405
+ ...state,
2406
+ isRecording: false
2407
+ };
2408
+ notify();
2409
+ };
2410
+ const startRecording = async () => {
2411
+ if (state.isRecording) {
2412
+ return;
2413
+ }
2414
+ try {
2415
+ state = {
2416
+ ...state,
2417
+ recordingError: null
2418
+ };
2419
+ notify();
2420
+ await ensureCapture().start();
2421
+ state = {
2422
+ ...state,
2423
+ isRecording: true
2424
+ };
2425
+ notify();
2426
+ } catch (error) {
2427
+ capture = null;
2428
+ state = {
2429
+ ...state,
2430
+ isRecording: false,
2431
+ recordingError: error instanceof Error ? error.message : String(error)
2432
+ };
2433
+ notify();
2434
+ throw error;
2435
+ }
2436
+ };
2437
+ const close = () => {
2438
+ unsubscribeStream();
2439
+ stopRecording();
2440
+ stream.close();
2441
+ };
2442
+ return {
2443
+ bindHTMX(bindingOptions) {
2444
+ return bindVoiceHTMX(stream, bindingOptions);
2445
+ },
2446
+ callControl: (message) => stream.callControl(message),
2447
+ close,
2448
+ endTurn: () => stream.endTurn(),
2449
+ get error() {
2450
+ return state.error;
2451
+ },
2452
+ getServerSnapshot: () => state,
2453
+ getSnapshot: () => state,
2454
+ get isConnected() {
2455
+ return state.isConnected;
2456
+ },
2457
+ get isRecording() {
2458
+ return state.isRecording;
2459
+ },
2460
+ get partial() {
2461
+ return state.partial;
2462
+ },
2463
+ get recordingError() {
2464
+ return state.recordingError;
2465
+ },
2466
+ get reconnect() {
2467
+ return state.reconnect;
2468
+ },
2469
+ sendAudio: (audio) => stream.sendAudio(audio),
2470
+ get sessionId() {
2471
+ return state.sessionId;
2472
+ },
2473
+ get scenarioId() {
2474
+ return state.scenarioId;
1018
2475
  },
1019
- connection: {
1020
- maxReconnectAttempts: 14,
1021
- pingInterval: 45000,
1022
- reconnect: true
2476
+ startRecording,
2477
+ get status() {
2478
+ return state.status;
1023
2479
  },
1024
- sttLifecycle: "continuous",
1025
- turnDetection: {
1026
- qualityProfile: "noisy-room",
1027
- profile: "long-form",
1028
- silenceMs: 660,
1029
- speechThreshold: 0.012,
1030
- transcriptStabilityMs: 300
2480
+ stopRecording,
2481
+ subscribe: (subscriber) => {
2482
+ subscribers.add(subscriber);
2483
+ return () => {
2484
+ subscribers.delete(subscriber);
2485
+ };
2486
+ },
2487
+ toggleRecording: async () => {
2488
+ if (state.isRecording) {
2489
+ stopRecording();
2490
+ return;
2491
+ }
2492
+ await startRecording();
2493
+ },
2494
+ get turns() {
2495
+ return state.turns;
2496
+ },
2497
+ get assistantTexts() {
2498
+ return state.assistantTexts;
2499
+ },
2500
+ get assistantAudio() {
2501
+ return state.assistantAudio;
2502
+ },
2503
+ get call() {
2504
+ return state.call;
2505
+ }
2506
+ };
2507
+ };
2508
+
2509
+ // src/angular/voice-controller.service.ts
2510
+ var _dec = [
2511
+ Injectable9({ providedIn: "root" })
2512
+ ];
2513
+ var _init = __decoratorStart(undefined);
2514
+
2515
+ class VoiceControllerService {
2516
+ connect(path, options = {}) {
2517
+ const controller = createVoiceController(path, options);
2518
+ const assistantAudioSignal = signal9([]);
2519
+ const assistantTextsSignal = signal9([]);
2520
+ const errorSignal = signal9(null);
2521
+ const isConnectedSignal = signal9(false);
2522
+ const isRecordingSignal = signal9(false);
2523
+ const partialSignal = signal9("");
2524
+ const reconnectSignal = signal9(controller.reconnect);
2525
+ const recordingErrorSignal = signal9(null);
2526
+ const sessionIdSignal = signal9(controller.sessionId);
2527
+ const statusSignal = signal9(controller.status);
2528
+ const turnsSignal = signal9([]);
2529
+ const sync = () => {
2530
+ assistantAudioSignal.set([...controller.assistantAudio]);
2531
+ assistantTextsSignal.set([...controller.assistantTexts]);
2532
+ errorSignal.set(controller.error);
2533
+ isConnectedSignal.set(controller.isConnected);
2534
+ isRecordingSignal.set(controller.isRecording);
2535
+ partialSignal.set(controller.partial);
2536
+ reconnectSignal.set(controller.reconnect);
2537
+ recordingErrorSignal.set(controller.recordingError);
2538
+ sessionIdSignal.set(controller.sessionId);
2539
+ statusSignal.set(controller.status);
2540
+ turnsSignal.set([...controller.turns]);
2541
+ };
2542
+ const unsubscribe = controller.subscribe(sync);
2543
+ sync();
2544
+ return {
2545
+ assistantAudio: computed9(() => assistantAudioSignal()),
2546
+ assistantTexts: computed9(() => assistantTextsSignal()),
2547
+ bindHTMX: controller.bindHTMX,
2548
+ close: () => {
2549
+ unsubscribe();
2550
+ controller.close();
2551
+ },
2552
+ endTurn: () => controller.endTurn(),
2553
+ error: computed9(() => errorSignal()),
2554
+ isConnected: computed9(() => isConnectedSignal()),
2555
+ isRecording: computed9(() => isRecordingSignal()),
2556
+ partial: computed9(() => partialSignal()),
2557
+ reconnect: computed9(() => reconnectSignal()),
2558
+ recordingError: computed9(() => recordingErrorSignal()),
2559
+ sendAudio: (audio) => controller.sendAudio(audio),
2560
+ sessionId: computed9(() => sessionIdSignal()),
2561
+ startRecording: () => controller.startRecording(),
2562
+ status: computed9(() => statusSignal()),
2563
+ stopRecording: () => controller.stopRecording(),
2564
+ toggleRecording: () => controller.toggleRecording(),
2565
+ turns: computed9(() => turnsSignal())
2566
+ };
2567
+ }
2568
+ }
2569
+ VoiceControllerService = __decorateElement(_init, 0, "VoiceControllerService", _dec, VoiceControllerService);
2570
+ __runInitializers(_init, 1, VoiceControllerService);
2571
+ __decoratorMetadata(_init, VoiceControllerService);
2572
+ let _VoiceControllerService = VoiceControllerService;
2573
+ // src/angular/voice-provider-capabilities.service.ts
2574
+ import { computed as computed10, Injectable as Injectable10, signal as signal10 } from "@angular/core";
2575
+
2576
+ // src/client/providerCapabilities.ts
2577
+ var fetchVoiceProviderCapabilities = async (path = "/api/provider-capabilities", options = {}) => {
2578
+ const fetchImpl = options.fetch ?? globalThis.fetch;
2579
+ const response = await fetchImpl(path);
2580
+ if (!response.ok) {
2581
+ throw new Error(`Voice provider capabilities failed: HTTP ${response.status}`);
2582
+ }
2583
+ return await response.json();
2584
+ };
2585
+ var createVoiceProviderCapabilitiesStore = (path = "/api/provider-capabilities", options = {}) => {
2586
+ const listeners = new Set;
2587
+ let closed = false;
2588
+ let timer;
2589
+ let snapshot = {
2590
+ error: null,
2591
+ isLoading: false
2592
+ };
2593
+ const emit = () => {
2594
+ for (const listener of listeners) {
2595
+ listener();
2596
+ }
2597
+ };
2598
+ const refresh = async () => {
2599
+ if (closed) {
2600
+ return snapshot.report;
2601
+ }
2602
+ snapshot = {
2603
+ ...snapshot,
2604
+ error: null,
2605
+ isLoading: true
2606
+ };
2607
+ emit();
2608
+ try {
2609
+ const report = await fetchVoiceProviderCapabilities(path, options);
2610
+ snapshot = {
2611
+ error: null,
2612
+ isLoading: false,
2613
+ report,
2614
+ updatedAt: Date.now()
2615
+ };
2616
+ emit();
2617
+ return report;
2618
+ } catch (error) {
2619
+ snapshot = {
2620
+ ...snapshot,
2621
+ error: error instanceof Error ? error.message : String(error),
2622
+ isLoading: false
2623
+ };
2624
+ emit();
2625
+ throw error;
2626
+ }
2627
+ };
2628
+ const close = () => {
2629
+ closed = true;
2630
+ if (timer) {
2631
+ clearInterval(timer);
2632
+ timer = undefined;
2633
+ }
2634
+ listeners.clear();
2635
+ };
2636
+ if (options.intervalMs && options.intervalMs > 0) {
2637
+ timer = setInterval(() => {
2638
+ refresh().catch(() => {});
2639
+ }, options.intervalMs);
2640
+ }
2641
+ return {
2642
+ close,
2643
+ getServerSnapshot: () => snapshot,
2644
+ getSnapshot: () => snapshot,
2645
+ refresh,
2646
+ subscribe: (listener) => {
2647
+ listeners.add(listener);
2648
+ return () => {
2649
+ listeners.delete(listener);
2650
+ };
2651
+ }
2652
+ };
2653
+ };
2654
+
2655
+ // src/angular/voice-provider-capabilities.service.ts
2656
+ var _dec = [
2657
+ Injectable10({ providedIn: "root" })
2658
+ ];
2659
+ var _init = __decoratorStart(undefined);
2660
+
2661
+ class VoiceProviderCapabilitiesService {
2662
+ connect(path = "/api/provider-capabilities", options = {}) {
2663
+ const store = createVoiceProviderCapabilitiesStore(path, options);
2664
+ const errorSignal = signal10(null);
2665
+ const isLoadingSignal = signal10(false);
2666
+ const reportSignal = signal10(undefined);
2667
+ const updatedAtSignal = signal10(undefined);
2668
+ const sync = () => {
2669
+ const snapshot = store.getSnapshot();
2670
+ errorSignal.set(snapshot.error);
2671
+ isLoadingSignal.set(snapshot.isLoading);
2672
+ reportSignal.set(snapshot.report);
2673
+ updatedAtSignal.set(snapshot.updatedAt);
2674
+ };
2675
+ const unsubscribe = store.subscribe(sync);
2676
+ sync();
2677
+ store.refresh().catch(() => {});
2678
+ return {
2679
+ close: () => {
2680
+ unsubscribe();
2681
+ store.close();
2682
+ },
2683
+ error: computed10(() => errorSignal()),
2684
+ isLoading: computed10(() => isLoadingSignal()),
2685
+ refresh: store.refresh,
2686
+ report: computed10(() => reportSignal()),
2687
+ updatedAt: computed10(() => updatedAtSignal())
2688
+ };
2689
+ }
2690
+ }
2691
+ VoiceProviderCapabilitiesService = __decorateElement(_init, 0, "VoiceProviderCapabilitiesService", _dec, VoiceProviderCapabilitiesService);
2692
+ __runInitializers(_init, 1, VoiceProviderCapabilitiesService);
2693
+ __decoratorMetadata(_init, VoiceProviderCapabilitiesService);
2694
+ let _VoiceProviderCapabilitiesService = VoiceProviderCapabilitiesService;
2695
+ // src/angular/voice-provider-contracts.service.ts
2696
+ import { computed as computed11, Injectable as Injectable11, signal as signal11 } from "@angular/core";
2697
+
2698
+ // src/client/providerContracts.ts
2699
+ var fetchVoiceProviderContracts = async (path = "/api/provider-contracts", options = {}) => {
2700
+ const fetchImpl = options.fetch ?? globalThis.fetch;
2701
+ const response = await fetchImpl(path);
2702
+ if (!response.ok) {
2703
+ throw new Error(`Voice provider contracts failed: HTTP ${response.status}`);
2704
+ }
2705
+ return await response.json();
2706
+ };
2707
+ var createVoiceProviderContractsStore = (path = "/api/provider-contracts", options = {}) => {
2708
+ const listeners = new Set;
2709
+ let closed = false;
2710
+ let timer;
2711
+ let snapshot = {
2712
+ error: null,
2713
+ isLoading: false
2714
+ };
2715
+ const emit = () => {
2716
+ for (const listener of listeners) {
2717
+ listener();
2718
+ }
2719
+ };
2720
+ const refresh = async () => {
2721
+ if (closed) {
2722
+ return snapshot.report;
2723
+ }
2724
+ snapshot = { ...snapshot, error: null, isLoading: true };
2725
+ emit();
2726
+ try {
2727
+ const report = await fetchVoiceProviderContracts(path, options);
2728
+ snapshot = {
2729
+ error: null,
2730
+ isLoading: false,
2731
+ report,
2732
+ updatedAt: Date.now()
2733
+ };
2734
+ emit();
2735
+ return report;
2736
+ } catch (error) {
2737
+ snapshot = {
2738
+ ...snapshot,
2739
+ error: error instanceof Error ? error.message : String(error),
2740
+ isLoading: false
2741
+ };
2742
+ emit();
2743
+ throw error;
2744
+ }
2745
+ };
2746
+ const close = () => {
2747
+ closed = true;
2748
+ if (timer) {
2749
+ clearInterval(timer);
2750
+ timer = undefined;
2751
+ }
2752
+ listeners.clear();
2753
+ };
2754
+ if (options.intervalMs && options.intervalMs > 0) {
2755
+ timer = setInterval(() => {
2756
+ refresh().catch(() => {});
2757
+ }, options.intervalMs);
2758
+ }
2759
+ return {
2760
+ close,
2761
+ getServerSnapshot: () => snapshot,
2762
+ getSnapshot: () => snapshot,
2763
+ refresh,
2764
+ subscribe: (listener) => {
2765
+ listeners.add(listener);
2766
+ return () => {
2767
+ listeners.delete(listener);
2768
+ };
2769
+ }
2770
+ };
2771
+ };
2772
+
2773
+ // src/angular/voice-provider-contracts.service.ts
2774
+ var _dec = [
2775
+ Injectable11({ providedIn: "root" })
2776
+ ];
2777
+ var _init = __decoratorStart(undefined);
2778
+
2779
+ class VoiceProviderContractsService {
2780
+ connect(path = "/api/provider-contracts", options = {}) {
2781
+ const store = createVoiceProviderContractsStore(path, options);
2782
+ const errorSignal = signal11(null);
2783
+ const isLoadingSignal = signal11(false);
2784
+ const reportSignal = signal11(undefined);
2785
+ const updatedAtSignal = signal11(undefined);
2786
+ const sync = () => {
2787
+ const snapshot = store.getSnapshot();
2788
+ errorSignal.set(snapshot.error);
2789
+ isLoadingSignal.set(snapshot.isLoading);
2790
+ reportSignal.set(snapshot.report);
2791
+ updatedAtSignal.set(snapshot.updatedAt);
2792
+ };
2793
+ const unsubscribe = store.subscribe(sync);
2794
+ sync();
2795
+ store.refresh().catch(() => {});
2796
+ return {
2797
+ close: () => {
2798
+ unsubscribe();
2799
+ store.close();
2800
+ },
2801
+ error: computed11(() => errorSignal()),
2802
+ isLoading: computed11(() => isLoadingSignal()),
2803
+ refresh: store.refresh,
2804
+ report: computed11(() => reportSignal()),
2805
+ updatedAt: computed11(() => updatedAtSignal())
2806
+ };
2807
+ }
2808
+ }
2809
+ VoiceProviderContractsService = __decorateElement(_init, 0, "VoiceProviderContractsService", _dec, VoiceProviderContractsService);
2810
+ __runInitializers(_init, 1, VoiceProviderContractsService);
2811
+ __decoratorMetadata(_init, VoiceProviderContractsService);
2812
+ let _VoiceProviderContractsService = VoiceProviderContractsService;
2813
+ // src/angular/voice-provider-status.service.ts
2814
+ import { computed as computed12, Injectable as Injectable12, signal as signal12 } from "@angular/core";
2815
+
2816
+ // src/client/providerStatus.ts
2817
+ var fetchVoiceProviderStatus = async (path = "/api/provider-status", options = {}) => {
2818
+ const fetchImpl = options.fetch ?? globalThis.fetch;
2819
+ const response = await fetchImpl(path);
2820
+ if (!response.ok) {
2821
+ throw new Error(`Voice provider status failed: HTTP ${response.status}`);
2822
+ }
2823
+ return await response.json();
2824
+ };
2825
+ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {}) => {
2826
+ const listeners = new Set;
2827
+ let closed = false;
2828
+ let timer;
2829
+ let snapshot = {
2830
+ error: null,
2831
+ isLoading: false,
2832
+ providers: []
2833
+ };
2834
+ const emit = () => {
2835
+ for (const listener of listeners) {
2836
+ listener();
2837
+ }
2838
+ };
2839
+ const refresh = async () => {
2840
+ if (closed) {
2841
+ return snapshot.providers;
2842
+ }
2843
+ snapshot = {
2844
+ ...snapshot,
2845
+ error: null,
2846
+ isLoading: true
2847
+ };
2848
+ emit();
2849
+ try {
2850
+ const providers = await fetchVoiceProviderStatus(path, options);
2851
+ snapshot = {
2852
+ error: null,
2853
+ isLoading: false,
2854
+ providers,
2855
+ updatedAt: Date.now()
2856
+ };
2857
+ emit();
2858
+ return providers;
2859
+ } catch (error) {
2860
+ snapshot = {
2861
+ ...snapshot,
2862
+ error: error instanceof Error ? error.message : String(error),
2863
+ isLoading: false
2864
+ };
2865
+ emit();
2866
+ throw error;
2867
+ }
2868
+ };
2869
+ const close = () => {
2870
+ closed = true;
2871
+ if (timer) {
2872
+ clearInterval(timer);
2873
+ timer = undefined;
2874
+ }
2875
+ listeners.clear();
2876
+ };
2877
+ if (options.intervalMs && options.intervalMs > 0) {
2878
+ timer = setInterval(() => {
2879
+ refresh().catch(() => {});
2880
+ }, options.intervalMs);
2881
+ }
2882
+ return {
2883
+ close,
2884
+ getServerSnapshot: () => snapshot,
2885
+ getSnapshot: () => snapshot,
2886
+ refresh,
2887
+ subscribe: (listener) => {
2888
+ listeners.add(listener);
2889
+ return () => {
2890
+ listeners.delete(listener);
2891
+ };
2892
+ }
2893
+ };
2894
+ };
2895
+
2896
+ // src/angular/voice-provider-status.service.ts
2897
+ var _dec = [
2898
+ Injectable12({ providedIn: "root" })
2899
+ ];
2900
+ var _init = __decoratorStart(undefined);
2901
+
2902
+ class VoiceProviderStatusService {
2903
+ connect(path = "/api/provider-status", options = {}) {
2904
+ const store = createVoiceProviderStatusStore(path, options);
2905
+ const errorSignal = signal12(null);
2906
+ const isLoadingSignal = signal12(false);
2907
+ const providersSignal = signal12([]);
2908
+ const updatedAtSignal = signal12(undefined);
2909
+ const sync = () => {
2910
+ const snapshot = store.getSnapshot();
2911
+ errorSignal.set(snapshot.error);
2912
+ isLoadingSignal.set(snapshot.isLoading);
2913
+ providersSignal.set([...snapshot.providers]);
2914
+ updatedAtSignal.set(snapshot.updatedAt);
2915
+ };
2916
+ const unsubscribe = store.subscribe(sync);
2917
+ sync();
2918
+ store.refresh().catch(() => {});
2919
+ return {
2920
+ close: () => {
2921
+ unsubscribe();
2922
+ store.close();
2923
+ },
2924
+ error: computed12(() => errorSignal()),
2925
+ isLoading: computed12(() => isLoadingSignal()),
2926
+ providers: computed12(() => providersSignal()),
2927
+ refresh: store.refresh,
2928
+ updatedAt: computed12(() => updatedAtSignal())
2929
+ };
2930
+ }
2931
+ }
2932
+ VoiceProviderStatusService = __decorateElement(_init, 0, "VoiceProviderStatusService", _dec, VoiceProviderStatusService);
2933
+ __runInitializers(_init, 1, VoiceProviderStatusService);
2934
+ __decoratorMetadata(_init, VoiceProviderStatusService);
2935
+ let _VoiceProviderStatusService = VoiceProviderStatusService;
2936
+ // src/angular/voice-routing-status.service.ts
2937
+ import { Injectable as Injectable13, signal as signal13 } from "@angular/core";
2938
+
2939
+ // src/client/routingStatus.ts
2940
+ var fetchVoiceRoutingStatus = async (path = "/api/routing/latest", options = {}) => {
2941
+ const fetchImpl = options.fetch ?? globalThis.fetch;
2942
+ const response = await fetchImpl(path);
2943
+ if (!response.ok) {
2944
+ throw new Error(`Voice routing status failed: HTTP ${response.status}`);
2945
+ }
2946
+ return await response.json();
2947
+ };
2948
+ var createVoiceRoutingStatusStore = (path = "/api/routing/latest", options = {}) => {
2949
+ const listeners = new Set;
2950
+ let closed = false;
2951
+ let timer;
2952
+ let snapshot = {
2953
+ decision: null,
2954
+ error: null,
2955
+ isLoading: false
2956
+ };
2957
+ const emit = () => {
2958
+ for (const listener of listeners) {
2959
+ listener();
2960
+ }
2961
+ };
2962
+ const refresh = async () => {
2963
+ if (closed) {
2964
+ return snapshot.decision;
2965
+ }
2966
+ snapshot = {
2967
+ ...snapshot,
2968
+ error: null,
2969
+ isLoading: true
2970
+ };
2971
+ emit();
2972
+ try {
2973
+ const decision = await fetchVoiceRoutingStatus(path, options);
2974
+ snapshot = {
2975
+ decision,
2976
+ error: null,
2977
+ isLoading: false,
2978
+ updatedAt: Date.now()
2979
+ };
2980
+ emit();
2981
+ return decision;
2982
+ } catch (error) {
2983
+ snapshot = {
2984
+ ...snapshot,
2985
+ error: error instanceof Error ? error.message : String(error),
2986
+ isLoading: false
2987
+ };
2988
+ emit();
2989
+ throw error;
2990
+ }
2991
+ };
2992
+ const close = () => {
2993
+ closed = true;
2994
+ if (timer) {
2995
+ clearInterval(timer);
2996
+ timer = undefined;
2997
+ }
2998
+ listeners.clear();
2999
+ };
3000
+ if (options.intervalMs && options.intervalMs > 0) {
3001
+ timer = setInterval(() => {
3002
+ refresh().catch(() => {});
3003
+ }, options.intervalMs);
3004
+ }
3005
+ return {
3006
+ close,
3007
+ getServerSnapshot: () => snapshot,
3008
+ getSnapshot: () => snapshot,
3009
+ refresh,
3010
+ subscribe: (listener) => {
3011
+ listeners.add(listener);
3012
+ return () => {
3013
+ listeners.delete(listener);
3014
+ };
3015
+ }
3016
+ };
3017
+ };
3018
+
3019
+ // src/angular/voice-routing-status.service.ts
3020
+ var _dec = [
3021
+ Injectable13({ providedIn: "root" })
3022
+ ];
3023
+ var _init = __decoratorStart(undefined);
3024
+
3025
+ class VoiceRoutingStatusService {
3026
+ connect(path = "/api/routing/latest", options = {}) {
3027
+ const store = createVoiceRoutingStatusStore(path, options);
3028
+ const decisionSignal = signal13(null);
3029
+ const errorSignal = signal13(null);
3030
+ const isLoadingSignal = signal13(false);
3031
+ const updatedAtSignal = signal13(undefined);
3032
+ const sync = () => {
3033
+ const snapshot = store.getSnapshot();
3034
+ decisionSignal.set(snapshot.decision);
3035
+ errorSignal.set(snapshot.error);
3036
+ isLoadingSignal.set(snapshot.isLoading);
3037
+ updatedAtSignal.set(snapshot.updatedAt);
3038
+ };
3039
+ const unsubscribe = store.subscribe(sync);
3040
+ sync();
3041
+ store.refresh().catch(() => {});
3042
+ return {
3043
+ close: () => {
3044
+ unsubscribe();
3045
+ store.close();
3046
+ },
3047
+ decision: decisionSignal.asReadonly(),
3048
+ error: errorSignal.asReadonly(),
3049
+ isLoading: isLoadingSignal.asReadonly(),
3050
+ refresh: store.refresh,
3051
+ updatedAt: updatedAtSignal.asReadonly()
3052
+ };
3053
+ }
3054
+ }
3055
+ VoiceRoutingStatusService = __decorateElement(_init, 0, "VoiceRoutingStatusService", _dec, VoiceRoutingStatusService);
3056
+ __runInitializers(_init, 1, VoiceRoutingStatusService);
3057
+ __decoratorMetadata(_init, VoiceRoutingStatusService);
3058
+ let _VoiceRoutingStatusService = VoiceRoutingStatusService;
3059
+ // src/angular/voice-trace-timeline.service.ts
3060
+ import { computed as computed13, Injectable as Injectable14, signal as signal14 } from "@angular/core";
3061
+
3062
+ // src/client/traceTimeline.ts
3063
+ var fetchVoiceTraceTimeline = async (path = "/api/voice-traces", options = {}) => {
3064
+ const fetchImpl = options.fetch ?? globalThis.fetch;
3065
+ const response = await fetchImpl(path);
3066
+ if (!response.ok) {
3067
+ throw new Error(`Voice trace timeline failed: HTTP ${response.status}`);
3068
+ }
3069
+ return await response.json();
3070
+ };
3071
+ var createVoiceTraceTimelineStore = (path = "/api/voice-traces", options = {}) => {
3072
+ const listeners = new Set;
3073
+ let closed = false;
3074
+ let timer;
3075
+ let snapshot = {
3076
+ error: null,
3077
+ isLoading: false,
3078
+ report: null
3079
+ };
3080
+ const emit = () => {
3081
+ for (const listener of listeners) {
3082
+ listener();
3083
+ }
3084
+ };
3085
+ const refresh = async () => {
3086
+ if (closed) {
3087
+ return snapshot.report;
3088
+ }
3089
+ snapshot = {
3090
+ ...snapshot,
3091
+ error: null,
3092
+ isLoading: true
3093
+ };
3094
+ emit();
3095
+ try {
3096
+ const report = await fetchVoiceTraceTimeline(path, options);
3097
+ snapshot = {
3098
+ error: null,
3099
+ isLoading: false,
3100
+ report,
3101
+ updatedAt: Date.now()
3102
+ };
3103
+ emit();
3104
+ return report;
3105
+ } catch (error) {
3106
+ snapshot = {
3107
+ ...snapshot,
3108
+ error: error instanceof Error ? error.message : String(error),
3109
+ isLoading: false
3110
+ };
3111
+ emit();
3112
+ throw error;
1031
3113
  }
1032
- },
1033
- "pstn-fast": {
1034
- audioConditioning: {
1035
- enabled: true,
1036
- maxGain: 2.75,
1037
- noiseGateAttenuation: 0.06,
1038
- noiseGateThreshold: 0.005,
1039
- targetLevel: 0.08
1040
- },
1041
- capture: {
1042
- channelCount: 1,
1043
- sampleRateHz: 16000
1044
- },
1045
- connection: {
1046
- maxReconnectAttempts: 14,
1047
- pingInterval: 45000,
1048
- reconnect: true
1049
- },
1050
- sttLifecycle: "continuous",
1051
- turnDetection: {
1052
- qualityProfile: "noisy-room",
1053
- profile: "long-form",
1054
- silenceMs: 620,
1055
- speechThreshold: 0.012,
1056
- transcriptStabilityMs: 280
3114
+ };
3115
+ const close = () => {
3116
+ closed = true;
3117
+ if (timer) {
3118
+ clearInterval(timer);
3119
+ timer = undefined;
1057
3120
  }
1058
- },
1059
- reliability: {
1060
- audioConditioning: {
1061
- enabled: true,
1062
- maxGain: 2.9,
1063
- noiseGateAttenuation: 0.08,
1064
- noiseGateThreshold: 0.005,
1065
- targetLevel: 0.08
1066
- },
1067
- capture: {
1068
- channelCount: 1,
1069
- sampleRateHz: 16000
1070
- },
1071
- connection: {
1072
- maxReconnectAttempts: 14,
1073
- pingInterval: 45000,
1074
- reconnect: true
1075
- },
1076
- sttLifecycle: "continuous",
1077
- turnDetection: {
1078
- qualityProfile: "noisy-room",
1079
- profile: "long-form"
3121
+ listeners.clear();
3122
+ };
3123
+ if (options.intervalMs && options.intervalMs > 0) {
3124
+ timer = setInterval(() => {
3125
+ refresh().catch(() => {});
3126
+ }, options.intervalMs);
3127
+ }
3128
+ return {
3129
+ close,
3130
+ getServerSnapshot: () => snapshot,
3131
+ getSnapshot: () => snapshot,
3132
+ refresh,
3133
+ subscribe: (listener) => {
3134
+ listeners.add(listener);
3135
+ return () => {
3136
+ listeners.delete(listener);
3137
+ };
1080
3138
  }
3139
+ };
3140
+ };
3141
+
3142
+ // src/angular/voice-trace-timeline.service.ts
3143
+ var _dec = [
3144
+ Injectable14({ providedIn: "root" })
3145
+ ];
3146
+ var _init = __decoratorStart(undefined);
3147
+
3148
+ class VoiceTraceTimelineService {
3149
+ connect(path = "/api/voice-traces", options = {}) {
3150
+ const store = createVoiceTraceTimelineStore(path, options);
3151
+ const errorSignal = signal14(null);
3152
+ const isLoadingSignal = signal14(false);
3153
+ const reportSignal = signal14(null);
3154
+ const updatedAtSignal = signal14(undefined);
3155
+ const sync = () => {
3156
+ const snapshot = store.getSnapshot();
3157
+ errorSignal.set(snapshot.error);
3158
+ isLoadingSignal.set(snapshot.isLoading);
3159
+ reportSignal.set(snapshot.report);
3160
+ updatedAtSignal.set(snapshot.updatedAt);
3161
+ };
3162
+ const unsubscribe = store.subscribe(sync);
3163
+ sync();
3164
+ store.refresh().catch(() => {});
3165
+ return {
3166
+ close: () => {
3167
+ unsubscribe();
3168
+ store.close();
3169
+ },
3170
+ error: computed13(() => errorSignal()),
3171
+ isLoading: computed13(() => isLoadingSignal()),
3172
+ refresh: store.refresh,
3173
+ report: computed13(() => reportSignal()),
3174
+ updatedAt: computed13(() => updatedAtSignal())
3175
+ };
1081
3176
  }
3177
+ }
3178
+ VoiceTraceTimelineService = __decorateElement(_init, 0, "VoiceTraceTimelineService", _dec, VoiceTraceTimelineService);
3179
+ __runInitializers(_init, 1, VoiceTraceTimelineService);
3180
+ __decoratorMetadata(_init, VoiceTraceTimelineService);
3181
+ let _VoiceTraceTimelineService = VoiceTraceTimelineService;
3182
+ // src/angular/voice-agent-squad-status.service.ts
3183
+ import { computed as computed14, Injectable as Injectable15, signal as signal15 } from "@angular/core";
3184
+
3185
+ // src/client/agentSquadStatus.ts
3186
+ var getString = (value) => typeof value === "string" && value.trim() ? value.trim() : undefined;
3187
+ var getPayloadString = (event, key) => getString(event.payload?.[key]);
3188
+ var eventStatus = (event) => {
3189
+ const status = getPayloadString(event, "status");
3190
+ if (status === "blocked")
3191
+ return "blocked";
3192
+ if (status === "unknown-target")
3193
+ return "unknown-target";
3194
+ if (status === "allowed")
3195
+ return "handoff";
3196
+ return event.type === "agent.result" ? "active" : "handoff";
1082
3197
  };
1083
- var resolveVoiceRuntimePreset = (name = "default") => {
1084
- const preset = PRESET_INPUTS[name];
3198
+ var deriveSessionSpecialist = (session) => {
3199
+ const events = [...session.events].sort((left, right) => left.at - right.at);
3200
+ const agentEvents = events.filter((event) => event.type === "agent.handoff" || event.type === "agent.context" || event.type === "agent.result" || event.type === "agent.model");
3201
+ const latest = agentEvents.at(-1);
3202
+ if (!latest) {
3203
+ return {
3204
+ lastEventAt: session.lastEventAt,
3205
+ sessionId: session.sessionId,
3206
+ status: "idle"
3207
+ };
3208
+ }
3209
+ const handoffEvents = events.filter((event) => event.type === "agent.handoff");
3210
+ const lastHandoff = handoffEvents.at(-1);
3211
+ const latestAgentId = getPayloadString(latest, "agentId");
3212
+ const handoffStatus = lastHandoff ? eventStatus(lastHandoff) : undefined;
3213
+ const currentTarget = handoffStatus === "blocked" || handoffStatus === "unknown-target" ? getPayloadString(lastHandoff, "fromAgentId") ?? latestAgentId : getPayloadString(lastHandoff ?? latest, "targetAgentId") ?? latestAgentId;
1085
3214
  return {
1086
- audioConditioning: resolveAudioConditioningConfig(preset.audioConditioning),
1087
- capture: {
1088
- channelCount: preset.capture?.channelCount ?? 1,
1089
- sampleRateHz: preset.capture?.sampleRateHz ?? 16000
1090
- },
1091
- connection: {
1092
- ...preset.connection
1093
- },
1094
- name,
1095
- sttLifecycle: preset.sttLifecycle ?? "continuous",
1096
- turnDetection: resolveTurnDetectionConfig(preset.turnDetection)
3215
+ fromAgentId: getPayloadString(lastHandoff ?? latest, "fromAgentId"),
3216
+ lastEventAt: latest.at,
3217
+ reason: getPayloadString(lastHandoff ?? latest, "reason") ?? getPayloadString(latest, "handoffTarget"),
3218
+ sessionId: session.sessionId,
3219
+ status: lastHandoff ? eventStatus(lastHandoff) : "active",
3220
+ summary: getPayloadString(lastHandoff ?? latest, "summary"),
3221
+ targetAgentId: currentTarget,
3222
+ turnId: latest.turnId
1097
3223
  };
1098
3224
  };
1099
-
1100
- // src/client/controller.ts
1101
- var createInitialState2 = (stream) => ({
1102
- assistantAudio: [...stream.assistantAudio],
1103
- assistantTexts: [...stream.assistantTexts],
1104
- call: stream.call,
1105
- error: stream.error,
1106
- isConnected: stream.isConnected,
1107
- isRecording: false,
1108
- partial: stream.partial,
1109
- recordingError: null,
1110
- sessionId: stream.sessionId,
1111
- scenarioId: stream.scenarioId,
1112
- status: stream.status,
1113
- turns: [...stream.turns]
1114
- });
1115
- var createVoiceController = (path, options = {}) => {
1116
- const preset = resolveVoiceRuntimePreset(options.preset);
1117
- const stream = createVoiceStream(path, {
1118
- ...preset.connection,
1119
- ...options.connection
3225
+ var buildVoiceAgentSquadStatusReport = (timeline, options = {}) => {
3226
+ const sessions = (timeline?.sessions ?? []).filter((session) => !options.sessionId || session.sessionId === options.sessionId).map(deriveSessionSpecialist).sort((left, right) => (right.lastEventAt ?? 0) - (left.lastEventAt ?? 0));
3227
+ const active = sessions.filter((session) => session.status !== "idle");
3228
+ return {
3229
+ active,
3230
+ checkedAt: timeline?.checkedAt,
3231
+ current: active[0] ?? sessions[0],
3232
+ sessionCount: sessions.length,
3233
+ sessions
3234
+ };
3235
+ };
3236
+ var createVoiceAgentSquadStatusStore = (path = "/api/voice-traces", options = {}) => {
3237
+ const timelineStore = createVoiceTraceTimelineStore(path, options);
3238
+ const getReport = () => buildVoiceAgentSquadStatusReport(timelineStore.getSnapshot().report, {
3239
+ sessionId: options.sessionId
1120
3240
  });
1121
- let capture = null;
1122
- let state = createInitialState2(stream);
1123
- const subscribers = new Set;
1124
- const notify = () => {
1125
- for (const subscriber of subscribers) {
1126
- subscriber();
1127
- }
3241
+ const getSnapshot = () => {
3242
+ const snapshot = timelineStore.getSnapshot();
3243
+ return {
3244
+ error: snapshot.error,
3245
+ isLoading: snapshot.isLoading,
3246
+ report: getReport(),
3247
+ updatedAt: snapshot.updatedAt
3248
+ };
1128
3249
  };
1129
- const sync = () => {
1130
- state = {
1131
- ...state,
1132
- assistantAudio: [...stream.assistantAudio],
1133
- assistantTexts: [...stream.assistantTexts],
1134
- call: stream.call,
1135
- error: stream.error,
1136
- isConnected: stream.isConnected,
1137
- partial: stream.partial,
1138
- sessionId: stream.sessionId,
1139
- scenarioId: stream.scenarioId,
1140
- status: stream.status,
1141
- turns: [...stream.turns]
3250
+ return {
3251
+ close: timelineStore.close,
3252
+ getServerSnapshot: getSnapshot,
3253
+ getSnapshot,
3254
+ refresh: timelineStore.refresh,
3255
+ subscribe: timelineStore.subscribe
3256
+ };
3257
+ };
3258
+
3259
+ // src/angular/voice-agent-squad-status.service.ts
3260
+ var _dec = [
3261
+ Injectable15({ providedIn: "root" })
3262
+ ];
3263
+ var _init = __decoratorStart(undefined);
3264
+
3265
+ class VoiceAgentSquadStatusService {
3266
+ connect(path = "/api/voice-traces", options = {}) {
3267
+ const store = createVoiceAgentSquadStatusStore(path, options);
3268
+ const errorSignal = signal15(null);
3269
+ const isLoadingSignal = signal15(false);
3270
+ const reportSignal = signal15(undefined);
3271
+ const updatedAtSignal = signal15(undefined);
3272
+ const sync = () => {
3273
+ const snapshot = store.getSnapshot();
3274
+ errorSignal.set(snapshot.error);
3275
+ isLoadingSignal.set(snapshot.isLoading);
3276
+ reportSignal.set(snapshot.report);
3277
+ updatedAtSignal.set(snapshot.updatedAt);
1142
3278
  };
1143
- if (options.autoStopOnComplete !== false && state.status === "completed" && state.isRecording) {
1144
- capture?.stop();
1145
- capture = null;
1146
- state = {
1147
- ...state,
1148
- isRecording: false
3279
+ const unsubscribe = store.subscribe(sync);
3280
+ sync();
3281
+ store.refresh().catch(() => {});
3282
+ return {
3283
+ close: () => {
3284
+ unsubscribe();
3285
+ store.close();
3286
+ },
3287
+ current: computed14(() => reportSignal()?.current),
3288
+ error: computed14(() => errorSignal()),
3289
+ isLoading: computed14(() => isLoadingSignal()),
3290
+ refresh: store.refresh,
3291
+ report: computed14(() => reportSignal()),
3292
+ updatedAt: computed14(() => updatedAtSignal())
3293
+ };
3294
+ }
3295
+ }
3296
+ VoiceAgentSquadStatusService = __decorateElement(_init, 0, "VoiceAgentSquadStatusService", _dec, VoiceAgentSquadStatusService);
3297
+ __runInitializers(_init, 1, VoiceAgentSquadStatusService);
3298
+ __decoratorMetadata(_init, VoiceAgentSquadStatusService);
3299
+ let _VoiceAgentSquadStatusService = VoiceAgentSquadStatusService;
3300
+ // src/angular/voice-turn-latency.service.ts
3301
+ import { computed as computed15, Injectable as Injectable16, signal as signal16 } from "@angular/core";
3302
+
3303
+ // src/client/turnLatency.ts
3304
+ var fetchVoiceTurnLatency = async (path = "/api/turn-latency", options = {}) => {
3305
+ const fetchImpl = options.fetch ?? globalThis.fetch;
3306
+ const response = await fetchImpl(path);
3307
+ if (!response.ok) {
3308
+ throw new Error(`Voice turn latency failed: HTTP ${response.status}`);
3309
+ }
3310
+ return await response.json();
3311
+ };
3312
+ var runVoiceTurnLatencyProof = async (path, options = {}) => {
3313
+ const fetchImpl = options.fetch ?? globalThis.fetch;
3314
+ const response = await fetchImpl(path, { method: "POST" });
3315
+ if (!response.ok) {
3316
+ throw new Error(`Voice turn latency proof failed: HTTP ${response.status}`);
3317
+ }
3318
+ return response.json();
3319
+ };
3320
+ var createVoiceTurnLatencyStore = (path = "/api/turn-latency", options = {}) => {
3321
+ const listeners = new Set;
3322
+ let closed = false;
3323
+ let timer;
3324
+ let snapshot = {
3325
+ error: null,
3326
+ isLoading: false
3327
+ };
3328
+ const emit = () => {
3329
+ for (const listener of listeners) {
3330
+ listener();
3331
+ }
3332
+ };
3333
+ const refresh = async () => {
3334
+ if (closed) {
3335
+ return snapshot.report;
3336
+ }
3337
+ snapshot = { ...snapshot, error: null, isLoading: true };
3338
+ emit();
3339
+ try {
3340
+ const report = await fetchVoiceTurnLatency(path, options);
3341
+ snapshot = {
3342
+ error: null,
3343
+ isLoading: false,
3344
+ report,
3345
+ updatedAt: Date.now()
3346
+ };
3347
+ emit();
3348
+ return report;
3349
+ } catch (error) {
3350
+ snapshot = {
3351
+ ...snapshot,
3352
+ error: error instanceof Error ? error.message : String(error),
3353
+ isLoading: false
3354
+ };
3355
+ emit();
3356
+ throw error;
3357
+ }
3358
+ };
3359
+ const runProof = async () => {
3360
+ if (!options.proofPath) {
3361
+ throw new Error("Voice turn latency proof path is not configured.");
3362
+ }
3363
+ snapshot = { ...snapshot, error: null, isLoading: true };
3364
+ emit();
3365
+ try {
3366
+ await runVoiceTurnLatencyProof(options.proofPath, options);
3367
+ return await refresh();
3368
+ } catch (error) {
3369
+ snapshot = {
3370
+ ...snapshot,
3371
+ error: error instanceof Error ? error.message : String(error),
3372
+ isLoading: false
1149
3373
  };
3374
+ emit();
3375
+ throw error;
3376
+ }
3377
+ };
3378
+ const close = () => {
3379
+ closed = true;
3380
+ if (timer) {
3381
+ clearInterval(timer);
3382
+ timer = undefined;
1150
3383
  }
1151
- notify();
3384
+ listeners.clear();
1152
3385
  };
1153
- const unsubscribeStream = stream.subscribe(sync);
1154
- sync();
1155
- const ensureCapture = () => {
1156
- if (capture) {
1157
- return capture;
3386
+ if (options.intervalMs && options.intervalMs > 0) {
3387
+ timer = setInterval(() => {
3388
+ refresh().catch(() => {});
3389
+ }, options.intervalMs);
3390
+ }
3391
+ return {
3392
+ close,
3393
+ getServerSnapshot: () => snapshot,
3394
+ getSnapshot: () => snapshot,
3395
+ refresh,
3396
+ runProof,
3397
+ subscribe: (listener) => {
3398
+ listeners.add(listener);
3399
+ return () => {
3400
+ listeners.delete(listener);
3401
+ };
1158
3402
  }
1159
- capture = createMicrophoneCapture({
1160
- channelCount: options.capture?.channelCount ?? preset.capture.channelCount,
1161
- onLevel: options.capture?.onLevel,
1162
- onAudio: (audio) => stream.sendAudio(audio),
1163
- sampleRateHz: options.capture?.sampleRateHz ?? preset.capture.sampleRateHz
1164
- });
1165
- return capture;
1166
3403
  };
1167
- const stopRecording = () => {
1168
- capture?.stop();
1169
- capture = null;
1170
- state = {
1171
- ...state,
1172
- isRecording: false
3404
+ };
3405
+
3406
+ // src/angular/voice-turn-latency.service.ts
3407
+ var _dec = [
3408
+ Injectable16({ providedIn: "root" })
3409
+ ];
3410
+ var _init = __decoratorStart(undefined);
3411
+
3412
+ class VoiceTurnLatencyService {
3413
+ connect(path = "/api/turn-latency", options = {}) {
3414
+ const store = createVoiceTurnLatencyStore(path, options);
3415
+ const errorSignal = signal16(null);
3416
+ const isLoadingSignal = signal16(false);
3417
+ const reportSignal = signal16(undefined);
3418
+ const updatedAtSignal = signal16(undefined);
3419
+ const sync = () => {
3420
+ const snapshot = store.getSnapshot();
3421
+ errorSignal.set(snapshot.error);
3422
+ isLoadingSignal.set(snapshot.isLoading);
3423
+ reportSignal.set(snapshot.report);
3424
+ updatedAtSignal.set(snapshot.updatedAt);
1173
3425
  };
1174
- notify();
3426
+ const unsubscribe = store.subscribe(sync);
3427
+ sync();
3428
+ store.refresh().catch(() => {});
3429
+ return {
3430
+ close: () => {
3431
+ unsubscribe();
3432
+ store.close();
3433
+ },
3434
+ error: computed15(() => errorSignal()),
3435
+ isLoading: computed15(() => isLoadingSignal()),
3436
+ refresh: store.refresh,
3437
+ report: computed15(() => reportSignal()),
3438
+ runProof: store.runProof,
3439
+ updatedAt: computed15(() => updatedAtSignal())
3440
+ };
3441
+ }
3442
+ }
3443
+ VoiceTurnLatencyService = __decorateElement(_init, 0, "VoiceTurnLatencyService", _dec, VoiceTurnLatencyService);
3444
+ __runInitializers(_init, 1, VoiceTurnLatencyService);
3445
+ __decoratorMetadata(_init, VoiceTurnLatencyService);
3446
+ let _VoiceTurnLatencyService = VoiceTurnLatencyService;
3447
+ // src/angular/voice-turn-quality.service.ts
3448
+ import { computed as computed16, Injectable as Injectable17, signal as signal17 } from "@angular/core";
3449
+
3450
+ // src/client/turnQuality.ts
3451
+ var fetchVoiceTurnQuality = async (path = "/api/turn-quality", options = {}) => {
3452
+ const fetchImpl = options.fetch ?? globalThis.fetch;
3453
+ const response = await fetchImpl(path);
3454
+ if (!response.ok) {
3455
+ throw new Error(`Voice turn quality failed: HTTP ${response.status}`);
3456
+ }
3457
+ return await response.json();
3458
+ };
3459
+ var createVoiceTurnQualityStore = (path = "/api/turn-quality", options = {}) => {
3460
+ const listeners = new Set;
3461
+ let closed = false;
3462
+ let timer;
3463
+ let snapshot = {
3464
+ error: null,
3465
+ isLoading: false
1175
3466
  };
1176
- const startRecording = async () => {
1177
- if (state.isRecording) {
1178
- return;
3467
+ const emit = () => {
3468
+ for (const listener of listeners) {
3469
+ listener();
3470
+ }
3471
+ };
3472
+ const refresh = async () => {
3473
+ if (closed) {
3474
+ return snapshot.report;
1179
3475
  }
3476
+ snapshot = {
3477
+ ...snapshot,
3478
+ error: null,
3479
+ isLoading: true
3480
+ };
3481
+ emit();
1180
3482
  try {
1181
- state = {
1182
- ...state,
1183
- recordingError: null
1184
- };
1185
- notify();
1186
- await ensureCapture().start();
1187
- state = {
1188
- ...state,
1189
- isRecording: true
3483
+ const report = await fetchVoiceTurnQuality(path, options);
3484
+ snapshot = {
3485
+ error: null,
3486
+ isLoading: false,
3487
+ report,
3488
+ updatedAt: Date.now()
1190
3489
  };
1191
- notify();
3490
+ emit();
3491
+ return report;
1192
3492
  } catch (error) {
1193
- capture = null;
1194
- state = {
1195
- ...state,
1196
- isRecording: false,
1197
- recordingError: error instanceof Error ? error.message : String(error)
3493
+ snapshot = {
3494
+ ...snapshot,
3495
+ error: error instanceof Error ? error.message : String(error),
3496
+ isLoading: false
1198
3497
  };
1199
- notify();
3498
+ emit();
1200
3499
  throw error;
1201
3500
  }
1202
3501
  };
1203
3502
  const close = () => {
1204
- unsubscribeStream();
1205
- stopRecording();
1206
- stream.close();
3503
+ closed = true;
3504
+ if (timer) {
3505
+ clearInterval(timer);
3506
+ timer = undefined;
3507
+ }
3508
+ listeners.clear();
1207
3509
  };
3510
+ if (options.intervalMs && options.intervalMs > 0) {
3511
+ timer = setInterval(() => {
3512
+ refresh().catch(() => {});
3513
+ }, options.intervalMs);
3514
+ }
1208
3515
  return {
1209
- bindHTMX(bindingOptions) {
1210
- return bindVoiceHTMX(stream, bindingOptions);
1211
- },
1212
- callControl: (message) => stream.callControl(message),
1213
3516
  close,
1214
- endTurn: () => stream.endTurn(),
1215
- get error() {
1216
- return state.error;
1217
- },
1218
- getServerSnapshot: () => state,
1219
- getSnapshot: () => state,
1220
- get isConnected() {
1221
- return state.isConnected;
1222
- },
1223
- get isRecording() {
1224
- return state.isRecording;
1225
- },
1226
- get partial() {
1227
- return state.partial;
1228
- },
1229
- get recordingError() {
1230
- return state.recordingError;
1231
- },
1232
- sendAudio: (audio) => stream.sendAudio(audio),
1233
- get sessionId() {
1234
- return state.sessionId;
1235
- },
1236
- get scenarioId() {
1237
- return state.scenarioId;
1238
- },
1239
- startRecording,
1240
- get status() {
1241
- return state.status;
1242
- },
1243
- stopRecording,
1244
- subscribe: (subscriber) => {
1245
- subscribers.add(subscriber);
3517
+ getServerSnapshot: () => snapshot,
3518
+ getSnapshot: () => snapshot,
3519
+ refresh,
3520
+ subscribe: (listener) => {
3521
+ listeners.add(listener);
1246
3522
  return () => {
1247
- subscribers.delete(subscriber);
3523
+ listeners.delete(listener);
1248
3524
  };
1249
- },
1250
- toggleRecording: async () => {
1251
- if (state.isRecording) {
1252
- stopRecording();
1253
- return;
1254
- }
1255
- await startRecording();
1256
- },
1257
- get turns() {
1258
- return state.turns;
1259
- },
1260
- get assistantTexts() {
1261
- return state.assistantTexts;
1262
- },
1263
- get assistantAudio() {
1264
- return state.assistantAudio;
1265
- },
1266
- get call() {
1267
- return state.call;
1268
3525
  }
1269
3526
  };
1270
3527
  };
1271
3528
 
1272
- // src/angular/voice-controller.service.ts
3529
+ // src/angular/voice-turn-quality.service.ts
1273
3530
  var _dec = [
1274
- Injectable2({ providedIn: "root" })
3531
+ Injectable17({ providedIn: "root" })
1275
3532
  ];
1276
3533
  var _init = __decoratorStart(undefined);
1277
3534
 
1278
- class VoiceControllerService {
1279
- connect(path, options = {}) {
1280
- const controller = createVoiceController(path, options);
1281
- const assistantAudioSignal = signal2([]);
1282
- const assistantTextsSignal = signal2([]);
1283
- const errorSignal = signal2(null);
1284
- const isConnectedSignal = signal2(false);
1285
- const isRecordingSignal = signal2(false);
1286
- const partialSignal = signal2("");
1287
- const recordingErrorSignal = signal2(null);
1288
- const sessionIdSignal = signal2(controller.sessionId);
1289
- const statusSignal = signal2(controller.status);
1290
- const turnsSignal = signal2([]);
3535
+ class VoiceTurnQualityService {
3536
+ connect(path = "/api/turn-quality", options = {}) {
3537
+ const store = createVoiceTurnQualityStore(path, options);
3538
+ const errorSignal = signal17(null);
3539
+ const isLoadingSignal = signal17(false);
3540
+ const reportSignal = signal17(undefined);
3541
+ const updatedAtSignal = signal17(undefined);
1291
3542
  const sync = () => {
1292
- assistantAudioSignal.set([...controller.assistantAudio]);
1293
- assistantTextsSignal.set([...controller.assistantTexts]);
1294
- errorSignal.set(controller.error);
1295
- isConnectedSignal.set(controller.isConnected);
1296
- isRecordingSignal.set(controller.isRecording);
1297
- partialSignal.set(controller.partial);
1298
- recordingErrorSignal.set(controller.recordingError);
1299
- sessionIdSignal.set(controller.sessionId);
1300
- statusSignal.set(controller.status);
1301
- turnsSignal.set([...controller.turns]);
3543
+ const snapshot = store.getSnapshot();
3544
+ errorSignal.set(snapshot.error);
3545
+ isLoadingSignal.set(snapshot.isLoading);
3546
+ reportSignal.set(snapshot.report);
3547
+ updatedAtSignal.set(snapshot.updatedAt);
1302
3548
  };
1303
- const unsubscribe = controller.subscribe(sync);
3549
+ const unsubscribe = store.subscribe(sync);
1304
3550
  sync();
3551
+ store.refresh().catch(() => {});
1305
3552
  return {
1306
- assistantAudio: computed2(() => assistantAudioSignal()),
1307
- assistantTexts: computed2(() => assistantTextsSignal()),
1308
- bindHTMX: controller.bindHTMX,
1309
3553
  close: () => {
1310
3554
  unsubscribe();
1311
- controller.close();
3555
+ store.close();
1312
3556
  },
1313
- endTurn: () => controller.endTurn(),
1314
- error: computed2(() => errorSignal()),
1315
- isConnected: computed2(() => isConnectedSignal()),
1316
- isRecording: computed2(() => isRecordingSignal()),
1317
- partial: computed2(() => partialSignal()),
1318
- recordingError: computed2(() => recordingErrorSignal()),
1319
- sendAudio: (audio) => controller.sendAudio(audio),
1320
- sessionId: computed2(() => sessionIdSignal()),
1321
- startRecording: () => controller.startRecording(),
1322
- status: computed2(() => statusSignal()),
1323
- stopRecording: () => controller.stopRecording(),
1324
- toggleRecording: () => controller.toggleRecording(),
1325
- turns: computed2(() => turnsSignal())
3557
+ error: computed16(() => errorSignal()),
3558
+ isLoading: computed16(() => isLoadingSignal()),
3559
+ refresh: store.refresh,
3560
+ report: computed16(() => reportSignal()),
3561
+ updatedAt: computed16(() => updatedAtSignal())
1326
3562
  };
1327
3563
  }
1328
3564
  }
1329
- VoiceControllerService = __decorateElement(_init, 0, "VoiceControllerService", _dec, VoiceControllerService);
1330
- __runInitializers(_init, 1, VoiceControllerService);
1331
- __decoratorMetadata(_init, VoiceControllerService);
1332
- let _VoiceControllerService = VoiceControllerService;
1333
- // src/angular/voice-provider-status.service.ts
1334
- import { computed as computed3, Injectable as Injectable3, signal as signal3 } from "@angular/core";
3565
+ VoiceTurnQualityService = __decorateElement(_init, 0, "VoiceTurnQualityService", _dec, VoiceTurnQualityService);
3566
+ __runInitializers(_init, 1, VoiceTurnQualityService);
3567
+ __decoratorMetadata(_init, VoiceTurnQualityService);
3568
+ let _VoiceTurnQualityService = VoiceTurnQualityService;
3569
+ // src/angular/voice-workflow-status.service.ts
3570
+ import { computed as computed17, Injectable as Injectable18, signal as signal18 } from "@angular/core";
1335
3571
 
1336
- // src/client/providerStatus.ts
1337
- var fetchVoiceProviderStatus = async (path = "/api/provider-status", options = {}) => {
3572
+ // src/client/workflowStatus.ts
3573
+ var fetchVoiceWorkflowStatus = async (path = "/evals/scenarios/json", options = {}) => {
1338
3574
  const fetchImpl = options.fetch ?? globalThis.fetch;
1339
3575
  const response = await fetchImpl(path);
1340
3576
  if (!response.ok) {
1341
- throw new Error(`Voice provider status failed: HTTP ${response.status}`);
3577
+ throw new Error(`Voice workflow status failed: HTTP ${response.status}`);
1342
3578
  }
1343
3579
  return await response.json();
1344
3580
  };
1345
- var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {}) => {
3581
+ var createVoiceWorkflowStatusStore = (path = "/evals/scenarios/json", options = {}) => {
1346
3582
  const listeners = new Set;
1347
3583
  let closed = false;
1348
3584
  let timer;
1349
3585
  let snapshot = {
1350
3586
  error: null,
1351
- isLoading: false,
1352
- providers: []
3587
+ isLoading: false
1353
3588
  };
1354
3589
  const emit = () => {
1355
3590
  for (const listener of listeners) {
@@ -1358,7 +3593,7 @@ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {
1358
3593
  };
1359
3594
  const refresh = async () => {
1360
3595
  if (closed) {
1361
- return snapshot.providers;
3596
+ return snapshot.report;
1362
3597
  }
1363
3598
  snapshot = {
1364
3599
  ...snapshot,
@@ -1367,15 +3602,15 @@ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {
1367
3602
  };
1368
3603
  emit();
1369
3604
  try {
1370
- const providers = await fetchVoiceProviderStatus(path, options);
3605
+ const report = await fetchVoiceWorkflowStatus(path, options);
1371
3606
  snapshot = {
1372
3607
  error: null,
1373
3608
  isLoading: false,
1374
- providers,
3609
+ report,
1375
3610
  updatedAt: Date.now()
1376
3611
  };
1377
3612
  emit();
1378
- return providers;
3613
+ return report;
1379
3614
  } catch (error) {
1380
3615
  snapshot = {
1381
3616
  ...snapshot,
@@ -1394,7 +3629,7 @@ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {
1394
3629
  }
1395
3630
  listeners.clear();
1396
3631
  };
1397
- if (options.intervalMs && options.intervalMs > 0) {
3632
+ if (typeof window !== "undefined" && options.intervalMs && options.intervalMs > 0) {
1398
3633
  timer = setInterval(() => {
1399
3634
  refresh().catch(() => {});
1400
3635
  }, options.intervalMs);
@@ -1413,48 +3648,65 @@ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {
1413
3648
  };
1414
3649
  };
1415
3650
 
1416
- // src/angular/voice-provider-status.service.ts
3651
+ // src/angular/voice-workflow-status.service.ts
1417
3652
  var _dec = [
1418
- Injectable3({ providedIn: "root" })
3653
+ Injectable18({ providedIn: "root" })
1419
3654
  ];
1420
3655
  var _init = __decoratorStart(undefined);
1421
3656
 
1422
- class VoiceProviderStatusService {
1423
- connect(path = "/api/provider-status", options = {}) {
1424
- const store = createVoiceProviderStatusStore(path, options);
1425
- const errorSignal = signal3(null);
1426
- const isLoadingSignal = signal3(false);
1427
- const providersSignal = signal3([]);
1428
- const updatedAtSignal = signal3(undefined);
3657
+ class VoiceWorkflowStatusService {
3658
+ connect(path = "/evals/scenarios/json", options = {}) {
3659
+ const store = createVoiceWorkflowStatusStore(path, options);
3660
+ const errorSignal = signal18(null);
3661
+ const isLoadingSignal = signal18(false);
3662
+ const reportSignal = signal18(undefined);
3663
+ const updatedAtSignal = signal18(undefined);
1429
3664
  const sync = () => {
1430
3665
  const snapshot = store.getSnapshot();
1431
3666
  errorSignal.set(snapshot.error);
1432
3667
  isLoadingSignal.set(snapshot.isLoading);
1433
- providersSignal.set([...snapshot.providers]);
3668
+ reportSignal.set(snapshot.report);
1434
3669
  updatedAtSignal.set(snapshot.updatedAt);
1435
3670
  };
1436
3671
  const unsubscribe = store.subscribe(sync);
1437
3672
  sync();
1438
- store.refresh().catch(() => {});
3673
+ if (typeof window !== "undefined") {
3674
+ store.refresh().catch(() => {});
3675
+ }
1439
3676
  return {
1440
3677
  close: () => {
1441
3678
  unsubscribe();
1442
3679
  store.close();
1443
3680
  },
1444
- error: computed3(() => errorSignal()),
1445
- isLoading: computed3(() => isLoadingSignal()),
1446
- providers: computed3(() => providersSignal()),
3681
+ error: computed17(() => errorSignal()),
3682
+ isLoading: computed17(() => isLoadingSignal()),
1447
3683
  refresh: store.refresh,
1448
- updatedAt: computed3(() => updatedAtSignal())
3684
+ report: computed17(() => reportSignal()),
3685
+ updatedAt: computed17(() => updatedAtSignal())
1449
3686
  };
1450
3687
  }
1451
3688
  }
1452
- VoiceProviderStatusService = __decorateElement(_init, 0, "VoiceProviderStatusService", _dec, VoiceProviderStatusService);
1453
- __runInitializers(_init, 1, VoiceProviderStatusService);
1454
- __decoratorMetadata(_init, VoiceProviderStatusService);
1455
- let _VoiceProviderStatusService = VoiceProviderStatusService;
3689
+ VoiceWorkflowStatusService = __decorateElement(_init, 0, "VoiceWorkflowStatusService", _dec, VoiceWorkflowStatusService);
3690
+ __runInitializers(_init, 1, VoiceWorkflowStatusService);
3691
+ __decoratorMetadata(_init, VoiceWorkflowStatusService);
3692
+ let _VoiceWorkflowStatusService = VoiceWorkflowStatusService;
1456
3693
  export {
3694
+ VoiceWorkflowStatusService,
3695
+ VoiceTurnQualityService,
3696
+ VoiceTurnLatencyService,
3697
+ VoiceTraceTimelineService,
1457
3698
  VoiceStreamService,
3699
+ VoiceRoutingStatusService,
1458
3700
  VoiceProviderStatusService,
1459
- VoiceControllerService
3701
+ VoiceProviderContractsService,
3702
+ VoiceProviderCapabilitiesService,
3703
+ VoiceProofTrendsService,
3704
+ VoicePlatformCoverageService,
3705
+ VoiceOpsStatusService,
3706
+ VoiceOpsActionCenterService,
3707
+ VoiceLiveOpsService,
3708
+ VoiceDeliveryRuntimeService,
3709
+ VoiceControllerService,
3710
+ VoiceCampaignDialerProofService,
3711
+ VoiceAgentSquadStatusService
1460
3712
  };