@absolutejs/voice 0.0.22-beta.6 → 0.0.22-beta.61

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 (81) hide show
  1. package/README.md +205 -0
  2. package/dist/angular/index.d.ts +4 -0
  3. package/dist/angular/index.js +587 -43
  4. package/dist/angular/voice-app-kit-status.service.d.ts +12 -0
  5. package/dist/angular/voice-ops-status.component.d.ts +15 -0
  6. package/dist/angular/voice-provider-status.service.d.ts +12 -0
  7. package/dist/angular/voice-routing-status.service.d.ts +11 -0
  8. package/dist/angular/voice-stream.service.d.ts +2 -0
  9. package/dist/angular/voice-workflow-status.service.d.ts +12 -0
  10. package/dist/appKit.d.ts +92 -0
  11. package/dist/assistantHealth.d.ts +81 -0
  12. package/dist/client/actions.d.ts +22 -0
  13. package/dist/client/appKitStatus.d.ts +19 -0
  14. package/dist/client/connection.d.ts +3 -0
  15. package/dist/client/htmxBootstrap.js +44 -2
  16. package/dist/client/index.d.ts +18 -0
  17. package/dist/client/index.js +893 -2
  18. package/dist/client/opsStatusWidget.d.ts +40 -0
  19. package/dist/client/providerSimulationControls.d.ts +33 -0
  20. package/dist/client/providerSimulationControlsWidget.d.ts +20 -0
  21. package/dist/client/providerStatus.d.ts +19 -0
  22. package/dist/client/providerStatusWidget.d.ts +32 -0
  23. package/dist/client/routingStatus.d.ts +19 -0
  24. package/dist/client/routingStatusWidget.d.ts +28 -0
  25. package/dist/client/workflowStatus.d.ts +19 -0
  26. package/dist/diagnosticsRoutes.d.ts +44 -0
  27. package/dist/evalRoutes.d.ts +213 -0
  28. package/dist/handoff.d.ts +54 -0
  29. package/dist/handoffHealth.d.ts +94 -0
  30. package/dist/index.d.ts +32 -4
  31. package/dist/index.js +4222 -133
  32. package/dist/modelAdapters.d.ts +75 -0
  33. package/dist/opsConsoleRoutes.d.ts +77 -0
  34. package/dist/opsWebhook.d.ts +126 -0
  35. package/dist/providerAdapters.d.ts +48 -0
  36. package/dist/providerHealth.d.ts +79 -0
  37. package/dist/qualityRoutes.d.ts +76 -0
  38. package/dist/queue.d.ts +52 -0
  39. package/dist/react/VoiceOpsStatus.d.ts +6 -0
  40. package/dist/react/VoiceProviderSimulationControls.d.ts +5 -0
  41. package/dist/react/VoiceProviderStatus.d.ts +6 -0
  42. package/dist/react/VoiceRoutingStatus.d.ts +6 -0
  43. package/dist/react/index.d.ts +9 -0
  44. package/dist/react/index.js +1295 -11
  45. package/dist/react/useVoiceAppKitStatus.d.ts +8 -0
  46. package/dist/react/useVoiceController.d.ts +2 -0
  47. package/dist/react/useVoiceProviderSimulationControls.d.ts +10 -0
  48. package/dist/react/useVoiceProviderStatus.d.ts +8 -0
  49. package/dist/react/useVoiceRoutingStatus.d.ts +8 -0
  50. package/dist/react/useVoiceStream.d.ts +2 -0
  51. package/dist/react/useVoiceWorkflowStatus.d.ts +8 -0
  52. package/dist/resilienceRoutes.d.ts +117 -0
  53. package/dist/sessionReplay.d.ts +175 -0
  54. package/dist/svelte/createVoiceAppKitStatus.d.ts +8 -0
  55. package/dist/svelte/createVoiceOpsStatus.d.ts +9 -0
  56. package/dist/svelte/createVoiceProviderSimulationControls.d.ts +11 -0
  57. package/dist/svelte/createVoiceProviderStatus.d.ts +10 -0
  58. package/dist/svelte/createVoiceRoutingStatus.d.ts +10 -0
  59. package/dist/svelte/createVoiceWorkflowStatus.d.ts +8 -0
  60. package/dist/svelte/index.d.ts +6 -0
  61. package/dist/svelte/index.js +923 -3
  62. package/dist/testing/index.d.ts +2 -0
  63. package/dist/testing/index.js +1537 -7
  64. package/dist/testing/ioProviderSimulator.d.ts +41 -0
  65. package/dist/testing/providerSimulator.d.ts +44 -0
  66. package/dist/trace.d.ts +1 -1
  67. package/dist/types.d.ts +84 -2
  68. package/dist/vue/VoiceOpsStatus.d.ts +30 -0
  69. package/dist/vue/VoiceProviderSimulationControls.d.ts +88 -0
  70. package/dist/vue/VoiceProviderStatus.d.ts +51 -0
  71. package/dist/vue/VoiceRoutingStatus.d.ts +51 -0
  72. package/dist/vue/index.d.ts +9 -0
  73. package/dist/vue/index.js +1354 -25
  74. package/dist/vue/useVoiceAppKitStatus.d.ts +9 -0
  75. package/dist/vue/useVoiceProviderSimulationControls.d.ts +24 -0
  76. package/dist/vue/useVoiceProviderStatus.d.ts +9 -0
  77. package/dist/vue/useVoiceRoutingStatus.d.ts +8 -0
  78. package/dist/vue/useVoiceStream.d.ts +2 -0
  79. package/dist/vue/useVoiceWorkflowStatus.d.ts +9 -0
  80. package/dist/workflowContract.d.ts +91 -0
  81. package/package.json +1 -1
@@ -69,6 +69,410 @@ 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/client/appKitStatus.ts
73
+ var fetchVoiceAppKitStatus = async (path = "/app-kit/status", options = {}) => {
74
+ const fetchImpl = options.fetch ?? globalThis.fetch;
75
+ const response = await fetchImpl(path);
76
+ if (!response.ok) {
77
+ throw new Error(`Voice app kit status failed: HTTP ${response.status}`);
78
+ }
79
+ return await response.json();
80
+ };
81
+ var createVoiceAppKitStatusStore = (path = "/app-kit/status", options = {}) => {
82
+ const listeners = new Set;
83
+ let closed = false;
84
+ let timer;
85
+ let snapshot = {
86
+ error: null,
87
+ isLoading: false
88
+ };
89
+ const emit = () => {
90
+ for (const listener of listeners) {
91
+ listener();
92
+ }
93
+ };
94
+ const refresh = async () => {
95
+ if (closed) {
96
+ return snapshot.report;
97
+ }
98
+ snapshot = {
99
+ ...snapshot,
100
+ error: null,
101
+ isLoading: true
102
+ };
103
+ emit();
104
+ try {
105
+ const report = await fetchVoiceAppKitStatus(path, options);
106
+ snapshot = {
107
+ error: null,
108
+ isLoading: false,
109
+ report,
110
+ updatedAt: Date.now()
111
+ };
112
+ emit();
113
+ return report;
114
+ } catch (error) {
115
+ snapshot = {
116
+ ...snapshot,
117
+ error: error instanceof Error ? error.message : String(error),
118
+ isLoading: false
119
+ };
120
+ emit();
121
+ throw error;
122
+ }
123
+ };
124
+ const close = () => {
125
+ closed = true;
126
+ if (timer) {
127
+ clearInterval(timer);
128
+ timer = undefined;
129
+ }
130
+ listeners.clear();
131
+ };
132
+ if (typeof window !== "undefined" && options.intervalMs && options.intervalMs > 0) {
133
+ timer = setInterval(() => {
134
+ refresh().catch(() => {});
135
+ }, options.intervalMs);
136
+ }
137
+ return {
138
+ close,
139
+ getServerSnapshot: () => snapshot,
140
+ getSnapshot: () => snapshot,
141
+ refresh,
142
+ subscribe: (listener) => {
143
+ listeners.add(listener);
144
+ return () => {
145
+ listeners.delete(listener);
146
+ };
147
+ }
148
+ };
149
+ };
150
+
151
+ // src/svelte/createVoiceAppKitStatus.ts
152
+ var createVoiceAppKitStatus = (path = "/app-kit/status", options = {}) => createVoiceAppKitStatusStore(path, options);
153
+ // src/client/opsStatusWidget.ts
154
+ var DEFAULT_TITLE = "Voice Ops Status";
155
+ var DEFAULT_DESCRIPTION = "Certified workflow, provider, and handoff readiness from the AbsoluteJS voice app kit.";
156
+ var SURFACE_LABELS = {
157
+ handoffs: "Handoffs",
158
+ providers: "Providers",
159
+ quality: "Quality",
160
+ sessions: "Sessions",
161
+ workflows: "Workflows"
162
+ };
163
+ var escapeHtml = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
164
+ var readNumber = (value, key) => value && typeof value === "object" && (key in value) ? Number(value[key] ?? 0) : 0;
165
+ var surfaceDetail = (surface) => {
166
+ const total = readNumber(surface, "total");
167
+ const failed = readNumber(surface, "failed");
168
+ const degraded = readNumber(surface, "degraded");
169
+ const source = surface && typeof surface === "object" && "source" in surface && typeof surface.source === "string" ? ` from ${surface.source}` : "";
170
+ if (degraded > 0) {
171
+ return `${degraded} degraded of ${total}`;
172
+ }
173
+ if (failed > 0) {
174
+ return `${failed} failing of ${total}${source}`;
175
+ }
176
+ return total > 0 ? `${total} passing${source}` : `No failures${source}`;
177
+ };
178
+ var getVoiceOpsStatusLabel = (report, error) => {
179
+ if (error) {
180
+ return "Unavailable";
181
+ }
182
+ if (!report) {
183
+ return "Checking";
184
+ }
185
+ return report.status === "pass" ? "Passing" : "Needs attention";
186
+ };
187
+ var createVoiceOpsStatusViewModel = (snapshot, options = {}) => {
188
+ const report = snapshot.report;
189
+ const surfaces = Object.entries(report?.surfaces ?? {}).map(([id, surface]) => {
190
+ const failed = readNumber(surface, "failed") || readNumber(surface, "degraded");
191
+ const total = readNumber(surface, "total");
192
+ const status = surface && typeof surface === "object" && "status" in surface ? surface.status ?? "pass" : "pass";
193
+ return {
194
+ detail: surfaceDetail(surface),
195
+ failed,
196
+ id,
197
+ label: SURFACE_LABELS[id] ?? id,
198
+ status,
199
+ total
200
+ };
201
+ });
202
+ return {
203
+ description: options.description ?? DEFAULT_DESCRIPTION,
204
+ error: snapshot.error,
205
+ isLoading: snapshot.isLoading,
206
+ label: getVoiceOpsStatusLabel(report, snapshot.error),
207
+ links: options.includeLinks === false ? [] : report?.links ?? [],
208
+ passed: report?.passed ?? 0,
209
+ status: snapshot.error ? "error" : report ? report.status : snapshot.isLoading ? "loading" : "loading",
210
+ surfaces,
211
+ title: options.title ?? DEFAULT_TITLE,
212
+ total: report?.total ?? 0,
213
+ updatedAt: snapshot.updatedAt
214
+ };
215
+ };
216
+ var renderVoiceOpsStatusHTML = (snapshot, options = {}) => {
217
+ const model = createVoiceOpsStatusViewModel(snapshot, options);
218
+ const surfaces = model.surfaces.length ? model.surfaces.map((surface) => `<li class="absolute-voice-ops-status__surface absolute-voice-ops-status__surface--${escapeHtml(surface.status)}">
219
+ <span>${escapeHtml(surface.label)}</span>
220
+ <strong>${escapeHtml(surface.detail)}</strong>
221
+ </li>`).join("") : '<li class="absolute-voice-ops-status__surface"><span>Status</span><strong>Waiting for first check</strong></li>';
222
+ const links = model.links.length ? `<nav class="absolute-voice-ops-status__links">${model.links.slice(0, 4).map((link) => `<a href="${escapeHtml(link.href)}">${escapeHtml(link.label)}</a>`).join("")}</nav>` : "";
223
+ return `<section class="absolute-voice-ops-status absolute-voice-ops-status--${escapeHtml(model.status)}">
224
+ <header class="absolute-voice-ops-status__header">
225
+ <span class="absolute-voice-ops-status__eyebrow">${escapeHtml(model.title)}</span>
226
+ <strong class="absolute-voice-ops-status__label">${escapeHtml(model.label)}</strong>
227
+ </header>
228
+ <p class="absolute-voice-ops-status__description">${escapeHtml(model.description)}</p>
229
+ <div class="absolute-voice-ops-status__summary">
230
+ <span>${model.passed} passing</span>
231
+ <span>${Math.max(model.total - model.passed, 0)} failing</span>
232
+ <span>${model.total} checks</span>
233
+ </div>
234
+ <ul class="absolute-voice-ops-status__surfaces">${surfaces}</ul>
235
+ ${model.error ? `<p class="absolute-voice-ops-status__error">${escapeHtml(model.error)}</p>` : ""}
236
+ ${links}
237
+ </section>`;
238
+ };
239
+ var getVoiceOpsStatusCSS = () => `.absolute-voice-ops-status{border:1px solid #d8d2c4;border-radius:20px;background:#fffaf0;color:#16130d;padding:18px;box-shadow:0 18px 40px rgba(47,37,18,.12);font-family:inherit}.absolute-voice-ops-status--fail,.absolute-voice-ops-status--error{border-color:#f2a7a7;background:#fff5f3}.absolute-voice-ops-status__header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-ops-status__eyebrow{color:#73664f;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-ops-status__label{font-size:28px;line-height:1}.absolute-voice-ops-status__description{color:#514733;margin:12px 0 0}.absolute-voice-ops-status__summary,.absolute-voice-ops-status__links{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px}.absolute-voice-ops-status__summary span,.absolute-voice-ops-status__links a{border:1px solid #e6ddca;border-radius:999px;color:inherit;padding:6px 10px;text-decoration:none}.absolute-voice-ops-status__surfaces{display:grid;gap:8px;list-style:none;margin:16px 0 0;padding:0}.absolute-voice-ops-status__surface{align-items:center;background:#fff;border:1px solid #eee4d2;border-radius:14px;display:flex;gap:12px;justify-content:space-between;padding:10px 12px}.absolute-voice-ops-status__surface--fail{border-color:#f2a7a7}.absolute-voice-ops-status__surface span{color:#655944}.absolute-voice-ops-status__error{color:#9f1239;font-weight:700}`;
240
+ var mountVoiceOpsStatus = (element, path = "/app-kit/status", options = {}) => {
241
+ const store = createVoiceAppKitStatusStore(path, options);
242
+ const render = () => {
243
+ element.innerHTML = renderVoiceOpsStatusHTML(store.getSnapshot(), options);
244
+ };
245
+ const unsubscribe = store.subscribe(render);
246
+ render();
247
+ store.refresh().catch(() => {});
248
+ return {
249
+ close: () => {
250
+ unsubscribe();
251
+ store.close();
252
+ },
253
+ refresh: store.refresh
254
+ };
255
+ };
256
+ var defineVoiceOpsStatusElement = (tagName = "absolute-voice-ops-status") => {
257
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
258
+ return;
259
+ }
260
+ customElements.define(tagName, class AbsoluteVoiceOpsStatusElement extends HTMLElement {
261
+ mounted;
262
+ connectedCallback() {
263
+ const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
264
+ this.mounted = mountVoiceOpsStatus(this, this.getAttribute("path") ?? "/app-kit/status", {
265
+ description: this.getAttribute("description") ?? undefined,
266
+ includeLinks: this.getAttribute("include-links") !== "false",
267
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
268
+ title: this.getAttribute("title") ?? undefined
269
+ });
270
+ }
271
+ disconnectedCallback() {
272
+ this.mounted?.close();
273
+ this.mounted = undefined;
274
+ }
275
+ });
276
+ };
277
+
278
+ // src/svelte/createVoiceOpsStatus.ts
279
+ var createVoiceOpsStatus = (path = "/app-kit/status", options = {}) => {
280
+ const store = createVoiceAppKitStatusStore(path, options);
281
+ return {
282
+ close: store.close,
283
+ getHTML: () => renderVoiceOpsStatusHTML(store.getSnapshot(), options),
284
+ getSnapshot: store.getSnapshot,
285
+ getViewModel: () => createVoiceOpsStatusViewModel(store.getSnapshot(), options),
286
+ refresh: store.refresh,
287
+ subscribe: store.subscribe
288
+ };
289
+ };
290
+ // src/client/providerSimulationControls.ts
291
+ var postSimulation = async (pathPrefix, mode, provider, fetchImpl) => {
292
+ const response = await fetchImpl(`${pathPrefix}/${mode}?provider=${encodeURIComponent(provider)}`, { method: "POST" });
293
+ const body = await response.json().catch(() => null);
294
+ if (!response.ok) {
295
+ const message = body && typeof body === "object" && "error" in body ? String(body.error) : `Voice provider simulation failed: HTTP ${response.status}`;
296
+ throw new Error(message);
297
+ }
298
+ return body;
299
+ };
300
+ var createVoiceProviderSimulationControlsStore = (options) => {
301
+ const listeners = new Set;
302
+ const fetchImpl = options.fetch ?? globalThis.fetch;
303
+ const pathPrefix = options.pathPrefix ?? `/api/${options.kind ?? "stt"}-simulate`;
304
+ let closed = false;
305
+ let snapshot = {
306
+ error: null,
307
+ isRunning: false,
308
+ lastResult: null,
309
+ mode: null,
310
+ provider: null
311
+ };
312
+ const emit = () => {
313
+ for (const listener of listeners) {
314
+ listener();
315
+ }
316
+ };
317
+ const run = async (provider, mode) => {
318
+ if (closed) {
319
+ return snapshot.lastResult;
320
+ }
321
+ snapshot = {
322
+ ...snapshot,
323
+ error: null,
324
+ isRunning: true,
325
+ mode,
326
+ provider
327
+ };
328
+ emit();
329
+ try {
330
+ const result = await postSimulation(pathPrefix, mode, provider, fetchImpl);
331
+ snapshot = {
332
+ error: null,
333
+ isRunning: false,
334
+ lastResult: result,
335
+ mode,
336
+ provider,
337
+ updatedAt: Date.now()
338
+ };
339
+ emit();
340
+ return result;
341
+ } catch (error) {
342
+ snapshot = {
343
+ ...snapshot,
344
+ error: error instanceof Error ? error.message : String(error),
345
+ isRunning: false
346
+ };
347
+ emit();
348
+ throw error;
349
+ }
350
+ };
351
+ const close = () => {
352
+ closed = true;
353
+ listeners.clear();
354
+ };
355
+ return {
356
+ close,
357
+ getServerSnapshot: () => snapshot,
358
+ getSnapshot: () => snapshot,
359
+ run,
360
+ subscribe: (listener) => {
361
+ listeners.add(listener);
362
+ return () => {
363
+ listeners.delete(listener);
364
+ };
365
+ }
366
+ };
367
+ };
368
+
369
+ // src/client/providerSimulationControlsWidget.ts
370
+ var escapeHtml2 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
371
+ var formatKind = (kind) => (kind ?? "stt").toUpperCase();
372
+ var createVoiceProviderSimulationControlsViewModel = (snapshot, options) => {
373
+ const configuredProviders = options.providers.filter((provider) => provider.configured !== false);
374
+ const fallbackReady = !options.fallbackRequiredProvider || configuredProviders.some((entry) => entry.provider === options.fallbackRequiredProvider);
375
+ const failureProviders = (options.failureProviders ? options.failureProviders.map((provider) => ({ provider })) : configuredProviders).filter((provider) => configuredProviders.some((entry) => entry.provider === provider.provider));
376
+ return {
377
+ canSimulateFailure: configuredProviders.length > 0 && fallbackReady,
378
+ description: options.failureMessage ?? `Simulate ${formatKind(options.kind)} provider failure and recovery without changing credentials.`,
379
+ error: snapshot.error,
380
+ failureProviders,
381
+ isRunning: snapshot.isRunning,
382
+ label: snapshot.isRunning ? `Running ${snapshot.mode ?? "simulation"}` : snapshot.lastResult ? `${snapshot.lastResult.provider} ${snapshot.lastResult.mode} simulated` : configuredProviders.length ? `${configuredProviders.length} configured` : "No configured providers",
383
+ providers: configuredProviders,
384
+ resultText: snapshot.lastResult ? JSON.stringify(snapshot.lastResult, null, 2) : null,
385
+ title: options.title ?? `${formatKind(options.kind)} Failure Simulation`
386
+ };
387
+ };
388
+ var renderVoiceProviderSimulationControlsHTML = (snapshot, options) => {
389
+ const model = createVoiceProviderSimulationControlsViewModel(snapshot, options);
390
+ const failureButtons = model.failureProviders.map((provider) => `<button type="button" data-voice-provider-fail="${escapeHtml2(provider.provider)}"${!model.canSimulateFailure || snapshot.isRunning ? " disabled" : ""}>Simulate ${escapeHtml2(provider.provider)} ${escapeHtml2(formatKind(options.kind))} failure</button>`).join("");
391
+ const recoveryButtons = model.providers.map((provider) => `<button type="button" data-voice-provider-recover="${escapeHtml2(provider.provider)}"${snapshot.isRunning ? " disabled" : ""}>Mark ${escapeHtml2(provider.provider)} recovered</button>`).join("");
392
+ return `<section class="absolute-voice-provider-simulation absolute-voice-provider-simulation--${snapshot.error ? "error" : snapshot.isRunning ? "running" : "ready"}">
393
+ <header class="absolute-voice-provider-simulation__header">
394
+ <span class="absolute-voice-provider-simulation__eyebrow">${escapeHtml2(model.title)}</span>
395
+ <strong class="absolute-voice-provider-simulation__label">${escapeHtml2(model.label)}</strong>
396
+ </header>
397
+ <p class="absolute-voice-provider-simulation__description">${escapeHtml2(model.description)}</p>
398
+ ${model.canSimulateFailure ? "" : `<p class="absolute-voice-provider-simulation__empty">${escapeHtml2(options.fallbackRequiredMessage ?? "Configure fallback providers before simulating failure.")}</p>`}
399
+ <div class="absolute-voice-provider-simulation__actions">${failureButtons}${recoveryButtons}</div>
400
+ ${snapshot.error ? `<p class="absolute-voice-provider-simulation__error">${escapeHtml2(snapshot.error)}</p>` : ""}
401
+ ${model.resultText ? `<pre class="absolute-voice-provider-simulation__result">${escapeHtml2(model.resultText)}</pre>` : ""}
402
+ </section>`;
403
+ };
404
+ var bindVoiceProviderSimulationControls = (element, store) => {
405
+ const onClick = (event) => {
406
+ const target = event.target;
407
+ if (!(target instanceof HTMLElement)) {
408
+ return;
409
+ }
410
+ const failProvider = target.getAttribute("data-voice-provider-fail");
411
+ const recoverProvider = target.getAttribute("data-voice-provider-recover");
412
+ if (failProvider) {
413
+ store.run(failProvider, "failure").catch(() => {});
414
+ }
415
+ if (recoverProvider) {
416
+ store.run(recoverProvider, "recovery").catch(() => {});
417
+ }
418
+ };
419
+ element.addEventListener("click", onClick);
420
+ return () => element.removeEventListener("click", onClick);
421
+ };
422
+ var mountVoiceProviderSimulationControls = (element, options) => {
423
+ const store = createVoiceProviderSimulationControlsStore(options);
424
+ const render = () => {
425
+ element.innerHTML = renderVoiceProviderSimulationControlsHTML(store.getSnapshot(), options);
426
+ };
427
+ const unsubscribeStore = store.subscribe(render);
428
+ const unsubscribeDom = bindVoiceProviderSimulationControls(element, store);
429
+ render();
430
+ return {
431
+ close: () => {
432
+ unsubscribeDom();
433
+ unsubscribeStore();
434
+ store.close();
435
+ },
436
+ run: store.run
437
+ };
438
+ };
439
+ var defineVoiceProviderSimulationControlsElement = (tagName = "absolute-voice-provider-simulation") => {
440
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
441
+ return;
442
+ }
443
+ customElements.define(tagName, class AbsoluteVoiceProviderSimulationElement extends HTMLElement {
444
+ mounted;
445
+ connectedCallback() {
446
+ const providers = (this.getAttribute("providers") ?? "").split(",").map((provider) => provider.trim()).filter(Boolean).map((provider) => ({ provider }));
447
+ const failureProviders = (this.getAttribute("failure-providers") ?? "").split(",").map((provider) => provider.trim()).filter(Boolean);
448
+ this.mounted = mountVoiceProviderSimulationControls(this, {
449
+ failureProviders: failureProviders.length ? failureProviders : undefined,
450
+ fallbackRequiredMessage: this.getAttribute("fallback-required-message") ?? undefined,
451
+ fallbackRequiredProvider: this.getAttribute("fallback-required-provider") ?? undefined,
452
+ failureMessage: this.getAttribute("failure-message") ?? undefined,
453
+ kind: this.getAttribute("kind") ?? "stt",
454
+ pathPrefix: this.getAttribute("path-prefix") ?? undefined,
455
+ providers,
456
+ title: this.getAttribute("title") ?? undefined
457
+ });
458
+ }
459
+ disconnectedCallback() {
460
+ this.mounted?.close();
461
+ this.mounted = undefined;
462
+ }
463
+ });
464
+ };
465
+
466
+ // src/svelte/createVoiceProviderSimulationControls.ts
467
+ var createVoiceProviderSimulationControls = (options) => {
468
+ const store = createVoiceProviderSimulationControlsStore(options);
469
+ return {
470
+ ...store,
471
+ bind: (element) => bindVoiceProviderSimulationControls(element, store),
472
+ getHTML: () => renderVoiceProviderSimulationControlsHTML(store.getSnapshot(), options),
473
+ getViewModel: () => createVoiceProviderSimulationControlsViewModel(store.getSnapshot(), options)
474
+ };
475
+ };
72
476
  // src/client/actions.ts
73
477
  var normalizeErrorMessage = (value) => {
74
478
  if (typeof value === "string" && value.trim()) {
@@ -117,6 +521,12 @@ var serverMessageToAction = (message) => {
117
521
  sessionId: message.sessionId,
118
522
  type: "complete"
119
523
  };
524
+ case "call_lifecycle":
525
+ return {
526
+ event: message.event,
527
+ sessionId: message.sessionId,
528
+ type: "call_lifecycle"
529
+ };
120
530
  case "error":
121
531
  return {
122
532
  message: normalizeErrorMessage(message.message),
@@ -160,7 +570,7 @@ var DEFAULT_SCENARIO_QUERY_PARAM = "scenarioId";
160
570
  var noop = () => {};
161
571
  var noopUnsubscribe = () => noop;
162
572
  var NOOP_CONNECTION = {
163
- start: () => {},
573
+ callControl: noop,
164
574
  close: noop,
165
575
  endTurn: noop,
166
576
  getReadyState: () => WS_CLOSED,
@@ -168,6 +578,7 @@ var NOOP_CONNECTION = {
168
578
  getSessionId: () => "",
169
579
  send: noop,
170
580
  sendAudio: noop,
581
+ start: () => {},
171
582
  subscribe: noopUnsubscribe
172
583
  };
173
584
  var createSessionId = () => crypto.randomUUID();
@@ -189,6 +600,7 @@ var isVoiceServerMessage = (value) => {
189
600
  switch (value.type) {
190
601
  case "audio":
191
602
  case "assistant":
603
+ case "call_lifecycle":
192
604
  case "complete":
193
605
  case "error":
194
606
  case "final":
@@ -329,6 +741,12 @@ var createVoiceConnection = (path, options = {}) => {
329
741
  const endTurn = () => {
330
742
  send({ type: "end_turn" });
331
743
  };
744
+ const callControl = (message) => {
745
+ send({
746
+ ...message,
747
+ type: "call_control"
748
+ });
749
+ };
332
750
  const close = () => {
333
751
  clearTimers();
334
752
  if (state.ws) {
@@ -346,7 +764,7 @@ var createVoiceConnection = (path, options = {}) => {
346
764
  };
347
765
  connect();
348
766
  return {
349
- start,
767
+ callControl,
350
768
  close,
351
769
  endTurn,
352
770
  getReadyState: () => state.ws?.readyState ?? WS_CLOSED,
@@ -354,6 +772,7 @@ var createVoiceConnection = (path, options = {}) => {
354
772
  getSessionId: () => state.sessionId,
355
773
  send,
356
774
  sendAudio,
775
+ start,
357
776
  subscribe
358
777
  };
359
778
  };
@@ -362,6 +781,7 @@ var createVoiceConnection = (path, options = {}) => {
362
781
  var createInitialState = () => ({
363
782
  assistantAudio: [],
364
783
  assistantTexts: [],
784
+ call: null,
365
785
  error: null,
366
786
  isConnected: false,
367
787
  scenarioId: null,
@@ -405,6 +825,20 @@ var createVoiceStreamStore = () => {
405
825
  status: "completed"
406
826
  };
407
827
  break;
828
+ case "call_lifecycle":
829
+ state = {
830
+ ...state,
831
+ call: {
832
+ ...state.call,
833
+ disposition: action.event.type === "end" ? action.event.disposition : state.call?.disposition,
834
+ endedAt: action.event.type === "end" ? action.event.at : state.call?.endedAt,
835
+ events: [...state.call?.events ?? [], action.event],
836
+ lastEventAt: action.event.at,
837
+ startedAt: state.call?.startedAt ?? action.event.at
838
+ },
839
+ sessionId: action.sessionId
840
+ };
841
+ break;
408
842
  case "connected":
409
843
  state = {
410
844
  ...state,
@@ -491,6 +925,9 @@ var createVoiceStream = (path, options = {}) => {
491
925
  }
492
926
  });
493
927
  return {
928
+ callControl(message) {
929
+ connection.callControl(message);
930
+ },
494
931
  close() {
495
932
  unsubscribeConnection();
496
933
  connection.close();
@@ -534,6 +971,9 @@ var createVoiceStream = (path, options = {}) => {
534
971
  get assistantAudio() {
535
972
  return store.getSnapshot().assistantAudio;
536
973
  },
974
+ get call() {
975
+ return store.getSnapshot().call;
976
+ },
537
977
  sendAudio(audio) {
538
978
  connection.sendAudio(audio);
539
979
  },
@@ -548,6 +988,474 @@ var createVoiceStream = (path, options = {}) => {
548
988
 
549
989
  // src/svelte/createVoiceStream.ts
550
990
  var createVoiceStream2 = (path, options = {}) => createVoiceStream(path, options);
991
+ // src/client/providerStatus.ts
992
+ var fetchVoiceProviderStatus = async (path = "/api/provider-status", options = {}) => {
993
+ const fetchImpl = options.fetch ?? globalThis.fetch;
994
+ const response = await fetchImpl(path);
995
+ if (!response.ok) {
996
+ throw new Error(`Voice provider status failed: HTTP ${response.status}`);
997
+ }
998
+ return await response.json();
999
+ };
1000
+ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {}) => {
1001
+ const listeners = new Set;
1002
+ let closed = false;
1003
+ let timer;
1004
+ let snapshot = {
1005
+ error: null,
1006
+ isLoading: false,
1007
+ providers: []
1008
+ };
1009
+ const emit = () => {
1010
+ for (const listener of listeners) {
1011
+ listener();
1012
+ }
1013
+ };
1014
+ const refresh = async () => {
1015
+ if (closed) {
1016
+ return snapshot.providers;
1017
+ }
1018
+ snapshot = {
1019
+ ...snapshot,
1020
+ error: null,
1021
+ isLoading: true
1022
+ };
1023
+ emit();
1024
+ try {
1025
+ const providers = await fetchVoiceProviderStatus(path, options);
1026
+ snapshot = {
1027
+ error: null,
1028
+ isLoading: false,
1029
+ providers,
1030
+ updatedAt: Date.now()
1031
+ };
1032
+ emit();
1033
+ return providers;
1034
+ } catch (error) {
1035
+ snapshot = {
1036
+ ...snapshot,
1037
+ error: error instanceof Error ? error.message : String(error),
1038
+ isLoading: false
1039
+ };
1040
+ emit();
1041
+ throw error;
1042
+ }
1043
+ };
1044
+ const close = () => {
1045
+ closed = true;
1046
+ if (timer) {
1047
+ clearInterval(timer);
1048
+ timer = undefined;
1049
+ }
1050
+ listeners.clear();
1051
+ };
1052
+ if (options.intervalMs && options.intervalMs > 0) {
1053
+ timer = setInterval(() => {
1054
+ refresh().catch(() => {});
1055
+ }, options.intervalMs);
1056
+ }
1057
+ return {
1058
+ close,
1059
+ getServerSnapshot: () => snapshot,
1060
+ getSnapshot: () => snapshot,
1061
+ refresh,
1062
+ subscribe: (listener) => {
1063
+ listeners.add(listener);
1064
+ return () => {
1065
+ listeners.delete(listener);
1066
+ };
1067
+ }
1068
+ };
1069
+ };
1070
+
1071
+ // src/client/providerStatusWidget.ts
1072
+ var DEFAULT_TITLE2 = "Voice Providers";
1073
+ var DEFAULT_DESCRIPTION2 = "Live provider health, fallback counts, latency, and suppression state from your self-hosted trace store.";
1074
+ var escapeHtml3 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
1075
+ var formatProvider = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
1076
+ var formatStatus = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
1077
+ var formatLatency = (value) => typeof value === "number" ? `${value}ms` : "No samples";
1078
+ var formatSuppression = (value) => typeof value === "number" ? `${Math.ceil(value / 1000)}s` : "None";
1079
+ var getProviderDetail = (provider) => {
1080
+ if (provider.status === "suppressed") {
1081
+ return provider.lastError ? `Suppressed for ${formatSuppression(provider.suppressionRemainingMs)} after ${provider.lastError}.` : `Suppressed for ${formatSuppression(provider.suppressionRemainingMs)}.`;
1082
+ }
1083
+ if (provider.status === "recoverable") {
1084
+ return "Cooldown expired; ready for recovery traffic.";
1085
+ }
1086
+ if (provider.status === "rate-limited") {
1087
+ return "Rate limit detected; router should avoid this provider.";
1088
+ }
1089
+ if (provider.status === "degraded") {
1090
+ return provider.lastError ?? "Recent provider errors detected.";
1091
+ }
1092
+ if (provider.status === "healthy") {
1093
+ return provider.recommended ? "Healthy and currently recommended." : "Healthy and available for routing.";
1094
+ }
1095
+ return "No provider traffic observed yet.";
1096
+ };
1097
+ var isWarningStatus = (status) => status === "degraded" || status === "rate-limited" || status === "recoverable" || status === "suppressed";
1098
+ var createVoiceProviderStatusViewModel = (snapshot, options = {}) => {
1099
+ const providers = snapshot.providers.map((provider) => ({
1100
+ ...provider,
1101
+ detail: getProviderDetail(provider),
1102
+ label: `${formatProvider(provider.provider)}${provider.recommended ? " recommended" : ""}`,
1103
+ rows: [
1104
+ { label: "Runs", value: String(provider.runCount) },
1105
+ { label: "Avg latency", value: formatLatency(provider.averageElapsedMs) },
1106
+ { label: "Errors", value: String(provider.errorCount) },
1107
+ { label: "Timeouts", value: String(provider.timeoutCount) },
1108
+ { label: "Fallbacks", value: String(provider.fallbackCount) },
1109
+ {
1110
+ label: "Suppression",
1111
+ value: formatSuppression(provider.suppressionRemainingMs)
1112
+ }
1113
+ ]
1114
+ }));
1115
+ const warningCount = providers.filter((provider) => isWarningStatus(provider.status)).length;
1116
+ const healthyCount = providers.filter((provider) => provider.status === "healthy").length;
1117
+ return {
1118
+ description: options.description ?? DEFAULT_DESCRIPTION2,
1119
+ error: snapshot.error,
1120
+ isLoading: snapshot.isLoading,
1121
+ label: snapshot.error ? "Unavailable" : providers.length ? warningCount > 0 ? `${warningCount} needs attention` : `${healthyCount} healthy` : snapshot.isLoading ? "Checking" : "No provider traffic",
1122
+ providers,
1123
+ status: snapshot.error ? "error" : providers.length ? warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
1124
+ title: options.title ?? DEFAULT_TITLE2,
1125
+ updatedAt: snapshot.updatedAt
1126
+ };
1127
+ };
1128
+ var renderVoiceProviderStatusHTML = (snapshot, options = {}) => {
1129
+ const model = createVoiceProviderStatusViewModel(snapshot, options);
1130
+ const providers = model.providers.length ? `<div class="absolute-voice-provider-status__providers">${model.providers.map((provider) => `<article class="absolute-voice-provider-status__provider absolute-voice-provider-status__provider--${escapeHtml3(provider.status)}">
1131
+ <header>
1132
+ <strong>${escapeHtml3(provider.label)}</strong>
1133
+ <span>${escapeHtml3(formatStatus(provider.status))}</span>
1134
+ </header>
1135
+ <p>${escapeHtml3(provider.detail)}</p>
1136
+ <dl>${provider.rows.map((row) => `<div>
1137
+ <dt>${escapeHtml3(row.label)}</dt>
1138
+ <dd>${escapeHtml3(row.value)}</dd>
1139
+ </div>`).join("")}</dl>
1140
+ </article>`).join("")}</div>` : '<p class="absolute-voice-provider-status__empty">Run voice traffic to see provider health.</p>';
1141
+ return `<section class="absolute-voice-provider-status absolute-voice-provider-status--${escapeHtml3(model.status)}">
1142
+ <header class="absolute-voice-provider-status__header">
1143
+ <span class="absolute-voice-provider-status__eyebrow">${escapeHtml3(model.title)}</span>
1144
+ <strong class="absolute-voice-provider-status__label">${escapeHtml3(model.label)}</strong>
1145
+ </header>
1146
+ <p class="absolute-voice-provider-status__description">${escapeHtml3(model.description)}</p>
1147
+ ${providers}
1148
+ ${model.error ? `<p class="absolute-voice-provider-status__error">${escapeHtml3(model.error)}</p>` : ""}
1149
+ </section>`;
1150
+ };
1151
+ var getVoiceProviderStatusCSS = () => `.absolute-voice-provider-status{border:1px solid #d8d2c4;border-radius:20px;background:#fffaf0;color:#16130d;padding:18px;box-shadow:0 18px 40px rgba(47,37,18,.12);font-family:inherit}.absolute-voice-provider-status--error,.absolute-voice-provider-status--warning{border-color:#f2a7a7;background:#fff5f3}.absolute-voice-provider-status__header,.absolute-voice-provider-status__provider header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-provider-status__eyebrow{color:#73664f;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-provider-status__label{font-size:24px;line-height:1}.absolute-voice-provider-status__description,.absolute-voice-provider-status__provider p,.absolute-voice-provider-status__provider dt,.absolute-voice-provider-status__empty{color:#514733}.absolute-voice-provider-status__providers{display:grid;gap:12px;margin-top:14px}.absolute-voice-provider-status__provider{background:#fff;border:1px solid #eee4d2;border-radius:16px;padding:14px}.absolute-voice-provider-status__provider--degraded,.absolute-voice-provider-status__provider--rate-limited,.absolute-voice-provider-status__provider--suppressed{border-color:#f2a7a7}.absolute-voice-provider-status__provider--recoverable{border-color:#fbbf24}.absolute-voice-provider-status__provider p{margin:10px 0}.absolute-voice-provider-status__provider dl{display:grid;gap:8px;grid-template-columns:repeat(2,minmax(0,1fr));margin:0}.absolute-voice-provider-status__provider div{background:#fffaf0;border:1px solid #eee4d2;border-radius:12px;padding:8px}.absolute-voice-provider-status__provider dt{font-size:12px}.absolute-voice-provider-status__provider dd{font-weight:800;margin:4px 0 0}.absolute-voice-provider-status__empty{margin:14px 0 0}.absolute-voice-provider-status__error{color:#9f1239;font-weight:700}`;
1152
+ var mountVoiceProviderStatus = (element, path = "/api/provider-status", options = {}) => {
1153
+ const store = createVoiceProviderStatusStore(path, options);
1154
+ const render = () => {
1155
+ element.innerHTML = renderVoiceProviderStatusHTML(store.getSnapshot(), options);
1156
+ };
1157
+ const unsubscribe = store.subscribe(render);
1158
+ render();
1159
+ store.refresh().catch(() => {});
1160
+ return {
1161
+ close: () => {
1162
+ unsubscribe();
1163
+ store.close();
1164
+ },
1165
+ refresh: store.refresh
1166
+ };
1167
+ };
1168
+ var defineVoiceProviderStatusElement = (tagName = "absolute-voice-provider-status") => {
1169
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
1170
+ return;
1171
+ }
1172
+ customElements.define(tagName, class AbsoluteVoiceProviderStatusElement extends HTMLElement {
1173
+ mounted;
1174
+ connectedCallback() {
1175
+ const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
1176
+ this.mounted = mountVoiceProviderStatus(this, this.getAttribute("path") ?? "/api/provider-status", {
1177
+ description: this.getAttribute("description") ?? undefined,
1178
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
1179
+ title: this.getAttribute("title") ?? undefined
1180
+ });
1181
+ }
1182
+ disconnectedCallback() {
1183
+ this.mounted?.close();
1184
+ this.mounted = undefined;
1185
+ }
1186
+ });
1187
+ };
1188
+
1189
+ // src/svelte/createVoiceProviderStatus.ts
1190
+ var createVoiceProviderStatus = (path = "/api/provider-status", options = {}) => {
1191
+ const store = createVoiceProviderStatusStore(path, options);
1192
+ return {
1193
+ ...store,
1194
+ getHTML: () => renderVoiceProviderStatusHTML(store.getSnapshot(), options),
1195
+ getViewModel: () => createVoiceProviderStatusViewModel(store.getSnapshot(), options)
1196
+ };
1197
+ };
1198
+ // src/client/routingStatus.ts
1199
+ var fetchVoiceRoutingStatus = async (path = "/api/routing/latest", options = {}) => {
1200
+ const fetchImpl = options.fetch ?? globalThis.fetch;
1201
+ const response = await fetchImpl(path);
1202
+ if (!response.ok) {
1203
+ throw new Error(`Voice routing status failed: HTTP ${response.status}`);
1204
+ }
1205
+ return await response.json();
1206
+ };
1207
+ var createVoiceRoutingStatusStore = (path = "/api/routing/latest", options = {}) => {
1208
+ const listeners = new Set;
1209
+ let closed = false;
1210
+ let timer;
1211
+ let snapshot = {
1212
+ decision: null,
1213
+ error: null,
1214
+ isLoading: false
1215
+ };
1216
+ const emit = () => {
1217
+ for (const listener of listeners) {
1218
+ listener();
1219
+ }
1220
+ };
1221
+ const refresh = async () => {
1222
+ if (closed) {
1223
+ return snapshot.decision;
1224
+ }
1225
+ snapshot = {
1226
+ ...snapshot,
1227
+ error: null,
1228
+ isLoading: true
1229
+ };
1230
+ emit();
1231
+ try {
1232
+ const decision = await fetchVoiceRoutingStatus(path, options);
1233
+ snapshot = {
1234
+ decision,
1235
+ error: null,
1236
+ isLoading: false,
1237
+ updatedAt: Date.now()
1238
+ };
1239
+ emit();
1240
+ return decision;
1241
+ } catch (error) {
1242
+ snapshot = {
1243
+ ...snapshot,
1244
+ error: error instanceof Error ? error.message : String(error),
1245
+ isLoading: false
1246
+ };
1247
+ emit();
1248
+ throw error;
1249
+ }
1250
+ };
1251
+ const close = () => {
1252
+ closed = true;
1253
+ if (timer) {
1254
+ clearInterval(timer);
1255
+ timer = undefined;
1256
+ }
1257
+ listeners.clear();
1258
+ };
1259
+ if (options.intervalMs && options.intervalMs > 0) {
1260
+ timer = setInterval(() => {
1261
+ refresh().catch(() => {});
1262
+ }, options.intervalMs);
1263
+ }
1264
+ return {
1265
+ close,
1266
+ getServerSnapshot: () => snapshot,
1267
+ getSnapshot: () => snapshot,
1268
+ refresh,
1269
+ subscribe: (listener) => {
1270
+ listeners.add(listener);
1271
+ return () => {
1272
+ listeners.delete(listener);
1273
+ };
1274
+ }
1275
+ };
1276
+ };
1277
+
1278
+ // src/client/routingStatusWidget.ts
1279
+ var DEFAULT_TITLE3 = "Voice Routing";
1280
+ var DEFAULT_DESCRIPTION3 = "Latest provider routing decision from the self-hosted trace store.";
1281
+ var escapeHtml4 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
1282
+ var formatValue = (value, fallback = "None") => typeof value === "string" && value.trim() ? value : typeof value === "number" && Number.isFinite(value) ? String(value) : fallback;
1283
+ var createVoiceRoutingStatusViewModel = (snapshot, options = {}) => {
1284
+ const decision = snapshot.decision;
1285
+ const rows = decision ? [
1286
+ { label: "Kind", value: decision.kind.toUpperCase() },
1287
+ { label: "Policy", value: formatValue(decision.routing, "Unknown") },
1288
+ { label: "Provider", value: formatValue(decision.provider, "Unknown") },
1289
+ {
1290
+ label: "Selected",
1291
+ value: formatValue(decision.selectedProvider, "Unknown")
1292
+ },
1293
+ {
1294
+ label: "Fallback",
1295
+ value: formatValue(decision.fallbackProvider)
1296
+ },
1297
+ { label: "Status", value: formatValue(decision.status, "unknown") },
1298
+ {
1299
+ label: "Latency budget",
1300
+ value: typeof decision.latencyBudgetMs === "number" ? `${decision.latencyBudgetMs}ms` : "None"
1301
+ }
1302
+ ] : [];
1303
+ return {
1304
+ decision,
1305
+ description: options.description ?? DEFAULT_DESCRIPTION3,
1306
+ error: snapshot.error,
1307
+ isLoading: snapshot.isLoading,
1308
+ label: snapshot.error ? "Unavailable" : decision ? `${formatValue(decision.kind).toUpperCase()} ${formatValue(decision.status, "unknown")}` : snapshot.isLoading ? "Checking" : "No routing yet",
1309
+ rows,
1310
+ status: snapshot.error ? "error" : decision ? "ready" : snapshot.isLoading ? "loading" : "empty",
1311
+ title: options.title ?? DEFAULT_TITLE3,
1312
+ updatedAt: snapshot.updatedAt
1313
+ };
1314
+ };
1315
+ var renderVoiceRoutingStatusHTML = (snapshot, options = {}) => {
1316
+ const model = createVoiceRoutingStatusViewModel(snapshot, options);
1317
+ const rows = model.rows.length ? `<div class="absolute-voice-routing-status__grid">${model.rows.map((row) => `<div>
1318
+ <span>${escapeHtml4(row.label)}</span>
1319
+ <strong>${escapeHtml4(row.value)}</strong>
1320
+ </div>`).join("")}</div>` : '<p class="absolute-voice-routing-status__empty">Start a voice session to see the selected provider.</p>';
1321
+ return `<section class="absolute-voice-routing-status absolute-voice-routing-status--${escapeHtml4(model.status)}">
1322
+ <header class="absolute-voice-routing-status__header">
1323
+ <span class="absolute-voice-routing-status__eyebrow">${escapeHtml4(model.title)}</span>
1324
+ <strong class="absolute-voice-routing-status__label">${escapeHtml4(model.label)}</strong>
1325
+ </header>
1326
+ <p class="absolute-voice-routing-status__description">${escapeHtml4(model.description)}</p>
1327
+ ${rows}
1328
+ ${model.error ? `<p class="absolute-voice-routing-status__error">${escapeHtml4(model.error)}</p>` : ""}
1329
+ </section>`;
1330
+ };
1331
+ var getVoiceRoutingStatusCSS = () => `.absolute-voice-routing-status{border:1px solid #d8d2c4;border-radius:20px;background:#fffaf0;color:#16130d;padding:18px;box-shadow:0 18px 40px rgba(47,37,18,.12);font-family:inherit}.absolute-voice-routing-status--error{border-color:#f2a7a7;background:#fff5f3}.absolute-voice-routing-status__header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-routing-status__eyebrow{color:#73664f;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-routing-status__label{font-size:24px;line-height:1}.absolute-voice-routing-status__description{color:#514733;margin:12px 0 0}.absolute-voice-routing-status__grid{display:grid;gap:8px;grid-template-columns:repeat(2,minmax(0,1fr));margin-top:14px}.absolute-voice-routing-status__grid div{background:#fff;border:1px solid #eee4d2;border-radius:14px;padding:10px 12px}.absolute-voice-routing-status__grid span{color:#655944;display:block;font-size:12px;margin-bottom:4px}.absolute-voice-routing-status__grid strong{overflow-wrap:anywhere}.absolute-voice-routing-status__empty{color:#655944;margin:14px 0 0}.absolute-voice-routing-status__error{color:#9f1239;font-weight:700}`;
1332
+ var mountVoiceRoutingStatus = (element, path = "/api/routing/latest", options = {}) => {
1333
+ const store = createVoiceRoutingStatusStore(path, options);
1334
+ const render = () => {
1335
+ element.innerHTML = renderVoiceRoutingStatusHTML(store.getSnapshot(), options);
1336
+ };
1337
+ const unsubscribe = store.subscribe(render);
1338
+ render();
1339
+ store.refresh().catch(() => {});
1340
+ return {
1341
+ close: () => {
1342
+ unsubscribe();
1343
+ store.close();
1344
+ },
1345
+ refresh: store.refresh
1346
+ };
1347
+ };
1348
+ var defineVoiceRoutingStatusElement = (tagName = "absolute-voice-routing-status") => {
1349
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
1350
+ return;
1351
+ }
1352
+ customElements.define(tagName, class AbsoluteVoiceRoutingStatusElement extends HTMLElement {
1353
+ mounted;
1354
+ connectedCallback() {
1355
+ const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
1356
+ this.mounted = mountVoiceRoutingStatus(this, this.getAttribute("path") ?? "/api/routing/latest", {
1357
+ description: this.getAttribute("description") ?? undefined,
1358
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
1359
+ title: this.getAttribute("title") ?? undefined
1360
+ });
1361
+ }
1362
+ disconnectedCallback() {
1363
+ this.mounted?.close();
1364
+ this.mounted = undefined;
1365
+ }
1366
+ });
1367
+ };
1368
+
1369
+ // src/svelte/createVoiceRoutingStatus.ts
1370
+ var createVoiceRoutingStatus = (path = "/api/routing/latest", options = {}) => {
1371
+ const store = createVoiceRoutingStatusStore(path, options);
1372
+ return {
1373
+ ...store,
1374
+ getHTML: () => renderVoiceRoutingStatusHTML(store.getSnapshot(), options),
1375
+ getViewModel: () => createVoiceRoutingStatusViewModel(store.getSnapshot(), options)
1376
+ };
1377
+ };
1378
+ // src/client/workflowStatus.ts
1379
+ var fetchVoiceWorkflowStatus = async (path = "/evals/scenarios/json", options = {}) => {
1380
+ const fetchImpl = options.fetch ?? globalThis.fetch;
1381
+ const response = await fetchImpl(path);
1382
+ if (!response.ok) {
1383
+ throw new Error(`Voice workflow status failed: HTTP ${response.status}`);
1384
+ }
1385
+ return await response.json();
1386
+ };
1387
+ var createVoiceWorkflowStatusStore = (path = "/evals/scenarios/json", options = {}) => {
1388
+ const listeners = new Set;
1389
+ let closed = false;
1390
+ let timer;
1391
+ let snapshot = {
1392
+ error: null,
1393
+ isLoading: false
1394
+ };
1395
+ const emit = () => {
1396
+ for (const listener of listeners) {
1397
+ listener();
1398
+ }
1399
+ };
1400
+ const refresh = async () => {
1401
+ if (closed) {
1402
+ return snapshot.report;
1403
+ }
1404
+ snapshot = {
1405
+ ...snapshot,
1406
+ error: null,
1407
+ isLoading: true
1408
+ };
1409
+ emit();
1410
+ try {
1411
+ const report = await fetchVoiceWorkflowStatus(path, options);
1412
+ snapshot = {
1413
+ error: null,
1414
+ isLoading: false,
1415
+ report,
1416
+ updatedAt: Date.now()
1417
+ };
1418
+ emit();
1419
+ return report;
1420
+ } catch (error) {
1421
+ snapshot = {
1422
+ ...snapshot,
1423
+ error: error instanceof Error ? error.message : String(error),
1424
+ isLoading: false
1425
+ };
1426
+ emit();
1427
+ throw error;
1428
+ }
1429
+ };
1430
+ const close = () => {
1431
+ closed = true;
1432
+ if (timer) {
1433
+ clearInterval(timer);
1434
+ timer = undefined;
1435
+ }
1436
+ listeners.clear();
1437
+ };
1438
+ if (typeof window !== "undefined" && options.intervalMs && options.intervalMs > 0) {
1439
+ timer = setInterval(() => {
1440
+ refresh().catch(() => {});
1441
+ }, options.intervalMs);
1442
+ }
1443
+ return {
1444
+ close,
1445
+ getServerSnapshot: () => snapshot,
1446
+ getSnapshot: () => snapshot,
1447
+ refresh,
1448
+ subscribe: (listener) => {
1449
+ listeners.add(listener);
1450
+ return () => {
1451
+ listeners.delete(listener);
1452
+ };
1453
+ }
1454
+ };
1455
+ };
1456
+
1457
+ // src/svelte/createVoiceWorkflowStatus.ts
1458
+ var createVoiceWorkflowStatus = (path = "/evals/scenarios/json", options = {}) => createVoiceWorkflowStatusStore(path, options);
551
1459
  // src/client/htmx.ts
552
1460
  var DEFAULT_EVENT_NAME = "voice-refresh";
553
1461
  var DEFAULT_QUERY_PARAM = "sessionId";
@@ -1010,6 +1918,7 @@ var resolveVoiceRuntimePreset = (name = "default") => {
1010
1918
  var createInitialState2 = (stream) => ({
1011
1919
  assistantAudio: [...stream.assistantAudio],
1012
1920
  assistantTexts: [...stream.assistantTexts],
1921
+ call: stream.call,
1013
1922
  error: stream.error,
1014
1923
  isConnected: stream.isConnected,
1015
1924
  isRecording: false,
@@ -1039,6 +1948,7 @@ var createVoiceController = (path, options = {}) => {
1039
1948
  ...state,
1040
1949
  assistantAudio: [...stream.assistantAudio],
1041
1950
  assistantTexts: [...stream.assistantTexts],
1951
+ call: stream.call,
1042
1952
  error: stream.error,
1043
1953
  isConnected: stream.isConnected,
1044
1954
  partial: stream.partial,
@@ -1116,6 +2026,7 @@ var createVoiceController = (path, options = {}) => {
1116
2026
  bindHTMX(bindingOptions) {
1117
2027
  return bindVoiceHTMX(stream, bindingOptions);
1118
2028
  },
2029
+ callControl: (message) => stream.callControl(message),
1119
2030
  close,
1120
2031
  endTurn: () => stream.endTurn(),
1121
2032
  get error() {
@@ -1168,10 +2079,19 @@ var createVoiceController = (path, options = {}) => {
1168
2079
  },
1169
2080
  get assistantAudio() {
1170
2081
  return state.assistantAudio;
2082
+ },
2083
+ get call() {
2084
+ return state.call;
1171
2085
  }
1172
2086
  };
1173
2087
  };
1174
2088
  export {
2089
+ createVoiceWorkflowStatus,
1175
2090
  createVoiceStream2 as createVoiceStream,
1176
- createVoiceController
2091
+ createVoiceRoutingStatus,
2092
+ createVoiceProviderStatus,
2093
+ createVoiceProviderSimulationControls,
2094
+ createVoiceOpsStatus,
2095
+ createVoiceController,
2096
+ createVoiceAppKitStatus
1177
2097
  };