@absolutejs/voice 0.0.22-beta.2 → 0.0.22-beta.200

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