@absolutejs/voice 0.0.22-beta.16 → 0.0.22-beta.160

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