@absolutejs/voice 0.0.22-beta.17 → 0.0.22-beta.170

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 (189) hide show
  1. package/README.md +1057 -5
  2. package/dist/agent.d.ts +24 -0
  3. package/dist/agentSquadContract.d.ts +64 -0
  4. package/dist/angular/index.d.ts +12 -0
  5. package/dist/angular/index.js +3196 -1111
  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-provider-contracts.service.d.ts +12 -0
  15. package/dist/angular/voice-routing-status.service.d.ts +11 -0
  16. package/dist/angular/voice-stream.service.d.ts +3 -0
  17. package/dist/angular/voice-trace-timeline.service.d.ts +12 -0
  18. package/dist/angular/voice-turn-latency.service.d.ts +13 -0
  19. package/dist/angular/voice-turn-quality.service.d.ts +12 -0
  20. package/dist/angular/voice-workflow-status.service.d.ts +12 -0
  21. package/dist/assistantHealth.d.ts +81 -0
  22. package/dist/audit.d.ts +128 -0
  23. package/dist/auditDeliveryRoutes.d.ts +85 -0
  24. package/dist/auditExport.d.ts +34 -0
  25. package/dist/auditRoutes.d.ts +66 -0
  26. package/dist/auditSinks.d.ts +151 -0
  27. package/dist/bargeInRoutes.d.ts +56 -0
  28. package/dist/campaign.d.ts +610 -0
  29. package/dist/campaignDialers.d.ts +90 -0
  30. package/dist/client/actions.d.ts +105 -0
  31. package/dist/client/bargeInMonitor.d.ts +7 -0
  32. package/dist/client/campaignDialerProof.d.ts +23 -0
  33. package/dist/client/connection.d.ts +3 -0
  34. package/dist/client/deliveryRuntime.d.ts +34 -0
  35. package/dist/client/deliveryRuntimeWidget.d.ts +37 -0
  36. package/dist/client/duplex.d.ts +1 -1
  37. package/dist/client/htmxBootstrap.js +747 -15
  38. package/dist/client/index.d.ts +54 -0
  39. package/dist/client/index.js +2944 -20
  40. package/dist/client/liveTurnLatency.d.ts +41 -0
  41. package/dist/client/opsActionCenter.d.ts +54 -0
  42. package/dist/client/opsActionCenterWidget.d.ts +29 -0
  43. package/dist/client/opsActionHistory.d.ts +19 -0
  44. package/dist/client/opsActionHistoryWidget.d.ts +11 -0
  45. package/dist/client/opsStatus.d.ts +19 -0
  46. package/dist/client/opsStatusWidget.d.ts +40 -0
  47. package/dist/client/providerCapabilities.d.ts +19 -0
  48. package/dist/client/providerCapabilitiesWidget.d.ts +32 -0
  49. package/dist/client/providerContracts.d.ts +19 -0
  50. package/dist/client/providerContractsWidget.d.ts +37 -0
  51. package/dist/client/providerSimulationControls.d.ts +33 -0
  52. package/dist/client/providerSimulationControlsWidget.d.ts +20 -0
  53. package/dist/client/providerStatusWidget.d.ts +32 -0
  54. package/dist/client/routingStatus.d.ts +19 -0
  55. package/dist/client/routingStatusWidget.d.ts +28 -0
  56. package/dist/client/traceTimeline.d.ts +19 -0
  57. package/dist/client/traceTimelineWidget.d.ts +32 -0
  58. package/dist/client/turnLatency.d.ts +22 -0
  59. package/dist/client/turnLatencyWidget.d.ts +33 -0
  60. package/dist/client/turnQuality.d.ts +19 -0
  61. package/dist/client/turnQualityWidget.d.ts +32 -0
  62. package/dist/client/workflowStatus.d.ts +19 -0
  63. package/dist/dataControl.d.ts +47 -0
  64. package/dist/deliveryRuntime.d.ts +158 -0
  65. package/dist/deliverySinkRoutes.d.ts +117 -0
  66. package/dist/demoReadyRoutes.d.ts +98 -0
  67. package/dist/diagnosticsRoutes.d.ts +44 -0
  68. package/dist/evalRoutes.d.ts +213 -0
  69. package/dist/fileStore.d.ts +11 -2
  70. package/dist/handoff.d.ts +54 -0
  71. package/dist/handoffHealth.d.ts +94 -0
  72. package/dist/index.d.ts +114 -11
  73. package/dist/index.js +18454 -4316
  74. package/dist/liveLatency.d.ts +78 -0
  75. package/dist/modelAdapters.d.ts +23 -2
  76. package/dist/openaiRealtime.d.ts +27 -0
  77. package/dist/openaiTTS.d.ts +18 -0
  78. package/dist/opsActionAuditRoutes.d.ts +99 -0
  79. package/dist/opsConsoleRoutes.d.ts +80 -0
  80. package/dist/opsStatus.d.ts +76 -0
  81. package/dist/opsStatusRoutes.d.ts +33 -0
  82. package/dist/opsWebhook.d.ts +126 -0
  83. package/dist/outcomeContract.d.ts +112 -0
  84. package/dist/phoneAgent.d.ts +62 -0
  85. package/dist/phoneAgentProductionSmoke.d.ts +115 -0
  86. package/dist/postgresStore.d.ts +13 -2
  87. package/dist/productionReadiness.d.ts +368 -0
  88. package/dist/providerAdapters.d.ts +48 -0
  89. package/dist/providerCapabilities.d.ts +92 -0
  90. package/dist/providerHealth.d.ts +1 -0
  91. package/dist/providerRoutingContract.d.ts +38 -0
  92. package/dist/providerStackRecommendations.d.ts +145 -0
  93. package/dist/qualityRoutes.d.ts +76 -0
  94. package/dist/queue.d.ts +61 -0
  95. package/dist/react/VoiceDeliveryRuntime.d.ts +7 -0
  96. package/dist/react/VoiceOpsActionCenter.d.ts +5 -0
  97. package/dist/react/VoiceOpsStatus.d.ts +6 -0
  98. package/dist/react/VoiceProviderCapabilities.d.ts +6 -0
  99. package/dist/react/VoiceProviderContracts.d.ts +6 -0
  100. package/dist/react/VoiceProviderSimulationControls.d.ts +5 -0
  101. package/dist/react/VoiceProviderStatus.d.ts +6 -0
  102. package/dist/react/VoiceRoutingStatus.d.ts +6 -0
  103. package/dist/react/VoiceTraceTimeline.d.ts +6 -0
  104. package/dist/react/VoiceTurnLatency.d.ts +6 -0
  105. package/dist/react/VoiceTurnQuality.d.ts +6 -0
  106. package/dist/react/index.d.ts +23 -0
  107. package/dist/react/index.js +3695 -33
  108. package/dist/react/useVoiceCampaignDialerProof.d.ts +10 -0
  109. package/dist/react/useVoiceController.d.ts +3 -0
  110. package/dist/react/useVoiceDeliveryRuntime.d.ts +13 -0
  111. package/dist/react/useVoiceOpsActionCenter.d.ts +11 -0
  112. package/dist/react/useVoiceOpsStatus.d.ts +8 -0
  113. package/dist/react/useVoiceProviderCapabilities.d.ts +8 -0
  114. package/dist/react/useVoiceProviderContracts.d.ts +8 -0
  115. package/dist/react/useVoiceProviderSimulationControls.d.ts +10 -0
  116. package/dist/react/useVoiceRoutingStatus.d.ts +8 -0
  117. package/dist/react/useVoiceStream.d.ts +3 -0
  118. package/dist/react/useVoiceTraceTimeline.d.ts +8 -0
  119. package/dist/react/useVoiceTurnLatency.d.ts +9 -0
  120. package/dist/react/useVoiceTurnQuality.d.ts +8 -0
  121. package/dist/react/useVoiceWorkflowStatus.d.ts +8 -0
  122. package/dist/readinessProfiles.d.ts +35 -0
  123. package/dist/reconnectContract.d.ts +87 -0
  124. package/dist/resilienceRoutes.d.ts +142 -0
  125. package/dist/sessionReplay.d.ts +185 -0
  126. package/dist/simulationSuite.d.ts +120 -0
  127. package/dist/sqliteStore.d.ts +13 -2
  128. package/dist/svelte/createVoiceCampaignDialerProof.d.ts +9 -0
  129. package/dist/svelte/createVoiceDeliveryRuntime.d.ts +11 -0
  130. package/dist/svelte/createVoiceOpsActionCenter.d.ts +10 -0
  131. package/dist/svelte/createVoiceOpsStatus.d.ts +9 -0
  132. package/dist/svelte/createVoiceProviderCapabilities.d.ts +10 -0
  133. package/dist/svelte/createVoiceProviderContracts.d.ts +10 -0
  134. package/dist/svelte/createVoiceProviderSimulationControls.d.ts +11 -0
  135. package/dist/svelte/createVoiceProviderStatus.d.ts +4 -2
  136. package/dist/svelte/createVoiceRoutingStatus.d.ts +10 -0
  137. package/dist/svelte/createVoiceTraceTimeline.d.ts +10 -0
  138. package/dist/svelte/createVoiceTurnLatency.d.ts +11 -0
  139. package/dist/svelte/createVoiceTurnQuality.d.ts +10 -0
  140. package/dist/svelte/createVoiceWorkflowStatus.d.ts +8 -0
  141. package/dist/svelte/index.d.ts +12 -0
  142. package/dist/svelte/index.js +3095 -439
  143. package/dist/telephony/contract.d.ts +61 -0
  144. package/dist/telephony/matrix.d.ts +97 -0
  145. package/dist/telephony/plivo.d.ts +254 -0
  146. package/dist/telephony/telnyx.d.ts +247 -0
  147. package/dist/telephony/twilio.d.ts +135 -2
  148. package/dist/telephonyOutcome.d.ts +201 -0
  149. package/dist/testing/index.d.ts +1 -0
  150. package/dist/testing/index.js +2008 -69
  151. package/dist/testing/ioProviderSimulator.d.ts +41 -0
  152. package/dist/testing/providerSimulator.d.ts +8 -0
  153. package/dist/toolContract.d.ts +130 -0
  154. package/dist/toolRuntime.d.ts +50 -0
  155. package/dist/trace.d.ts +19 -1
  156. package/dist/traceDeliveryRoutes.d.ts +86 -0
  157. package/dist/traceTimeline.d.ts +93 -0
  158. package/dist/turnLatency.d.ts +95 -0
  159. package/dist/turnQuality.d.ts +94 -0
  160. package/dist/types.d.ts +170 -4
  161. package/dist/vue/VoiceDeliveryRuntime.d.ts +30 -0
  162. package/dist/vue/VoiceOpsActionCenter.d.ts +13 -0
  163. package/dist/vue/VoiceOpsStatus.d.ts +30 -0
  164. package/dist/vue/VoiceProviderCapabilities.d.ts +51 -0
  165. package/dist/vue/VoiceProviderContracts.d.ts +21 -0
  166. package/dist/vue/VoiceProviderSimulationControls.d.ts +88 -0
  167. package/dist/vue/VoiceProviderStatus.d.ts +51 -0
  168. package/dist/vue/VoiceRoutingStatus.d.ts +51 -0
  169. package/dist/vue/VoiceTurnLatency.d.ts +69 -0
  170. package/dist/vue/VoiceTurnQuality.d.ts +51 -0
  171. package/dist/vue/index.d.ts +22 -0
  172. package/dist/vue/index.js +3617 -53
  173. package/dist/vue/useVoiceCampaignDialerProof.d.ts +11 -0
  174. package/dist/vue/useVoiceController.d.ts +2 -1
  175. package/dist/vue/useVoiceDeliveryRuntime.d.ts +13 -0
  176. package/dist/vue/useVoiceOpsActionCenter.d.ts +11 -0
  177. package/dist/vue/useVoiceOpsStatus.d.ts +9 -0
  178. package/dist/vue/useVoiceProviderCapabilities.d.ts +9 -0
  179. package/dist/vue/useVoiceProviderContracts.d.ts +9 -0
  180. package/dist/vue/useVoiceProviderSimulationControls.d.ts +24 -0
  181. package/dist/vue/useVoiceProviderStatus.d.ts +1 -1
  182. package/dist/vue/useVoiceRoutingStatus.d.ts +8 -0
  183. package/dist/vue/useVoiceStream.d.ts +4 -1
  184. package/dist/vue/useVoiceTraceTimeline.d.ts +9 -0
  185. package/dist/vue/useVoiceTurnLatency.d.ts +10 -0
  186. package/dist/vue/useVoiceTurnQuality.d.ts +9 -0
  187. package/dist/vue/useVoiceWorkflowStatus.d.ts +9 -0
  188. package/dist/workflowContract.d.ts +91 -0
  189. package/package.json +1 -1
@@ -69,1245 +69,3028 @@ 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
810
1696
  },
811
- fast: {
812
- qualityProfile: "general",
813
- silenceMs: 700,
814
- speechThreshold: 0.015,
815
- transcriptStabilityMs: 450
1697
+ fast: {
1698
+ qualityProfile: "general",
1699
+ silenceMs: 700,
1700
+ speechThreshold: 0.015,
1701
+ transcriptStabilityMs: 450
1702
+ },
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
+ }
816
1784
  },
817
- "long-form": {
818
- qualityProfile: "general",
819
- silenceMs: 2200,
820
- speechThreshold: 0.01,
821
- transcriptStabilityMs: 1500
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}`);
2206
+ }
2207
+ return await response.json();
2208
+ };
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);
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
+ };
2277
+ };
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-contracts.service.ts
2320
+ import { computed as computed8, Injectable as Injectable8, signal as signal8 } from "@angular/core";
2321
+
2322
+ // src/client/providerContracts.ts
2323
+ var fetchVoiceProviderContracts = async (path = "/api/provider-contracts", options = {}) => {
2324
+ const fetchImpl = options.fetch ?? globalThis.fetch;
2325
+ const response = await fetchImpl(path);
2326
+ if (!response.ok) {
2327
+ throw new Error(`Voice provider contracts failed: HTTP ${response.status}`);
822
2328
  }
2329
+ return await response.json();
823
2330
  };
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
2331
+ var createVoiceProviderContractsStore = (path = "/api/provider-contracts", options = {}) => {
2332
+ const listeners = new Set;
2333
+ let closed = false;
2334
+ let timer;
2335
+ let snapshot = {
2336
+ error: null,
2337
+ isLoading: false
2338
+ };
2339
+ const emit = () => {
2340
+ for (const listener of listeners) {
2341
+ listener();
2342
+ }
2343
+ };
2344
+ const refresh = async () => {
2345
+ if (closed) {
2346
+ return snapshot.report;
2347
+ }
2348
+ snapshot = { ...snapshot, error: null, isLoading: true };
2349
+ emit();
2350
+ try {
2351
+ const report = await fetchVoiceProviderContracts(path, options);
2352
+ snapshot = {
2353
+ error: null,
2354
+ isLoading: false,
2355
+ report,
2356
+ updatedAt: Date.now()
2357
+ };
2358
+ emit();
2359
+ return report;
2360
+ } catch (error) {
2361
+ snapshot = {
2362
+ ...snapshot,
2363
+ error: error instanceof Error ? error.message : String(error),
2364
+ isLoading: false
2365
+ };
2366
+ emit();
2367
+ throw error;
2368
+ }
2369
+ };
2370
+ const close = () => {
2371
+ closed = true;
2372
+ if (timer) {
2373
+ clearInterval(timer);
2374
+ timer = undefined;
2375
+ }
2376
+ listeners.clear();
2377
+ };
2378
+ if (options.intervalMs && options.intervalMs > 0) {
2379
+ timer = setInterval(() => {
2380
+ refresh().catch(() => {});
2381
+ }, options.intervalMs);
840
2382
  }
2383
+ return {
2384
+ close,
2385
+ getServerSnapshot: () => snapshot,
2386
+ getSnapshot: () => snapshot,
2387
+ refresh,
2388
+ subscribe: (listener) => {
2389
+ listeners.add(listener);
2390
+ return () => {
2391
+ listeners.delete(listener);
2392
+ };
2393
+ }
2394
+ };
841
2395
  };
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];
2396
+
2397
+ // src/angular/voice-provider-contracts.service.ts
2398
+ var _dec = [
2399
+ Injectable8({ providedIn: "root" })
2400
+ ];
2401
+ var _init = __decoratorStart(undefined);
2402
+
2403
+ class VoiceProviderContractsService {
2404
+ connect(path = "/api/provider-contracts", options = {}) {
2405
+ const store = createVoiceProviderContractsStore(path, options);
2406
+ const errorSignal = signal8(null);
2407
+ const isLoadingSignal = signal8(false);
2408
+ const reportSignal = signal8(undefined);
2409
+ const updatedAtSignal = signal8(undefined);
2410
+ const sync = () => {
2411
+ const snapshot = store.getSnapshot();
2412
+ errorSignal.set(snapshot.error);
2413
+ isLoadingSignal.set(snapshot.isLoading);
2414
+ reportSignal.set(snapshot.report);
2415
+ updatedAtSignal.set(snapshot.updatedAt);
2416
+ };
2417
+ const unsubscribe = store.subscribe(sync);
2418
+ sync();
2419
+ store.refresh().catch(() => {});
2420
+ return {
2421
+ close: () => {
2422
+ unsubscribe();
2423
+ store.close();
2424
+ },
2425
+ error: computed8(() => errorSignal()),
2426
+ isLoading: computed8(() => isLoadingSignal()),
2427
+ refresh: store.refresh,
2428
+ report: computed8(() => reportSignal()),
2429
+ updatedAt: computed8(() => updatedAtSignal())
2430
+ };
2431
+ }
2432
+ }
2433
+ VoiceProviderContractsService = __decorateElement(_init, 0, "VoiceProviderContractsService", _dec, VoiceProviderContractsService);
2434
+ __runInitializers(_init, 1, VoiceProviderContractsService);
2435
+ __decoratorMetadata(_init, VoiceProviderContractsService);
2436
+ let _VoiceProviderContractsService = VoiceProviderContractsService;
2437
+ // src/angular/voice-provider-status.service.ts
2438
+ import { computed as computed9, Injectable as Injectable9, signal as signal9 } from "@angular/core";
2439
+
2440
+ // src/client/providerStatus.ts
2441
+ var fetchVoiceProviderStatus = async (path = "/api/provider-status", options = {}) => {
2442
+ const fetchImpl = options.fetch ?? globalThis.fetch;
2443
+ const response = await fetchImpl(path);
2444
+ if (!response.ok) {
2445
+ throw new Error(`Voice provider status failed: HTTP ${response.status}`);
2446
+ }
2447
+ return await response.json();
2448
+ };
2449
+ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {}) => {
2450
+ const listeners = new Set;
2451
+ let closed = false;
2452
+ let timer;
2453
+ let snapshot = {
2454
+ error: null,
2455
+ isLoading: false,
2456
+ providers: []
2457
+ };
2458
+ const emit = () => {
2459
+ for (const listener of listeners) {
2460
+ listener();
2461
+ }
2462
+ };
2463
+ const refresh = async () => {
2464
+ if (closed) {
2465
+ return snapshot.providers;
2466
+ }
2467
+ snapshot = {
2468
+ ...snapshot,
2469
+ error: null,
2470
+ isLoading: true
2471
+ };
2472
+ emit();
2473
+ try {
2474
+ const providers = await fetchVoiceProviderStatus(path, options);
2475
+ snapshot = {
2476
+ error: null,
2477
+ isLoading: false,
2478
+ providers,
2479
+ updatedAt: Date.now()
2480
+ };
2481
+ emit();
2482
+ return providers;
2483
+ } catch (error) {
2484
+ snapshot = {
2485
+ ...snapshot,
2486
+ error: error instanceof Error ? error.message : String(error),
2487
+ isLoading: false
2488
+ };
2489
+ emit();
2490
+ throw error;
2491
+ }
2492
+ };
2493
+ const close = () => {
2494
+ closed = true;
2495
+ if (timer) {
2496
+ clearInterval(timer);
2497
+ timer = undefined;
2498
+ }
2499
+ listeners.clear();
2500
+ };
2501
+ if (options.intervalMs && options.intervalMs > 0) {
2502
+ timer = setInterval(() => {
2503
+ refresh().catch(() => {});
2504
+ }, options.intervalMs);
2505
+ }
849
2506
  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
2507
+ close,
2508
+ getServerSnapshot: () => snapshot,
2509
+ getSnapshot: () => snapshot,
2510
+ refresh,
2511
+ subscribe: (listener) => {
2512
+ listeners.add(listener);
2513
+ return () => {
2514
+ listeners.delete(listener);
2515
+ };
2516
+ }
2517
+ };
2518
+ };
2519
+
2520
+ // src/angular/voice-provider-status.service.ts
2521
+ var _dec = [
2522
+ Injectable9({ providedIn: "root" })
2523
+ ];
2524
+ var _init = __decoratorStart(undefined);
2525
+
2526
+ class VoiceProviderStatusService {
2527
+ connect(path = "/api/provider-status", options = {}) {
2528
+ const store = createVoiceProviderStatusStore(path, options);
2529
+ const errorSignal = signal9(null);
2530
+ const isLoadingSignal = signal9(false);
2531
+ const providersSignal = signal9([]);
2532
+ const updatedAtSignal = signal9(undefined);
2533
+ const sync = () => {
2534
+ const snapshot = store.getSnapshot();
2535
+ errorSignal.set(snapshot.error);
2536
+ isLoadingSignal.set(snapshot.isLoading);
2537
+ providersSignal.set([...snapshot.providers]);
2538
+ updatedAtSignal.set(snapshot.updatedAt);
2539
+ };
2540
+ const unsubscribe = store.subscribe(sync);
2541
+ sync();
2542
+ store.refresh().catch(() => {});
2543
+ return {
2544
+ close: () => {
2545
+ unsubscribe();
2546
+ store.close();
2547
+ },
2548
+ error: computed9(() => errorSignal()),
2549
+ isLoading: computed9(() => isLoadingSignal()),
2550
+ providers: computed9(() => providersSignal()),
2551
+ refresh: store.refresh,
2552
+ updatedAt: computed9(() => updatedAtSignal())
2553
+ };
2554
+ }
2555
+ }
2556
+ VoiceProviderStatusService = __decorateElement(_init, 0, "VoiceProviderStatusService", _dec, VoiceProviderStatusService);
2557
+ __runInitializers(_init, 1, VoiceProviderStatusService);
2558
+ __decoratorMetadata(_init, VoiceProviderStatusService);
2559
+ let _VoiceProviderStatusService = VoiceProviderStatusService;
2560
+ // src/angular/voice-routing-status.service.ts
2561
+ import { Injectable as Injectable10, signal as signal10 } from "@angular/core";
2562
+
2563
+ // src/client/routingStatus.ts
2564
+ var fetchVoiceRoutingStatus = async (path = "/api/routing/latest", options = {}) => {
2565
+ const fetchImpl = options.fetch ?? globalThis.fetch;
2566
+ const response = await fetchImpl(path);
2567
+ if (!response.ok) {
2568
+ throw new Error(`Voice routing status failed: HTTP ${response.status}`);
2569
+ }
2570
+ return await response.json();
2571
+ };
2572
+ var createVoiceRoutingStatusStore = (path = "/api/routing/latest", options = {}) => {
2573
+ const listeners = new Set;
2574
+ let closed = false;
2575
+ let timer;
2576
+ let snapshot = {
2577
+ decision: null,
2578
+ error: null,
2579
+ isLoading: false
855
2580
  };
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"
2581
+ const emit = () => {
2582
+ for (const listener of listeners) {
2583
+ listener();
881
2584
  }
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"
2585
+ };
2586
+ const refresh = async () => {
2587
+ if (closed) {
2588
+ return snapshot.decision;
897
2589
  }
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"
2590
+ snapshot = {
2591
+ ...snapshot,
2592
+ error: null,
2593
+ isLoading: true
2594
+ };
2595
+ emit();
2596
+ try {
2597
+ const decision = await fetchVoiceRoutingStatus(path, options);
2598
+ snapshot = {
2599
+ decision,
2600
+ error: null,
2601
+ isLoading: false,
2602
+ updatedAt: Date.now()
2603
+ };
2604
+ emit();
2605
+ return decision;
2606
+ } catch (error) {
2607
+ snapshot = {
2608
+ ...snapshot,
2609
+ error: error instanceof Error ? error.message : String(error),
2610
+ isLoading: false
2611
+ };
2612
+ emit();
2613
+ throw error;
920
2614
  }
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"
2615
+ };
2616
+ const close = () => {
2617
+ closed = true;
2618
+ if (timer) {
2619
+ clearInterval(timer);
2620
+ timer = undefined;
943
2621
  }
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
2622
+ listeners.clear();
2623
+ };
2624
+ if (options.intervalMs && options.intervalMs > 0) {
2625
+ timer = setInterval(() => {
2626
+ refresh().catch(() => {});
2627
+ }, options.intervalMs);
2628
+ }
2629
+ return {
2630
+ close,
2631
+ getServerSnapshot: () => snapshot,
2632
+ getSnapshot: () => snapshot,
2633
+ refresh,
2634
+ subscribe: (listener) => {
2635
+ listeners.add(listener);
2636
+ return () => {
2637
+ listeners.delete(listener);
2638
+ };
969
2639
  }
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
2640
+ };
2641
+ };
2642
+
2643
+ // src/angular/voice-routing-status.service.ts
2644
+ var _dec = [
2645
+ Injectable10({ providedIn: "root" })
2646
+ ];
2647
+ var _init = __decoratorStart(undefined);
2648
+
2649
+ class VoiceRoutingStatusService {
2650
+ connect(path = "/api/routing/latest", options = {}) {
2651
+ const store = createVoiceRoutingStatusStore(path, options);
2652
+ const decisionSignal = signal10(null);
2653
+ const errorSignal = signal10(null);
2654
+ const isLoadingSignal = signal10(false);
2655
+ const updatedAtSignal = signal10(undefined);
2656
+ const sync = () => {
2657
+ const snapshot = store.getSnapshot();
2658
+ decisionSignal.set(snapshot.decision);
2659
+ errorSignal.set(snapshot.error);
2660
+ isLoadingSignal.set(snapshot.isLoading);
2661
+ updatedAtSignal.set(snapshot.updatedAt);
2662
+ };
2663
+ const unsubscribe = store.subscribe(sync);
2664
+ sync();
2665
+ store.refresh().catch(() => {});
2666
+ return {
2667
+ close: () => {
2668
+ unsubscribe();
2669
+ store.close();
2670
+ },
2671
+ decision: decisionSignal.asReadonly(),
2672
+ error: errorSignal.asReadonly(),
2673
+ isLoading: isLoadingSignal.asReadonly(),
2674
+ refresh: store.refresh,
2675
+ updatedAt: updatedAtSignal.asReadonly()
2676
+ };
2677
+ }
2678
+ }
2679
+ VoiceRoutingStatusService = __decorateElement(_init, 0, "VoiceRoutingStatusService", _dec, VoiceRoutingStatusService);
2680
+ __runInitializers(_init, 1, VoiceRoutingStatusService);
2681
+ __decoratorMetadata(_init, VoiceRoutingStatusService);
2682
+ let _VoiceRoutingStatusService = VoiceRoutingStatusService;
2683
+ // src/angular/voice-trace-timeline.service.ts
2684
+ import { computed as computed10, Injectable as Injectable11, signal as signal11 } from "@angular/core";
2685
+
2686
+ // src/client/traceTimeline.ts
2687
+ var fetchVoiceTraceTimeline = async (path = "/api/voice-traces", options = {}) => {
2688
+ const fetchImpl = options.fetch ?? globalThis.fetch;
2689
+ const response = await fetchImpl(path);
2690
+ if (!response.ok) {
2691
+ throw new Error(`Voice trace timeline failed: HTTP ${response.status}`);
2692
+ }
2693
+ return await response.json();
2694
+ };
2695
+ var createVoiceTraceTimelineStore = (path = "/api/voice-traces", options = {}) => {
2696
+ const listeners = new Set;
2697
+ let closed = false;
2698
+ let timer;
2699
+ let snapshot = {
2700
+ error: null,
2701
+ isLoading: false,
2702
+ report: null
2703
+ };
2704
+ const emit = () => {
2705
+ for (const listener of listeners) {
2706
+ listener();
995
2707
  }
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
2708
+ };
2709
+ const refresh = async () => {
2710
+ if (closed) {
2711
+ return snapshot.report;
1021
2712
  }
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"
2713
+ snapshot = {
2714
+ ...snapshot,
2715
+ error: null,
2716
+ isLoading: true
2717
+ };
2718
+ emit();
2719
+ try {
2720
+ const report = await fetchVoiceTraceTimeline(path, options);
2721
+ snapshot = {
2722
+ error: null,
2723
+ isLoading: false,
2724
+ report,
2725
+ updatedAt: Date.now()
2726
+ };
2727
+ emit();
2728
+ return report;
2729
+ } catch (error) {
2730
+ snapshot = {
2731
+ ...snapshot,
2732
+ error: error instanceof Error ? error.message : String(error),
2733
+ isLoading: false
2734
+ };
2735
+ emit();
2736
+ throw error;
2737
+ }
2738
+ };
2739
+ const close = () => {
2740
+ closed = true;
2741
+ if (timer) {
2742
+ clearInterval(timer);
2743
+ timer = undefined;
1044
2744
  }
2745
+ listeners.clear();
2746
+ };
2747
+ if (options.intervalMs && options.intervalMs > 0) {
2748
+ timer = setInterval(() => {
2749
+ refresh().catch(() => {});
2750
+ }, options.intervalMs);
1045
2751
  }
1046
- };
1047
- var resolveVoiceRuntimePreset = (name = "default") => {
1048
- const preset = PRESET_INPUTS[name];
1049
2752
  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)
2753
+ close,
2754
+ getServerSnapshot: () => snapshot,
2755
+ getSnapshot: () => snapshot,
2756
+ refresh,
2757
+ subscribe: (listener) => {
2758
+ listeners.add(listener);
2759
+ return () => {
2760
+ listeners.delete(listener);
2761
+ };
2762
+ }
1061
2763
  };
1062
2764
  };
1063
2765
 
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();
2766
+ // src/angular/voice-trace-timeline.service.ts
2767
+ var _dec = [
2768
+ Injectable11({ providedIn: "root" })
2769
+ ];
2770
+ var _init = __decoratorStart(undefined);
2771
+
2772
+ class VoiceTraceTimelineService {
2773
+ connect(path = "/api/voice-traces", options = {}) {
2774
+ const store = createVoiceTraceTimelineStore(path, options);
2775
+ const errorSignal = signal11(null);
2776
+ const isLoadingSignal = signal11(false);
2777
+ const reportSignal = signal11(null);
2778
+ const updatedAtSignal = signal11(undefined);
2779
+ const sync = () => {
2780
+ const snapshot = store.getSnapshot();
2781
+ errorSignal.set(snapshot.error);
2782
+ isLoadingSignal.set(snapshot.isLoading);
2783
+ reportSignal.set(snapshot.report);
2784
+ updatedAtSignal.set(snapshot.updatedAt);
2785
+ };
2786
+ const unsubscribe = store.subscribe(sync);
2787
+ sync();
2788
+ store.refresh().catch(() => {});
2789
+ return {
2790
+ close: () => {
2791
+ unsubscribe();
2792
+ store.close();
2793
+ },
2794
+ error: computed10(() => errorSignal()),
2795
+ isLoading: computed10(() => isLoadingSignal()),
2796
+ refresh: store.refresh,
2797
+ report: computed10(() => reportSignal()),
2798
+ updatedAt: computed10(() => updatedAtSignal())
2799
+ };
2800
+ }
2801
+ }
2802
+ VoiceTraceTimelineService = __decorateElement(_init, 0, "VoiceTraceTimelineService", _dec, VoiceTraceTimelineService);
2803
+ __runInitializers(_init, 1, VoiceTraceTimelineService);
2804
+ __decoratorMetadata(_init, VoiceTraceTimelineService);
2805
+ let _VoiceTraceTimelineService = VoiceTraceTimelineService;
2806
+ // src/angular/voice-turn-latency.service.ts
2807
+ import { computed as computed11, Injectable as Injectable12, signal as signal12 } from "@angular/core";
2808
+
2809
+ // src/client/turnLatency.ts
2810
+ var fetchVoiceTurnLatency = async (path = "/api/turn-latency", options = {}) => {
2811
+ const fetchImpl = options.fetch ?? globalThis.fetch;
2812
+ const response = await fetchImpl(path);
2813
+ if (!response.ok) {
2814
+ throw new Error(`Voice turn latency failed: HTTP ${response.status}`);
2815
+ }
2816
+ return await response.json();
2817
+ };
2818
+ var runVoiceTurnLatencyProof = async (path, options = {}) => {
2819
+ const fetchImpl = options.fetch ?? globalThis.fetch;
2820
+ const response = await fetchImpl(path, { method: "POST" });
2821
+ if (!response.ok) {
2822
+ throw new Error(`Voice turn latency proof failed: HTTP ${response.status}`);
2823
+ }
2824
+ return response.json();
2825
+ };
2826
+ var createVoiceTurnLatencyStore = (path = "/api/turn-latency", options = {}) => {
2827
+ const listeners = new Set;
2828
+ let closed = false;
2829
+ let timer;
2830
+ let snapshot = {
2831
+ error: null,
2832
+ isLoading: false
2833
+ };
2834
+ const emit = () => {
2835
+ for (const listener of listeners) {
2836
+ listener();
1090
2837
  }
1091
2838
  };
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
2839
+ const refresh = async () => {
2840
+ if (closed) {
2841
+ return snapshot.report;
2842
+ }
2843
+ snapshot = { ...snapshot, error: null, isLoading: true };
2844
+ emit();
2845
+ try {
2846
+ const report = await fetchVoiceTurnLatency(path, options);
2847
+ snapshot = {
2848
+ error: null,
2849
+ isLoading: false,
2850
+ report,
2851
+ updatedAt: Date.now()
1111
2852
  };
2853
+ emit();
2854
+ return report;
2855
+ } catch (error) {
2856
+ snapshot = {
2857
+ ...snapshot,
2858
+ error: error instanceof Error ? error.message : String(error),
2859
+ isLoading: false
2860
+ };
2861
+ emit();
2862
+ throw error;
1112
2863
  }
1113
- notify();
1114
2864
  };
1115
- const unsubscribeStream = stream.subscribe(sync);
1116
- sync();
1117
- const ensureCapture = () => {
1118
- if (capture) {
1119
- return capture;
2865
+ const runProof = async () => {
2866
+ if (!options.proofPath) {
2867
+ throw new Error("Voice turn latency proof path is not configured.");
2868
+ }
2869
+ snapshot = { ...snapshot, error: null, isLoading: true };
2870
+ emit();
2871
+ try {
2872
+ await runVoiceTurnLatencyProof(options.proofPath, options);
2873
+ return await refresh();
2874
+ } catch (error) {
2875
+ snapshot = {
2876
+ ...snapshot,
2877
+ error: error instanceof Error ? error.message : String(error),
2878
+ isLoading: false
2879
+ };
2880
+ emit();
2881
+ throw error;
2882
+ }
2883
+ };
2884
+ const close = () => {
2885
+ closed = true;
2886
+ if (timer) {
2887
+ clearInterval(timer);
2888
+ timer = undefined;
2889
+ }
2890
+ listeners.clear();
2891
+ };
2892
+ if (options.intervalMs && options.intervalMs > 0) {
2893
+ timer = setInterval(() => {
2894
+ refresh().catch(() => {});
2895
+ }, options.intervalMs);
2896
+ }
2897
+ return {
2898
+ close,
2899
+ getServerSnapshot: () => snapshot,
2900
+ getSnapshot: () => snapshot,
2901
+ refresh,
2902
+ runProof,
2903
+ subscribe: (listener) => {
2904
+ listeners.add(listener);
2905
+ return () => {
2906
+ listeners.delete(listener);
2907
+ };
2908
+ }
2909
+ };
2910
+ };
2911
+
2912
+ // src/angular/voice-turn-latency.service.ts
2913
+ var _dec = [
2914
+ Injectable12({ providedIn: "root" })
2915
+ ];
2916
+ var _init = __decoratorStart(undefined);
2917
+
2918
+ class VoiceTurnLatencyService {
2919
+ connect(path = "/api/turn-latency", options = {}) {
2920
+ const store = createVoiceTurnLatencyStore(path, options);
2921
+ const errorSignal = signal12(null);
2922
+ const isLoadingSignal = signal12(false);
2923
+ const reportSignal = signal12(undefined);
2924
+ const updatedAtSignal = signal12(undefined);
2925
+ const sync = () => {
2926
+ const snapshot = store.getSnapshot();
2927
+ errorSignal.set(snapshot.error);
2928
+ isLoadingSignal.set(snapshot.isLoading);
2929
+ reportSignal.set(snapshot.report);
2930
+ updatedAtSignal.set(snapshot.updatedAt);
2931
+ };
2932
+ const unsubscribe = store.subscribe(sync);
2933
+ sync();
2934
+ store.refresh().catch(() => {});
2935
+ return {
2936
+ close: () => {
2937
+ unsubscribe();
2938
+ store.close();
2939
+ },
2940
+ error: computed11(() => errorSignal()),
2941
+ isLoading: computed11(() => isLoadingSignal()),
2942
+ refresh: store.refresh,
2943
+ report: computed11(() => reportSignal()),
2944
+ runProof: store.runProof,
2945
+ updatedAt: computed11(() => updatedAtSignal())
2946
+ };
2947
+ }
2948
+ }
2949
+ VoiceTurnLatencyService = __decorateElement(_init, 0, "VoiceTurnLatencyService", _dec, VoiceTurnLatencyService);
2950
+ __runInitializers(_init, 1, VoiceTurnLatencyService);
2951
+ __decoratorMetadata(_init, VoiceTurnLatencyService);
2952
+ let _VoiceTurnLatencyService = VoiceTurnLatencyService;
2953
+ // src/angular/voice-turn-quality.service.ts
2954
+ import { computed as computed12, Injectable as Injectable13, signal as signal13 } from "@angular/core";
2955
+
2956
+ // src/client/turnQuality.ts
2957
+ var fetchVoiceTurnQuality = async (path = "/api/turn-quality", options = {}) => {
2958
+ const fetchImpl = options.fetch ?? globalThis.fetch;
2959
+ const response = await fetchImpl(path);
2960
+ if (!response.ok) {
2961
+ throw new Error(`Voice turn quality failed: HTTP ${response.status}`);
2962
+ }
2963
+ return await response.json();
2964
+ };
2965
+ var createVoiceTurnQualityStore = (path = "/api/turn-quality", options = {}) => {
2966
+ const listeners = new Set;
2967
+ let closed = false;
2968
+ let timer;
2969
+ let snapshot = {
2970
+ error: null,
2971
+ isLoading: false
2972
+ };
2973
+ const emit = () => {
2974
+ for (const listener of listeners) {
2975
+ listener();
1120
2976
  }
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
2977
  };
1138
- const startRecording = async () => {
1139
- if (state.isRecording) {
1140
- return;
2978
+ const refresh = async () => {
2979
+ if (closed) {
2980
+ return snapshot.report;
1141
2981
  }
2982
+ snapshot = {
2983
+ ...snapshot,
2984
+ error: null,
2985
+ isLoading: true
2986
+ };
2987
+ emit();
1142
2988
  try {
1143
- state = {
1144
- ...state,
1145
- recordingError: null
1146
- };
1147
- notify();
1148
- await ensureCapture().start();
1149
- state = {
1150
- ...state,
1151
- isRecording: true
2989
+ const report = await fetchVoiceTurnQuality(path, options);
2990
+ snapshot = {
2991
+ error: null,
2992
+ isLoading: false,
2993
+ report,
2994
+ updatedAt: Date.now()
1152
2995
  };
1153
- notify();
2996
+ emit();
2997
+ return report;
1154
2998
  } catch (error) {
1155
- capture = null;
1156
- state = {
1157
- ...state,
1158
- isRecording: false,
1159
- recordingError: error instanceof Error ? error.message : String(error)
2999
+ snapshot = {
3000
+ ...snapshot,
3001
+ error: error instanceof Error ? error.message : String(error),
3002
+ isLoading: false
1160
3003
  };
1161
- notify();
3004
+ emit();
1162
3005
  throw error;
1163
3006
  }
1164
3007
  };
1165
3008
  const close = () => {
1166
- unsubscribeStream();
1167
- stopRecording();
1168
- stream.close();
3009
+ closed = true;
3010
+ if (timer) {
3011
+ clearInterval(timer);
3012
+ timer = undefined;
3013
+ }
3014
+ listeners.clear();
1169
3015
  };
3016
+ if (options.intervalMs && options.intervalMs > 0) {
3017
+ timer = setInterval(() => {
3018
+ refresh().catch(() => {});
3019
+ }, options.intervalMs);
3020
+ }
1170
3021
  return {
1171
- bindHTMX(bindingOptions) {
1172
- return bindVoiceHTMX(stream, bindingOptions);
1173
- },
1174
3022
  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);
3023
+ getServerSnapshot: () => snapshot,
3024
+ getSnapshot: () => snapshot,
3025
+ refresh,
3026
+ subscribe: (listener) => {
3027
+ listeners.add(listener);
1207
3028
  return () => {
1208
- subscribers.delete(subscriber);
3029
+ listeners.delete(listener);
1209
3030
  };
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
3031
  }
1227
3032
  };
1228
3033
  };
1229
3034
 
1230
- // src/angular/voice-controller.service.ts
3035
+ // src/angular/voice-turn-quality.service.ts
1231
3036
  var _dec = [
1232
- Injectable2({ providedIn: "root" })
3037
+ Injectable13({ providedIn: "root" })
1233
3038
  ];
1234
3039
  var _init = __decoratorStart(undefined);
1235
3040
 
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([]);
3041
+ class VoiceTurnQualityService {
3042
+ connect(path = "/api/turn-quality", options = {}) {
3043
+ const store = createVoiceTurnQualityStore(path, options);
3044
+ const errorSignal = signal13(null);
3045
+ const isLoadingSignal = signal13(false);
3046
+ const reportSignal = signal13(undefined);
3047
+ const updatedAtSignal = signal13(undefined);
1249
3048
  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]);
3049
+ const snapshot = store.getSnapshot();
3050
+ errorSignal.set(snapshot.error);
3051
+ isLoadingSignal.set(snapshot.isLoading);
3052
+ reportSignal.set(snapshot.report);
3053
+ updatedAtSignal.set(snapshot.updatedAt);
1260
3054
  };
1261
- const unsubscribe = controller.subscribe(sync);
3055
+ const unsubscribe = store.subscribe(sync);
1262
3056
  sync();
3057
+ store.refresh().catch(() => {});
1263
3058
  return {
1264
- assistantAudio: computed2(() => assistantAudioSignal()),
1265
- assistantTexts: computed2(() => assistantTextsSignal()),
1266
- bindHTMX: controller.bindHTMX,
1267
3059
  close: () => {
1268
3060
  unsubscribe();
1269
- controller.close();
3061
+ store.close();
1270
3062
  },
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())
3063
+ error: computed12(() => errorSignal()),
3064
+ isLoading: computed12(() => isLoadingSignal()),
3065
+ refresh: store.refresh,
3066
+ report: computed12(() => reportSignal()),
3067
+ updatedAt: computed12(() => updatedAtSignal())
1284
3068
  };
1285
3069
  }
1286
3070
  }
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";
3071
+ VoiceTurnQualityService = __decorateElement(_init, 0, "VoiceTurnQualityService", _dec, VoiceTurnQualityService);
3072
+ __runInitializers(_init, 1, VoiceTurnQualityService);
3073
+ __decoratorMetadata(_init, VoiceTurnQualityService);
3074
+ let _VoiceTurnQualityService = VoiceTurnQualityService;
3075
+ // src/angular/voice-workflow-status.service.ts
3076
+ import { computed as computed13, Injectable as Injectable14, signal as signal14 } from "@angular/core";
1293
3077
 
1294
- // src/client/providerStatus.ts
1295
- var fetchVoiceProviderStatus = async (path = "/api/provider-status", options = {}) => {
3078
+ // src/client/workflowStatus.ts
3079
+ var fetchVoiceWorkflowStatus = async (path = "/evals/scenarios/json", options = {}) => {
1296
3080
  const fetchImpl = options.fetch ?? globalThis.fetch;
1297
3081
  const response = await fetchImpl(path);
1298
3082
  if (!response.ok) {
1299
- throw new Error(`Voice provider status failed: HTTP ${response.status}`);
3083
+ throw new Error(`Voice workflow status failed: HTTP ${response.status}`);
1300
3084
  }
1301
3085
  return await response.json();
1302
3086
  };
1303
- var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {}) => {
3087
+ var createVoiceWorkflowStatusStore = (path = "/evals/scenarios/json", options = {}) => {
1304
3088
  const listeners = new Set;
1305
3089
  let closed = false;
1306
3090
  let timer;
1307
3091
  let snapshot = {
1308
3092
  error: null,
1309
- isLoading: false,
1310
- providers: []
3093
+ isLoading: false
1311
3094
  };
1312
3095
  const emit = () => {
1313
3096
  for (const listener of listeners) {
@@ -1316,7 +3099,7 @@ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {
1316
3099
  };
1317
3100
  const refresh = async () => {
1318
3101
  if (closed) {
1319
- return snapshot.providers;
3102
+ return snapshot.report;
1320
3103
  }
1321
3104
  snapshot = {
1322
3105
  ...snapshot,
@@ -1325,15 +3108,15 @@ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {
1325
3108
  };
1326
3109
  emit();
1327
3110
  try {
1328
- const providers = await fetchVoiceProviderStatus(path, options);
3111
+ const report = await fetchVoiceWorkflowStatus(path, options);
1329
3112
  snapshot = {
1330
3113
  error: null,
1331
3114
  isLoading: false,
1332
- providers,
3115
+ report,
1333
3116
  updatedAt: Date.now()
1334
3117
  };
1335
3118
  emit();
1336
- return providers;
3119
+ return report;
1337
3120
  } catch (error) {
1338
3121
  snapshot = {
1339
3122
  ...snapshot,
@@ -1352,7 +3135,7 @@ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {
1352
3135
  }
1353
3136
  listeners.clear();
1354
3137
  };
1355
- if (options.intervalMs && options.intervalMs > 0) {
3138
+ if (typeof window !== "undefined" && options.intervalMs && options.intervalMs > 0) {
1356
3139
  timer = setInterval(() => {
1357
3140
  refresh().catch(() => {});
1358
3141
  }, options.intervalMs);
@@ -1371,48 +3154,350 @@ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {
1371
3154
  };
1372
3155
  };
1373
3156
 
1374
- // src/angular/voice-provider-status.service.ts
3157
+ // src/angular/voice-workflow-status.service.ts
1375
3158
  var _dec = [
1376
- Injectable3({ providedIn: "root" })
3159
+ Injectable14({ providedIn: "root" })
1377
3160
  ];
1378
3161
  var _init = __decoratorStart(undefined);
1379
3162
 
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);
3163
+ class VoiceWorkflowStatusService {
3164
+ connect(path = "/evals/scenarios/json", options = {}) {
3165
+ const store = createVoiceWorkflowStatusStore(path, options);
3166
+ const errorSignal = signal14(null);
3167
+ const isLoadingSignal = signal14(false);
3168
+ const reportSignal = signal14(undefined);
3169
+ const updatedAtSignal = signal14(undefined);
1387
3170
  const sync = () => {
1388
3171
  const snapshot = store.getSnapshot();
1389
3172
  errorSignal.set(snapshot.error);
1390
3173
  isLoadingSignal.set(snapshot.isLoading);
1391
- providersSignal.set([...snapshot.providers]);
3174
+ reportSignal.set(snapshot.report);
1392
3175
  updatedAtSignal.set(snapshot.updatedAt);
1393
3176
  };
1394
3177
  const unsubscribe = store.subscribe(sync);
1395
3178
  sync();
1396
- store.refresh().catch(() => {});
3179
+ if (typeof window !== "undefined") {
3180
+ store.refresh().catch(() => {});
3181
+ }
1397
3182
  return {
1398
3183
  close: () => {
1399
3184
  unsubscribe();
1400
3185
  store.close();
1401
3186
  },
1402
- error: computed3(() => errorSignal()),
1403
- isLoading: computed3(() => isLoadingSignal()),
1404
- providers: computed3(() => providersSignal()),
3187
+ error: computed13(() => errorSignal()),
3188
+ isLoading: computed13(() => isLoadingSignal()),
1405
3189
  refresh: store.refresh,
1406
- updatedAt: computed3(() => updatedAtSignal())
3190
+ report: computed13(() => reportSignal()),
3191
+ updatedAt: computed13(() => updatedAtSignal())
1407
3192
  };
1408
3193
  }
1409
3194
  }
1410
- VoiceProviderStatusService = __decorateElement(_init, 0, "VoiceProviderStatusService", _dec, VoiceProviderStatusService);
1411
- __runInitializers(_init, 1, VoiceProviderStatusService);
1412
- __decoratorMetadata(_init, VoiceProviderStatusService);
1413
- let _VoiceProviderStatusService = VoiceProviderStatusService;
3195
+ VoiceWorkflowStatusService = __decorateElement(_init, 0, "VoiceWorkflowStatusService", _dec, VoiceWorkflowStatusService);
3196
+ __runInitializers(_init, 1, VoiceWorkflowStatusService);
3197
+ __decoratorMetadata(_init, VoiceWorkflowStatusService);
3198
+ let _VoiceWorkflowStatusService = VoiceWorkflowStatusService;
3199
+ // src/angular/voice-delivery-runtime.component.ts
3200
+ import { Component, Input, signal as signal15 } from "@angular/core";
3201
+
3202
+ // src/client/deliveryRuntimeWidget.ts
3203
+ var DEFAULT_TITLE = "Voice Delivery Runtime";
3204
+ var DEFAULT_DESCRIPTION = "Audit and trace delivery worker health from your AbsoluteJS voice app.";
3205
+ var escapeHtml = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3206
+ var createSurface = (id, summary) => {
3207
+ if (!summary) {
3208
+ return {
3209
+ deadLettered: 0,
3210
+ detail: "Worker disabled",
3211
+ failed: 0,
3212
+ id,
3213
+ label: id === "audit" ? "Audit delivery" : "Trace delivery",
3214
+ pending: 0,
3215
+ status: "disabled",
3216
+ total: 0
3217
+ };
3218
+ }
3219
+ const blocked = summary.failed + summary.deadLettered;
3220
+ return {
3221
+ deadLettered: summary.deadLettered,
3222
+ detail: `${summary.delivered}/${summary.total} delivered, ${summary.pending} pending`,
3223
+ failed: summary.failed,
3224
+ id,
3225
+ label: id === "audit" ? "Audit delivery" : "Trace delivery",
3226
+ pending: summary.pending,
3227
+ status: blocked > 0 ? "warn" : "pass",
3228
+ total: summary.total
3229
+ };
3230
+ };
3231
+ var createVoiceDeliveryRuntimeViewModel = (snapshot, options = {}) => {
3232
+ const report = snapshot.report;
3233
+ const surfaces = [
3234
+ createSurface("audit", report?.summary.audit),
3235
+ createSurface("trace", report?.summary.trace)
3236
+ ];
3237
+ const hasWarnings = surfaces.some((surface) => surface.status === "warn");
3238
+ return {
3239
+ description: options.description ?? DEFAULT_DESCRIPTION,
3240
+ error: snapshot.error,
3241
+ actionError: snapshot.actionError,
3242
+ actionStatus: snapshot.actionStatus,
3243
+ isLoading: snapshot.isLoading,
3244
+ isRunning: Boolean(report?.isRunning),
3245
+ label: snapshot.error ? "Unavailable" : report ? report.isRunning ? "Running" : "Stopped" : "Checking",
3246
+ status: snapshot.error ? "error" : report ? hasWarnings ? "warn" : "pass" : "loading",
3247
+ surfaces,
3248
+ title: options.title ?? DEFAULT_TITLE,
3249
+ updatedAt: snapshot.updatedAt
3250
+ };
3251
+ };
3252
+ var renderVoiceDeliveryRuntimeHTML = (snapshot, options = {}) => {
3253
+ const model = createVoiceDeliveryRuntimeViewModel(snapshot, options);
3254
+ const surfaces = model.surfaces.map((surface) => `<li class="absolute-voice-delivery-runtime__surface absolute-voice-delivery-runtime__surface--${escapeHtml(surface.status)}">
3255
+ <span>${escapeHtml(surface.label)}</span>
3256
+ <strong>${escapeHtml(surface.detail)}</strong>
3257
+ <small>${String(surface.failed)} failed &middot; ${String(surface.deadLettered)} dead-lettered</small>
3258
+ </li>`).join("");
3259
+ const actions = options.includeActions === false ? "" : `<div class="absolute-voice-delivery-runtime__actions">
3260
+ <button type="button" data-absolute-voice-delivery-runtime-action="tick">${model.actionStatus === "running" ? "Working..." : "Tick workers"}</button>
3261
+ <button type="button" data-absolute-voice-delivery-runtime-action="requeue-dead-letters"${model.surfaces.some((surface) => surface.deadLettered > 0) ? "" : " disabled"}>Requeue dead letters</button>
3262
+ </div>`;
3263
+ const actionError = model.actionError ? `<p class="absolute-voice-delivery-runtime__error">${escapeHtml(model.actionError)}</p>` : "";
3264
+ return `<section class="absolute-voice-delivery-runtime absolute-voice-delivery-runtime--${escapeHtml(model.status)}">
3265
+ <header class="absolute-voice-delivery-runtime__header">
3266
+ <span class="absolute-voice-delivery-runtime__eyebrow">${escapeHtml(model.title)}</span>
3267
+ <strong class="absolute-voice-delivery-runtime__label">${escapeHtml(model.label)}</strong>
3268
+ </header>
3269
+ <p class="absolute-voice-delivery-runtime__description">${escapeHtml(model.description)}</p>
3270
+ <ul class="absolute-voice-delivery-runtime__surfaces">${surfaces}</ul>
3271
+ ${actions}
3272
+ ${actionError}
3273
+ ${model.error ? `<p class="absolute-voice-delivery-runtime__error">${escapeHtml(model.error)}</p>` : ""}
3274
+ </section>`;
3275
+ };
3276
+ 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}`;
3277
+ var mountVoiceDeliveryRuntime = (element, path = "/api/voice-delivery-runtime", options = {}) => {
3278
+ const store = createVoiceDeliveryRuntimeStore(path, options);
3279
+ const render = () => {
3280
+ element.innerHTML = renderVoiceDeliveryRuntimeHTML(store.getSnapshot(), options);
3281
+ };
3282
+ const unsubscribe = store.subscribe(render);
3283
+ const handleClick = (event) => {
3284
+ const target = event.target;
3285
+ if (!(target instanceof Element)) {
3286
+ return;
3287
+ }
3288
+ const action = target.closest("[data-absolute-voice-delivery-runtime-action]");
3289
+ const actionName = action?.getAttribute("data-absolute-voice-delivery-runtime-action");
3290
+ if (actionName === "tick") {
3291
+ store.tick().catch(() => {});
3292
+ }
3293
+ if (actionName === "requeue-dead-letters") {
3294
+ store.requeueDeadLetters().catch(() => {});
3295
+ }
3296
+ };
3297
+ element.addEventListener?.("click", handleClick);
3298
+ render();
3299
+ store.refresh().catch(() => {});
3300
+ return {
3301
+ close: () => {
3302
+ element.removeEventListener?.("click", handleClick);
3303
+ unsubscribe();
3304
+ store.close();
3305
+ },
3306
+ refresh: store.refresh
3307
+ };
3308
+ };
3309
+ var defineVoiceDeliveryRuntimeElement = (tagName = "absolute-voice-delivery-runtime") => {
3310
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
3311
+ return;
3312
+ }
3313
+ customElements.define(tagName, class AbsoluteVoiceDeliveryRuntimeElement extends HTMLElement {
3314
+ mounted;
3315
+ connectedCallback() {
3316
+ const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
3317
+ this.mounted = mountVoiceDeliveryRuntime(this, this.getAttribute("path") ?? "/api/voice-delivery-runtime", {
3318
+ description: this.getAttribute("description") ?? undefined,
3319
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
3320
+ title: this.getAttribute("title") ?? undefined
3321
+ });
3322
+ }
3323
+ disconnectedCallback() {
3324
+ this.mounted?.close();
3325
+ this.mounted = undefined;
3326
+ }
3327
+ });
3328
+ };
3329
+
3330
+ // src/angular/voice-delivery-runtime.component.ts
3331
+ var _dec = [
3332
+ Component({
3333
+ selector: "absolute-voice-delivery-runtime",
3334
+ standalone: true,
3335
+ template: `
3336
+ <section
3337
+ class="absolute-voice-delivery-runtime"
3338
+ [class.absolute-voice-delivery-runtime--pass]="model().status === 'pass'"
3339
+ [class.absolute-voice-delivery-runtime--warn]="model().status === 'warn'"
3340
+ [class.absolute-voice-delivery-runtime--loading]="
3341
+ model().status === 'loading'
3342
+ "
3343
+ [class.absolute-voice-delivery-runtime--error]="
3344
+ model().status === 'error'
3345
+ "
3346
+ >
3347
+ <header class="absolute-voice-delivery-runtime__header">
3348
+ <span class="absolute-voice-delivery-runtime__eyebrow">{{
3349
+ model().title
3350
+ }}</span>
3351
+ <strong class="absolute-voice-delivery-runtime__label">{{
3352
+ model().label
3353
+ }}</strong>
3354
+ </header>
3355
+ <p class="absolute-voice-delivery-runtime__description">
3356
+ {{ model().description }}
3357
+ </p>
3358
+ <ul class="absolute-voice-delivery-runtime__surfaces">
3359
+ @for (surface of model().surfaces; track surface.id) {
3360
+ <li
3361
+ class="absolute-voice-delivery-runtime__surface"
3362
+ [class.absolute-voice-delivery-runtime__surface--pass]="
3363
+ surface.status === 'pass'
3364
+ "
3365
+ [class.absolute-voice-delivery-runtime__surface--warn]="
3366
+ surface.status === 'warn'
3367
+ "
3368
+ [class.absolute-voice-delivery-runtime__surface--disabled]="
3369
+ surface.status === 'disabled'
3370
+ "
3371
+ >
3372
+ <span>{{ surface.label }}</span>
3373
+ <strong>{{ surface.detail }}</strong>
3374
+ <small
3375
+ >{{ surface.failed }} failed /
3376
+ {{ surface.deadLettered }} dead-lettered</small
3377
+ >
3378
+ </li>
3379
+ }
3380
+ </ul>
3381
+ <div class="absolute-voice-delivery-runtime__actions">
3382
+ <button
3383
+ type="button"
3384
+ [disabled]="model().actionStatus === 'running'"
3385
+ (click)="tick()"
3386
+ >
3387
+ {{ model().actionStatus === "running" ? "Working..." : "Tick workers" }}
3388
+ </button>
3389
+ <button
3390
+ type="button"
3391
+ [disabled]="
3392
+ model().actionStatus === 'running' || deadLettered() === 0
3393
+ "
3394
+ (click)="requeueDeadLetters()"
3395
+ >
3396
+ Requeue dead letters
3397
+ </button>
3398
+ </div>
3399
+ @if (model().actionError) {
3400
+ <p class="absolute-voice-delivery-runtime__error">
3401
+ {{ model().actionError }}
3402
+ </p>
3403
+ }
3404
+ @if (model().error) {
3405
+ <p class="absolute-voice-delivery-runtime__error">
3406
+ {{ model().error }}
3407
+ </p>
3408
+ }
3409
+ </section>
3410
+ `
3411
+ })
3412
+ ];
3413
+ var _dec2 = [
3414
+ Input()
3415
+ ];
3416
+ var _dec3 = [
3417
+ Input()
3418
+ ];
3419
+ var _dec4 = [
3420
+ Input()
3421
+ ];
3422
+ var _dec5 = [
3423
+ Input()
3424
+ ];
3425
+ var _init = __decoratorStart(undefined);
3426
+
3427
+ class VoiceDeliveryRuntimeComponent {
3428
+ constructor() {
3429
+ this.description = __runInitializers(_init, 8, this);
3430
+ __runInitializers(_init, 11, this);
3431
+ this.intervalMs = __runInitializers(_init, 12, this);
3432
+ __runInitializers(_init, 15, this);
3433
+ this.path = __runInitializers(_init, 16, this, "/api/voice-delivery-runtime");
3434
+ __runInitializers(_init, 19, this);
3435
+ this.title = __runInitializers(_init, 20, this);
3436
+ __runInitializers(_init, 23, this);
3437
+ }
3438
+ cleanup = () => {};
3439
+ store;
3440
+ model = signal15(createVoiceDeliveryRuntimeViewModel({
3441
+ actionError: null,
3442
+ actionStatus: "idle",
3443
+ error: null,
3444
+ isLoading: true
3445
+ }));
3446
+ ngOnInit() {
3447
+ const options = this.options();
3448
+ this.store = createVoiceDeliveryRuntimeStore(this.path, options);
3449
+ const sync = () => {
3450
+ this.model.set(createVoiceDeliveryRuntimeViewModel(this.store.getSnapshot(), options));
3451
+ };
3452
+ this.cleanup = this.store.subscribe(sync);
3453
+ sync();
3454
+ if (typeof window !== "undefined") {
3455
+ this.store.refresh().catch(() => {});
3456
+ }
3457
+ }
3458
+ ngOnDestroy() {
3459
+ this.cleanup();
3460
+ this.store?.close();
3461
+ }
3462
+ deadLettered() {
3463
+ return this.model().surfaces.reduce((total, surface) => total + surface.deadLettered, 0);
3464
+ }
3465
+ tick() {
3466
+ this.store?.tick().catch(() => {});
3467
+ }
3468
+ requeueDeadLetters() {
3469
+ this.store?.requeueDeadLetters().catch(() => {});
3470
+ }
3471
+ options() {
3472
+ return {
3473
+ description: this.description,
3474
+ intervalMs: this.intervalMs,
3475
+ title: this.title
3476
+ };
3477
+ }
3478
+ }
3479
+ __decorateElement(_init, 5, "description", _dec2, VoiceDeliveryRuntimeComponent);
3480
+ __decorateElement(_init, 5, "intervalMs", _dec3, VoiceDeliveryRuntimeComponent);
3481
+ __decorateElement(_init, 5, "path", _dec4, VoiceDeliveryRuntimeComponent);
3482
+ __decorateElement(_init, 5, "title", _dec5, VoiceDeliveryRuntimeComponent);
3483
+ VoiceDeliveryRuntimeComponent = __decorateElement(_init, 0, "VoiceDeliveryRuntimeComponent", _dec, VoiceDeliveryRuntimeComponent);
3484
+ __runInitializers(_init, 1, VoiceDeliveryRuntimeComponent);
3485
+ __decoratorMetadata(_init, VoiceDeliveryRuntimeComponent);
3486
+ let _VoiceDeliveryRuntimeComponent = VoiceDeliveryRuntimeComponent;
1414
3487
  export {
3488
+ VoiceWorkflowStatusService,
3489
+ VoiceTurnQualityService,
3490
+ VoiceTurnLatencyService,
3491
+ VoiceTraceTimelineService,
1415
3492
  VoiceStreamService,
3493
+ VoiceRoutingStatusService,
1416
3494
  VoiceProviderStatusService,
1417
- VoiceControllerService
3495
+ VoiceProviderContractsService,
3496
+ VoiceProviderCapabilitiesService,
3497
+ VoiceOpsStatusService,
3498
+ VoiceOpsActionCenterService,
3499
+ VoiceDeliveryRuntimeService,
3500
+ VoiceDeliveryRuntimeComponent,
3501
+ VoiceControllerService,
3502
+ VoiceCampaignDialerProofService
1418
3503
  };