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

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 (74) 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 +14 -0
  17. package/dist/client/index.js +713 -2
  18. package/dist/client/opsStatusWidget.d.ts +40 -0
  19. package/dist/client/providerStatus.d.ts +19 -0
  20. package/dist/client/providerStatusWidget.d.ts +32 -0
  21. package/dist/client/routingStatus.d.ts +19 -0
  22. package/dist/client/routingStatusWidget.d.ts +28 -0
  23. package/dist/client/workflowStatus.d.ts +19 -0
  24. package/dist/diagnosticsRoutes.d.ts +44 -0
  25. package/dist/evalRoutes.d.ts +213 -0
  26. package/dist/handoff.d.ts +54 -0
  27. package/dist/handoffHealth.d.ts +94 -0
  28. package/dist/index.d.ts +32 -4
  29. package/dist/index.js +4222 -133
  30. package/dist/modelAdapters.d.ts +75 -0
  31. package/dist/opsConsoleRoutes.d.ts +77 -0
  32. package/dist/opsWebhook.d.ts +126 -0
  33. package/dist/providerAdapters.d.ts +48 -0
  34. package/dist/providerHealth.d.ts +79 -0
  35. package/dist/qualityRoutes.d.ts +76 -0
  36. package/dist/queue.d.ts +52 -0
  37. package/dist/react/VoiceOpsStatus.d.ts +6 -0
  38. package/dist/react/VoiceProviderStatus.d.ts +6 -0
  39. package/dist/react/VoiceRoutingStatus.d.ts +6 -0
  40. package/dist/react/index.d.ts +7 -0
  41. package/dist/react/index.js +1024 -11
  42. package/dist/react/useVoiceAppKitStatus.d.ts +8 -0
  43. package/dist/react/useVoiceController.d.ts +2 -0
  44. package/dist/react/useVoiceProviderStatus.d.ts +8 -0
  45. package/dist/react/useVoiceRoutingStatus.d.ts +8 -0
  46. package/dist/react/useVoiceStream.d.ts +2 -0
  47. package/dist/react/useVoiceWorkflowStatus.d.ts +8 -0
  48. package/dist/resilienceRoutes.d.ts +117 -0
  49. package/dist/sessionReplay.d.ts +175 -0
  50. package/dist/svelte/createVoiceAppKitStatus.d.ts +8 -0
  51. package/dist/svelte/createVoiceOpsStatus.d.ts +9 -0
  52. package/dist/svelte/createVoiceProviderStatus.d.ts +10 -0
  53. package/dist/svelte/createVoiceRoutingStatus.d.ts +10 -0
  54. package/dist/svelte/createVoiceWorkflowStatus.d.ts +8 -0
  55. package/dist/svelte/index.d.ts +5 -0
  56. package/dist/svelte/index.js +736 -3
  57. package/dist/testing/index.d.ts +2 -0
  58. package/dist/testing/index.js +1537 -7
  59. package/dist/testing/ioProviderSimulator.d.ts +41 -0
  60. package/dist/testing/providerSimulator.d.ts +44 -0
  61. package/dist/trace.d.ts +1 -1
  62. package/dist/types.d.ts +84 -2
  63. package/dist/vue/VoiceOpsStatus.d.ts +30 -0
  64. package/dist/vue/VoiceProviderStatus.d.ts +51 -0
  65. package/dist/vue/VoiceRoutingStatus.d.ts +51 -0
  66. package/dist/vue/index.d.ts +7 -0
  67. package/dist/vue/index.js +1062 -25
  68. package/dist/vue/useVoiceAppKitStatus.d.ts +9 -0
  69. package/dist/vue/useVoiceProviderStatus.d.ts +9 -0
  70. package/dist/vue/useVoiceRoutingStatus.d.ts +8 -0
  71. package/dist/vue/useVoiceStream.d.ts +2 -0
  72. package/dist/vue/useVoiceWorkflowStatus.d.ts +9 -0
  73. package/dist/workflowContract.d.ts +91 -0
  74. package/package.json +1 -1
@@ -69,9 +69,871 @@ 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/react/useVoiceStream.tsx
72
+ // src/react/useVoiceAppKitStatus.tsx
73
73
  import { useEffect, useRef, useSyncExternalStore } from "react";
74
74
 
75
+ // src/client/appKitStatus.ts
76
+ var fetchVoiceAppKitStatus = async (path = "/app-kit/status", options = {}) => {
77
+ const fetchImpl = options.fetch ?? globalThis.fetch;
78
+ const response = await fetchImpl(path);
79
+ if (!response.ok) {
80
+ throw new Error(`Voice app kit status failed: HTTP ${response.status}`);
81
+ }
82
+ return await response.json();
83
+ };
84
+ var createVoiceAppKitStatusStore = (path = "/app-kit/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();
95
+ }
96
+ };
97
+ const refresh = async () => {
98
+ if (closed) {
99
+ return snapshot.report;
100
+ }
101
+ snapshot = {
102
+ ...snapshot,
103
+ error: null,
104
+ isLoading: true
105
+ };
106
+ emit();
107
+ try {
108
+ const report = await fetchVoiceAppKitStatus(path, options);
109
+ snapshot = {
110
+ error: null,
111
+ isLoading: false,
112
+ report,
113
+ updatedAt: Date.now()
114
+ };
115
+ emit();
116
+ return report;
117
+ } catch (error) {
118
+ snapshot = {
119
+ ...snapshot,
120
+ error: error instanceof Error ? error.message : String(error),
121
+ isLoading: false
122
+ };
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);
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
+ };
152
+ };
153
+
154
+ // src/react/useVoiceAppKitStatus.tsx
155
+ var useVoiceAppKitStatus = (path = "/app-kit/status", options = {}) => {
156
+ const storeRef = useRef(null);
157
+ if (!storeRef.current) {
158
+ storeRef.current = createVoiceAppKitStatusStore(path, options);
159
+ }
160
+ const store = storeRef.current;
161
+ useEffect(() => {
162
+ store.refresh().catch(() => {});
163
+ return () => store.close();
164
+ }, [store]);
165
+ return {
166
+ ...useSyncExternalStore(store.subscribe, store.getSnapshot, store.getServerSnapshot),
167
+ refresh: store.refresh
168
+ };
169
+ };
170
+
171
+ // src/client/opsStatusWidget.ts
172
+ var DEFAULT_TITLE = "Voice Ops Status";
173
+ var DEFAULT_DESCRIPTION = "Certified workflow, provider, and handoff readiness from the AbsoluteJS voice app kit.";
174
+ var SURFACE_LABELS = {
175
+ handoffs: "Handoffs",
176
+ providers: "Providers",
177
+ quality: "Quality",
178
+ sessions: "Sessions",
179
+ workflows: "Workflows"
180
+ };
181
+ var escapeHtml = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
182
+ var readNumber = (value, key) => value && typeof value === "object" && (key in value) ? Number(value[key] ?? 0) : 0;
183
+ var surfaceDetail = (surface) => {
184
+ const total = readNumber(surface, "total");
185
+ const failed = readNumber(surface, "failed");
186
+ const degraded = readNumber(surface, "degraded");
187
+ const source = surface && typeof surface === "object" && "source" in surface && typeof surface.source === "string" ? ` from ${surface.source}` : "";
188
+ if (degraded > 0) {
189
+ return `${degraded} degraded of ${total}`;
190
+ }
191
+ if (failed > 0) {
192
+ return `${failed} failing of ${total}${source}`;
193
+ }
194
+ return total > 0 ? `${total} passing${source}` : `No failures${source}`;
195
+ };
196
+ var getVoiceOpsStatusLabel = (report, error) => {
197
+ if (error) {
198
+ return "Unavailable";
199
+ }
200
+ if (!report) {
201
+ return "Checking";
202
+ }
203
+ return report.status === "pass" ? "Passing" : "Needs attention";
204
+ };
205
+ var createVoiceOpsStatusViewModel = (snapshot, options = {}) => {
206
+ const report = snapshot.report;
207
+ const surfaces = Object.entries(report?.surfaces ?? {}).map(([id, surface]) => {
208
+ const failed = readNumber(surface, "failed") || readNumber(surface, "degraded");
209
+ const total = readNumber(surface, "total");
210
+ const status = surface && typeof surface === "object" && "status" in surface ? surface.status ?? "pass" : "pass";
211
+ return {
212
+ detail: surfaceDetail(surface),
213
+ failed,
214
+ id,
215
+ label: SURFACE_LABELS[id] ?? id,
216
+ status,
217
+ total
218
+ };
219
+ });
220
+ return {
221
+ description: options.description ?? DEFAULT_DESCRIPTION,
222
+ error: snapshot.error,
223
+ isLoading: snapshot.isLoading,
224
+ label: getVoiceOpsStatusLabel(report, snapshot.error),
225
+ links: options.includeLinks === false ? [] : report?.links ?? [],
226
+ passed: report?.passed ?? 0,
227
+ status: snapshot.error ? "error" : report ? report.status : snapshot.isLoading ? "loading" : "loading",
228
+ surfaces,
229
+ title: options.title ?? DEFAULT_TITLE,
230
+ total: report?.total ?? 0,
231
+ updatedAt: snapshot.updatedAt
232
+ };
233
+ };
234
+ var renderVoiceOpsStatusHTML = (snapshot, options = {}) => {
235
+ const model = createVoiceOpsStatusViewModel(snapshot, options);
236
+ const surfaces = model.surfaces.length ? model.surfaces.map((surface) => `<li class="absolute-voice-ops-status__surface absolute-voice-ops-status__surface--${escapeHtml(surface.status)}">
237
+ <span>${escapeHtml(surface.label)}</span>
238
+ <strong>${escapeHtml(surface.detail)}</strong>
239
+ </li>`).join("") : '<li class="absolute-voice-ops-status__surface"><span>Status</span><strong>Waiting for first check</strong></li>';
240
+ 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>` : "";
241
+ return `<section class="absolute-voice-ops-status absolute-voice-ops-status--${escapeHtml(model.status)}">
242
+ <header class="absolute-voice-ops-status__header">
243
+ <span class="absolute-voice-ops-status__eyebrow">${escapeHtml(model.title)}</span>
244
+ <strong class="absolute-voice-ops-status__label">${escapeHtml(model.label)}</strong>
245
+ </header>
246
+ <p class="absolute-voice-ops-status__description">${escapeHtml(model.description)}</p>
247
+ <div class="absolute-voice-ops-status__summary">
248
+ <span>${model.passed} passing</span>
249
+ <span>${Math.max(model.total - model.passed, 0)} failing</span>
250
+ <span>${model.total} checks</span>
251
+ </div>
252
+ <ul class="absolute-voice-ops-status__surfaces">${surfaces}</ul>
253
+ ${model.error ? `<p class="absolute-voice-ops-status__error">${escapeHtml(model.error)}</p>` : ""}
254
+ ${links}
255
+ </section>`;
256
+ };
257
+ 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}`;
258
+ var mountVoiceOpsStatus = (element, path = "/app-kit/status", options = {}) => {
259
+ const store = createVoiceAppKitStatusStore(path, options);
260
+ const render = () => {
261
+ element.innerHTML = renderVoiceOpsStatusHTML(store.getSnapshot(), options);
262
+ };
263
+ const unsubscribe = store.subscribe(render);
264
+ render();
265
+ store.refresh().catch(() => {});
266
+ return {
267
+ close: () => {
268
+ unsubscribe();
269
+ store.close();
270
+ },
271
+ refresh: store.refresh
272
+ };
273
+ };
274
+ var defineVoiceOpsStatusElement = (tagName = "absolute-voice-ops-status") => {
275
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
276
+ return;
277
+ }
278
+ customElements.define(tagName, class AbsoluteVoiceOpsStatusElement extends HTMLElement {
279
+ mounted;
280
+ connectedCallback() {
281
+ const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
282
+ this.mounted = mountVoiceOpsStatus(this, this.getAttribute("path") ?? "/app-kit/status", {
283
+ description: this.getAttribute("description") ?? undefined,
284
+ includeLinks: this.getAttribute("include-links") !== "false",
285
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
286
+ title: this.getAttribute("title") ?? undefined
287
+ });
288
+ }
289
+ disconnectedCallback() {
290
+ this.mounted?.close();
291
+ this.mounted = undefined;
292
+ }
293
+ });
294
+ };
295
+
296
+ // src/react/VoiceOpsStatus.tsx
297
+ import { jsxDEV } from "react/jsx-dev-runtime";
298
+ var VoiceOpsStatus = ({
299
+ className,
300
+ path = "/app-kit/status",
301
+ ...options
302
+ }) => {
303
+ const snapshot = useVoiceAppKitStatus(path, options);
304
+ const model = createVoiceOpsStatusViewModel(snapshot, options);
305
+ return /* @__PURE__ */ jsxDEV("section", {
306
+ className: [
307
+ "absolute-voice-ops-status",
308
+ `absolute-voice-ops-status--${model.status}`,
309
+ className
310
+ ].filter(Boolean).join(" "),
311
+ children: [
312
+ /* @__PURE__ */ jsxDEV("header", {
313
+ className: "absolute-voice-ops-status__header",
314
+ children: [
315
+ /* @__PURE__ */ jsxDEV("span", {
316
+ className: "absolute-voice-ops-status__eyebrow",
317
+ children: model.title
318
+ }, undefined, false, undefined, this),
319
+ /* @__PURE__ */ jsxDEV("strong", {
320
+ className: "absolute-voice-ops-status__label",
321
+ children: model.label
322
+ }, undefined, false, undefined, this)
323
+ ]
324
+ }, undefined, true, undefined, this),
325
+ /* @__PURE__ */ jsxDEV("p", {
326
+ className: "absolute-voice-ops-status__description",
327
+ children: model.description
328
+ }, undefined, false, undefined, this),
329
+ /* @__PURE__ */ jsxDEV("div", {
330
+ className: "absolute-voice-ops-status__summary",
331
+ children: [
332
+ /* @__PURE__ */ jsxDEV("span", {
333
+ children: [
334
+ model.passed,
335
+ " passing"
336
+ ]
337
+ }, undefined, true, undefined, this),
338
+ /* @__PURE__ */ jsxDEV("span", {
339
+ children: [
340
+ Math.max(model.total - model.passed, 0),
341
+ " failing"
342
+ ]
343
+ }, undefined, true, undefined, this),
344
+ /* @__PURE__ */ jsxDEV("span", {
345
+ children: [
346
+ model.total,
347
+ " checks"
348
+ ]
349
+ }, undefined, true, undefined, this)
350
+ ]
351
+ }, undefined, true, undefined, this),
352
+ /* @__PURE__ */ jsxDEV("ul", {
353
+ className: "absolute-voice-ops-status__surfaces",
354
+ children: model.surfaces.length > 0 ? model.surfaces.map((surface) => /* @__PURE__ */ jsxDEV("li", {
355
+ className: `absolute-voice-ops-status__surface absolute-voice-ops-status__surface--${surface.status}`,
356
+ children: [
357
+ /* @__PURE__ */ jsxDEV("span", {
358
+ children: surface.label
359
+ }, undefined, false, undefined, this),
360
+ /* @__PURE__ */ jsxDEV("strong", {
361
+ children: surface.detail
362
+ }, undefined, false, undefined, this)
363
+ ]
364
+ }, surface.id, true, undefined, this)) : /* @__PURE__ */ jsxDEV("li", {
365
+ className: "absolute-voice-ops-status__surface",
366
+ children: [
367
+ /* @__PURE__ */ jsxDEV("span", {
368
+ children: "Status"
369
+ }, undefined, false, undefined, this),
370
+ /* @__PURE__ */ jsxDEV("strong", {
371
+ children: "Waiting for first check"
372
+ }, undefined, false, undefined, this)
373
+ ]
374
+ }, undefined, true, undefined, this)
375
+ }, undefined, false, undefined, this),
376
+ model.error ? /* @__PURE__ */ jsxDEV("p", {
377
+ className: "absolute-voice-ops-status__error",
378
+ children: model.error
379
+ }, undefined, false, undefined, this) : null,
380
+ model.links.length > 0 ? /* @__PURE__ */ jsxDEV("nav", {
381
+ className: "absolute-voice-ops-status__links",
382
+ children: model.links.slice(0, 4).map((link) => /* @__PURE__ */ jsxDEV("a", {
383
+ href: link.href,
384
+ children: link.label
385
+ }, `${link.label}:${link.href}`, false, undefined, this))
386
+ }, undefined, false, undefined, this) : null
387
+ ]
388
+ }, undefined, true, undefined, this);
389
+ };
390
+ // src/react/useVoiceProviderStatus.tsx
391
+ import { useEffect as useEffect2, useRef as useRef2, useSyncExternalStore as useSyncExternalStore2 } from "react";
392
+
393
+ // src/client/providerStatus.ts
394
+ var fetchVoiceProviderStatus = async (path = "/api/provider-status", options = {}) => {
395
+ const fetchImpl = options.fetch ?? globalThis.fetch;
396
+ const response = await fetchImpl(path);
397
+ if (!response.ok) {
398
+ throw new Error(`Voice provider status failed: HTTP ${response.status}`);
399
+ }
400
+ return await response.json();
401
+ };
402
+ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {}) => {
403
+ const listeners = new Set;
404
+ let closed = false;
405
+ let timer;
406
+ let snapshot = {
407
+ error: null,
408
+ isLoading: false,
409
+ providers: []
410
+ };
411
+ const emit = () => {
412
+ for (const listener of listeners) {
413
+ listener();
414
+ }
415
+ };
416
+ const refresh = async () => {
417
+ if (closed) {
418
+ return snapshot.providers;
419
+ }
420
+ snapshot = {
421
+ ...snapshot,
422
+ error: null,
423
+ isLoading: true
424
+ };
425
+ emit();
426
+ try {
427
+ const providers = await fetchVoiceProviderStatus(path, options);
428
+ snapshot = {
429
+ error: null,
430
+ isLoading: false,
431
+ providers,
432
+ updatedAt: Date.now()
433
+ };
434
+ emit();
435
+ return providers;
436
+ } catch (error) {
437
+ snapshot = {
438
+ ...snapshot,
439
+ error: error instanceof Error ? error.message : String(error),
440
+ isLoading: false
441
+ };
442
+ emit();
443
+ throw error;
444
+ }
445
+ };
446
+ const close = () => {
447
+ closed = true;
448
+ if (timer) {
449
+ clearInterval(timer);
450
+ timer = undefined;
451
+ }
452
+ listeners.clear();
453
+ };
454
+ if (options.intervalMs && options.intervalMs > 0) {
455
+ timer = setInterval(() => {
456
+ refresh().catch(() => {});
457
+ }, options.intervalMs);
458
+ }
459
+ return {
460
+ close,
461
+ getServerSnapshot: () => snapshot,
462
+ getSnapshot: () => snapshot,
463
+ refresh,
464
+ subscribe: (listener) => {
465
+ listeners.add(listener);
466
+ return () => {
467
+ listeners.delete(listener);
468
+ };
469
+ }
470
+ };
471
+ };
472
+
473
+ // src/react/useVoiceProviderStatus.tsx
474
+ var useVoiceProviderStatus = (path = "/api/provider-status", options = {}) => {
475
+ const storeRef = useRef2(null);
476
+ if (!storeRef.current) {
477
+ storeRef.current = createVoiceProviderStatusStore(path, options);
478
+ }
479
+ const store = storeRef.current;
480
+ useEffect2(() => {
481
+ store.refresh().catch(() => {});
482
+ return () => store.close();
483
+ }, [store]);
484
+ return {
485
+ ...useSyncExternalStore2(store.subscribe, store.getSnapshot, store.getServerSnapshot),
486
+ refresh: store.refresh
487
+ };
488
+ };
489
+
490
+ // src/client/providerStatusWidget.ts
491
+ var DEFAULT_TITLE2 = "Voice Providers";
492
+ var DEFAULT_DESCRIPTION2 = "Live provider health, fallback counts, latency, and suppression state from your self-hosted trace store.";
493
+ var escapeHtml2 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
494
+ var formatProvider = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
495
+ var formatStatus = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
496
+ var formatLatency = (value) => typeof value === "number" ? `${value}ms` : "No samples";
497
+ var formatSuppression = (value) => typeof value === "number" ? `${Math.ceil(value / 1000)}s` : "None";
498
+ var getProviderDetail = (provider) => {
499
+ if (provider.status === "suppressed") {
500
+ return provider.lastError ? `Suppressed for ${formatSuppression(provider.suppressionRemainingMs)} after ${provider.lastError}.` : `Suppressed for ${formatSuppression(provider.suppressionRemainingMs)}.`;
501
+ }
502
+ if (provider.status === "recoverable") {
503
+ return "Cooldown expired; ready for recovery traffic.";
504
+ }
505
+ if (provider.status === "rate-limited") {
506
+ return "Rate limit detected; router should avoid this provider.";
507
+ }
508
+ if (provider.status === "degraded") {
509
+ return provider.lastError ?? "Recent provider errors detected.";
510
+ }
511
+ if (provider.status === "healthy") {
512
+ return provider.recommended ? "Healthy and currently recommended." : "Healthy and available for routing.";
513
+ }
514
+ return "No provider traffic observed yet.";
515
+ };
516
+ var isWarningStatus = (status) => status === "degraded" || status === "rate-limited" || status === "recoverable" || status === "suppressed";
517
+ var createVoiceProviderStatusViewModel = (snapshot, options = {}) => {
518
+ const providers = snapshot.providers.map((provider) => ({
519
+ ...provider,
520
+ detail: getProviderDetail(provider),
521
+ label: `${formatProvider(provider.provider)}${provider.recommended ? " recommended" : ""}`,
522
+ rows: [
523
+ { label: "Runs", value: String(provider.runCount) },
524
+ { label: "Avg latency", value: formatLatency(provider.averageElapsedMs) },
525
+ { label: "Errors", value: String(provider.errorCount) },
526
+ { label: "Timeouts", value: String(provider.timeoutCount) },
527
+ { label: "Fallbacks", value: String(provider.fallbackCount) },
528
+ {
529
+ label: "Suppression",
530
+ value: formatSuppression(provider.suppressionRemainingMs)
531
+ }
532
+ ]
533
+ }));
534
+ const warningCount = providers.filter((provider) => isWarningStatus(provider.status)).length;
535
+ const healthyCount = providers.filter((provider) => provider.status === "healthy").length;
536
+ return {
537
+ description: options.description ?? DEFAULT_DESCRIPTION2,
538
+ error: snapshot.error,
539
+ isLoading: snapshot.isLoading,
540
+ label: snapshot.error ? "Unavailable" : providers.length ? warningCount > 0 ? `${warningCount} needs attention` : `${healthyCount} healthy` : snapshot.isLoading ? "Checking" : "No provider traffic",
541
+ providers,
542
+ status: snapshot.error ? "error" : providers.length ? warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
543
+ title: options.title ?? DEFAULT_TITLE2,
544
+ updatedAt: snapshot.updatedAt
545
+ };
546
+ };
547
+ var renderVoiceProviderStatusHTML = (snapshot, options = {}) => {
548
+ const model = createVoiceProviderStatusViewModel(snapshot, options);
549
+ 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--${escapeHtml2(provider.status)}">
550
+ <header>
551
+ <strong>${escapeHtml2(provider.label)}</strong>
552
+ <span>${escapeHtml2(formatStatus(provider.status))}</span>
553
+ </header>
554
+ <p>${escapeHtml2(provider.detail)}</p>
555
+ <dl>${provider.rows.map((row) => `<div>
556
+ <dt>${escapeHtml2(row.label)}</dt>
557
+ <dd>${escapeHtml2(row.value)}</dd>
558
+ </div>`).join("")}</dl>
559
+ </article>`).join("")}</div>` : '<p class="absolute-voice-provider-status__empty">Run voice traffic to see provider health.</p>';
560
+ return `<section class="absolute-voice-provider-status absolute-voice-provider-status--${escapeHtml2(model.status)}">
561
+ <header class="absolute-voice-provider-status__header">
562
+ <span class="absolute-voice-provider-status__eyebrow">${escapeHtml2(model.title)}</span>
563
+ <strong class="absolute-voice-provider-status__label">${escapeHtml2(model.label)}</strong>
564
+ </header>
565
+ <p class="absolute-voice-provider-status__description">${escapeHtml2(model.description)}</p>
566
+ ${providers}
567
+ ${model.error ? `<p class="absolute-voice-provider-status__error">${escapeHtml2(model.error)}</p>` : ""}
568
+ </section>`;
569
+ };
570
+ 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}`;
571
+ var mountVoiceProviderStatus = (element, path = "/api/provider-status", options = {}) => {
572
+ const store = createVoiceProviderStatusStore(path, options);
573
+ const render = () => {
574
+ element.innerHTML = renderVoiceProviderStatusHTML(store.getSnapshot(), options);
575
+ };
576
+ const unsubscribe = store.subscribe(render);
577
+ render();
578
+ store.refresh().catch(() => {});
579
+ return {
580
+ close: () => {
581
+ unsubscribe();
582
+ store.close();
583
+ },
584
+ refresh: store.refresh
585
+ };
586
+ };
587
+ var defineVoiceProviderStatusElement = (tagName = "absolute-voice-provider-status") => {
588
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
589
+ return;
590
+ }
591
+ customElements.define(tagName, class AbsoluteVoiceProviderStatusElement extends HTMLElement {
592
+ mounted;
593
+ connectedCallback() {
594
+ const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
595
+ this.mounted = mountVoiceProviderStatus(this, this.getAttribute("path") ?? "/api/provider-status", {
596
+ description: this.getAttribute("description") ?? undefined,
597
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
598
+ title: this.getAttribute("title") ?? undefined
599
+ });
600
+ }
601
+ disconnectedCallback() {
602
+ this.mounted?.close();
603
+ this.mounted = undefined;
604
+ }
605
+ });
606
+ };
607
+
608
+ // src/react/VoiceProviderStatus.tsx
609
+ import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
610
+ var VoiceProviderStatus = ({
611
+ className,
612
+ path = "/api/provider-status",
613
+ ...options
614
+ }) => {
615
+ const snapshot = useVoiceProviderStatus(path, options);
616
+ const model = createVoiceProviderStatusViewModel(snapshot, options);
617
+ return /* @__PURE__ */ jsxDEV2("section", {
618
+ className: [
619
+ "absolute-voice-provider-status",
620
+ `absolute-voice-provider-status--${model.status}`,
621
+ className
622
+ ].filter(Boolean).join(" "),
623
+ children: [
624
+ /* @__PURE__ */ jsxDEV2("header", {
625
+ className: "absolute-voice-provider-status__header",
626
+ children: [
627
+ /* @__PURE__ */ jsxDEV2("span", {
628
+ className: "absolute-voice-provider-status__eyebrow",
629
+ children: model.title
630
+ }, undefined, false, undefined, this),
631
+ /* @__PURE__ */ jsxDEV2("strong", {
632
+ className: "absolute-voice-provider-status__label",
633
+ children: model.label
634
+ }, undefined, false, undefined, this)
635
+ ]
636
+ }, undefined, true, undefined, this),
637
+ /* @__PURE__ */ jsxDEV2("p", {
638
+ className: "absolute-voice-provider-status__description",
639
+ children: model.description
640
+ }, undefined, false, undefined, this),
641
+ model.providers.length ? /* @__PURE__ */ jsxDEV2("div", {
642
+ className: "absolute-voice-provider-status__providers",
643
+ children: model.providers.map((provider) => /* @__PURE__ */ jsxDEV2("article", {
644
+ className: [
645
+ "absolute-voice-provider-status__provider",
646
+ `absolute-voice-provider-status__provider--${provider.status}`
647
+ ].join(" "),
648
+ children: [
649
+ /* @__PURE__ */ jsxDEV2("header", {
650
+ children: [
651
+ /* @__PURE__ */ jsxDEV2("strong", {
652
+ children: provider.label
653
+ }, undefined, false, undefined, this),
654
+ /* @__PURE__ */ jsxDEV2("span", {
655
+ children: provider.status
656
+ }, undefined, false, undefined, this)
657
+ ]
658
+ }, undefined, true, undefined, this),
659
+ /* @__PURE__ */ jsxDEV2("p", {
660
+ children: provider.detail
661
+ }, undefined, false, undefined, this),
662
+ /* @__PURE__ */ jsxDEV2("dl", {
663
+ children: provider.rows.map((row) => /* @__PURE__ */ jsxDEV2("div", {
664
+ children: [
665
+ /* @__PURE__ */ jsxDEV2("dt", {
666
+ children: row.label
667
+ }, undefined, false, undefined, this),
668
+ /* @__PURE__ */ jsxDEV2("dd", {
669
+ children: row.value
670
+ }, undefined, false, undefined, this)
671
+ ]
672
+ }, row.label, true, undefined, this))
673
+ }, undefined, false, undefined, this)
674
+ ]
675
+ }, provider.provider, true, undefined, this))
676
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV2("p", {
677
+ className: "absolute-voice-provider-status__empty",
678
+ children: "Run voice traffic to see provider health."
679
+ }, undefined, false, undefined, this),
680
+ model.error ? /* @__PURE__ */ jsxDEV2("p", {
681
+ className: "absolute-voice-provider-status__error",
682
+ children: model.error
683
+ }, undefined, false, undefined, this) : null
684
+ ]
685
+ }, undefined, true, undefined, this);
686
+ };
687
+ // src/react/useVoiceRoutingStatus.tsx
688
+ import { useEffect as useEffect3, useRef as useRef3, useSyncExternalStore as useSyncExternalStore3 } from "react";
689
+
690
+ // src/client/routingStatus.ts
691
+ var fetchVoiceRoutingStatus = async (path = "/api/routing/latest", options = {}) => {
692
+ const fetchImpl = options.fetch ?? globalThis.fetch;
693
+ const response = await fetchImpl(path);
694
+ if (!response.ok) {
695
+ throw new Error(`Voice routing status failed: HTTP ${response.status}`);
696
+ }
697
+ return await response.json();
698
+ };
699
+ var createVoiceRoutingStatusStore = (path = "/api/routing/latest", options = {}) => {
700
+ const listeners = new Set;
701
+ let closed = false;
702
+ let timer;
703
+ let snapshot = {
704
+ decision: null,
705
+ error: null,
706
+ isLoading: false
707
+ };
708
+ const emit = () => {
709
+ for (const listener of listeners) {
710
+ listener();
711
+ }
712
+ };
713
+ const refresh = async () => {
714
+ if (closed) {
715
+ return snapshot.decision;
716
+ }
717
+ snapshot = {
718
+ ...snapshot,
719
+ error: null,
720
+ isLoading: true
721
+ };
722
+ emit();
723
+ try {
724
+ const decision = await fetchVoiceRoutingStatus(path, options);
725
+ snapshot = {
726
+ decision,
727
+ error: null,
728
+ isLoading: false,
729
+ updatedAt: Date.now()
730
+ };
731
+ emit();
732
+ return decision;
733
+ } catch (error) {
734
+ snapshot = {
735
+ ...snapshot,
736
+ error: error instanceof Error ? error.message : String(error),
737
+ isLoading: false
738
+ };
739
+ emit();
740
+ throw error;
741
+ }
742
+ };
743
+ const close = () => {
744
+ closed = true;
745
+ if (timer) {
746
+ clearInterval(timer);
747
+ timer = undefined;
748
+ }
749
+ listeners.clear();
750
+ };
751
+ if (options.intervalMs && options.intervalMs > 0) {
752
+ timer = setInterval(() => {
753
+ refresh().catch(() => {});
754
+ }, options.intervalMs);
755
+ }
756
+ return {
757
+ close,
758
+ getServerSnapshot: () => snapshot,
759
+ getSnapshot: () => snapshot,
760
+ refresh,
761
+ subscribe: (listener) => {
762
+ listeners.add(listener);
763
+ return () => {
764
+ listeners.delete(listener);
765
+ };
766
+ }
767
+ };
768
+ };
769
+
770
+ // src/react/useVoiceRoutingStatus.tsx
771
+ var useVoiceRoutingStatus = (path = "/api/routing/latest", options = {}) => {
772
+ const storeRef = useRef3(null);
773
+ if (!storeRef.current) {
774
+ storeRef.current = createVoiceRoutingStatusStore(path, options);
775
+ }
776
+ const store = storeRef.current;
777
+ useEffect3(() => {
778
+ store.refresh().catch(() => {});
779
+ return () => store.close();
780
+ }, [store]);
781
+ return {
782
+ ...useSyncExternalStore3(store.subscribe, store.getSnapshot, store.getServerSnapshot),
783
+ refresh: store.refresh
784
+ };
785
+ };
786
+
787
+ // src/client/routingStatusWidget.ts
788
+ var DEFAULT_TITLE3 = "Voice Routing";
789
+ var DEFAULT_DESCRIPTION3 = "Latest provider routing decision from the self-hosted trace store.";
790
+ var escapeHtml3 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
791
+ var formatValue = (value, fallback = "None") => typeof value === "string" && value.trim() ? value : typeof value === "number" && Number.isFinite(value) ? String(value) : fallback;
792
+ var createVoiceRoutingStatusViewModel = (snapshot, options = {}) => {
793
+ const decision = snapshot.decision;
794
+ const rows = decision ? [
795
+ { label: "Kind", value: decision.kind.toUpperCase() },
796
+ { label: "Policy", value: formatValue(decision.routing, "Unknown") },
797
+ { label: "Provider", value: formatValue(decision.provider, "Unknown") },
798
+ {
799
+ label: "Selected",
800
+ value: formatValue(decision.selectedProvider, "Unknown")
801
+ },
802
+ {
803
+ label: "Fallback",
804
+ value: formatValue(decision.fallbackProvider)
805
+ },
806
+ { label: "Status", value: formatValue(decision.status, "unknown") },
807
+ {
808
+ label: "Latency budget",
809
+ value: typeof decision.latencyBudgetMs === "number" ? `${decision.latencyBudgetMs}ms` : "None"
810
+ }
811
+ ] : [];
812
+ return {
813
+ decision,
814
+ description: options.description ?? DEFAULT_DESCRIPTION3,
815
+ error: snapshot.error,
816
+ isLoading: snapshot.isLoading,
817
+ label: snapshot.error ? "Unavailable" : decision ? `${formatValue(decision.kind).toUpperCase()} ${formatValue(decision.status, "unknown")}` : snapshot.isLoading ? "Checking" : "No routing yet",
818
+ rows,
819
+ status: snapshot.error ? "error" : decision ? "ready" : snapshot.isLoading ? "loading" : "empty",
820
+ title: options.title ?? DEFAULT_TITLE3,
821
+ updatedAt: snapshot.updatedAt
822
+ };
823
+ };
824
+ var renderVoiceRoutingStatusHTML = (snapshot, options = {}) => {
825
+ const model = createVoiceRoutingStatusViewModel(snapshot, options);
826
+ const rows = model.rows.length ? `<div class="absolute-voice-routing-status__grid">${model.rows.map((row) => `<div>
827
+ <span>${escapeHtml3(row.label)}</span>
828
+ <strong>${escapeHtml3(row.value)}</strong>
829
+ </div>`).join("")}</div>` : '<p class="absolute-voice-routing-status__empty">Start a voice session to see the selected provider.</p>';
830
+ return `<section class="absolute-voice-routing-status absolute-voice-routing-status--${escapeHtml3(model.status)}">
831
+ <header class="absolute-voice-routing-status__header">
832
+ <span class="absolute-voice-routing-status__eyebrow">${escapeHtml3(model.title)}</span>
833
+ <strong class="absolute-voice-routing-status__label">${escapeHtml3(model.label)}</strong>
834
+ </header>
835
+ <p class="absolute-voice-routing-status__description">${escapeHtml3(model.description)}</p>
836
+ ${rows}
837
+ ${model.error ? `<p class="absolute-voice-routing-status__error">${escapeHtml3(model.error)}</p>` : ""}
838
+ </section>`;
839
+ };
840
+ 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}`;
841
+ var mountVoiceRoutingStatus = (element, path = "/api/routing/latest", options = {}) => {
842
+ const store = createVoiceRoutingStatusStore(path, options);
843
+ const render = () => {
844
+ element.innerHTML = renderVoiceRoutingStatusHTML(store.getSnapshot(), options);
845
+ };
846
+ const unsubscribe = store.subscribe(render);
847
+ render();
848
+ store.refresh().catch(() => {});
849
+ return {
850
+ close: () => {
851
+ unsubscribe();
852
+ store.close();
853
+ },
854
+ refresh: store.refresh
855
+ };
856
+ };
857
+ var defineVoiceRoutingStatusElement = (tagName = "absolute-voice-routing-status") => {
858
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
859
+ return;
860
+ }
861
+ customElements.define(tagName, class AbsoluteVoiceRoutingStatusElement extends HTMLElement {
862
+ mounted;
863
+ connectedCallback() {
864
+ const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
865
+ this.mounted = mountVoiceRoutingStatus(this, this.getAttribute("path") ?? "/api/routing/latest", {
866
+ description: this.getAttribute("description") ?? undefined,
867
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
868
+ title: this.getAttribute("title") ?? undefined
869
+ });
870
+ }
871
+ disconnectedCallback() {
872
+ this.mounted?.close();
873
+ this.mounted = undefined;
874
+ }
875
+ });
876
+ };
877
+
878
+ // src/react/VoiceRoutingStatus.tsx
879
+ import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
880
+ var VoiceRoutingStatus = ({
881
+ className,
882
+ path = "/api/routing/latest",
883
+ ...options
884
+ }) => {
885
+ const snapshot = useVoiceRoutingStatus(path, options);
886
+ const model = createVoiceRoutingStatusViewModel(snapshot, options);
887
+ return /* @__PURE__ */ jsxDEV3("section", {
888
+ className: [
889
+ "absolute-voice-routing-status",
890
+ `absolute-voice-routing-status--${model.status}`,
891
+ className
892
+ ].filter(Boolean).join(" "),
893
+ children: [
894
+ /* @__PURE__ */ jsxDEV3("header", {
895
+ className: "absolute-voice-routing-status__header",
896
+ children: [
897
+ /* @__PURE__ */ jsxDEV3("span", {
898
+ className: "absolute-voice-routing-status__eyebrow",
899
+ children: model.title
900
+ }, undefined, false, undefined, this),
901
+ /* @__PURE__ */ jsxDEV3("strong", {
902
+ className: "absolute-voice-routing-status__label",
903
+ children: model.label
904
+ }, undefined, false, undefined, this)
905
+ ]
906
+ }, undefined, true, undefined, this),
907
+ /* @__PURE__ */ jsxDEV3("p", {
908
+ className: "absolute-voice-routing-status__description",
909
+ children: model.description
910
+ }, undefined, false, undefined, this),
911
+ model.rows.length ? /* @__PURE__ */ jsxDEV3("div", {
912
+ className: "absolute-voice-routing-status__grid",
913
+ children: model.rows.map((row) => /* @__PURE__ */ jsxDEV3("div", {
914
+ children: [
915
+ /* @__PURE__ */ jsxDEV3("span", {
916
+ children: row.label
917
+ }, undefined, false, undefined, this),
918
+ /* @__PURE__ */ jsxDEV3("strong", {
919
+ children: row.value
920
+ }, undefined, false, undefined, this)
921
+ ]
922
+ }, row.label, true, undefined, this))
923
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV3("p", {
924
+ className: "absolute-voice-routing-status__empty",
925
+ children: "Start a voice session to see the selected provider."
926
+ }, undefined, false, undefined, this),
927
+ model.error ? /* @__PURE__ */ jsxDEV3("p", {
928
+ className: "absolute-voice-routing-status__error",
929
+ children: model.error
930
+ }, undefined, false, undefined, this) : null
931
+ ]
932
+ }, undefined, true, undefined, this);
933
+ };
934
+ // src/react/useVoiceStream.tsx
935
+ import { useEffect as useEffect4, useRef as useRef4, useSyncExternalStore as useSyncExternalStore4 } from "react";
936
+
75
937
  // src/client/actions.ts
76
938
  var normalizeErrorMessage = (value) => {
77
939
  if (typeof value === "string" && value.trim()) {
@@ -120,6 +982,12 @@ var serverMessageToAction = (message) => {
120
982
  sessionId: message.sessionId,
121
983
  type: "complete"
122
984
  };
985
+ case "call_lifecycle":
986
+ return {
987
+ event: message.event,
988
+ sessionId: message.sessionId,
989
+ type: "call_lifecycle"
990
+ };
123
991
  case "error":
124
992
  return {
125
993
  message: normalizeErrorMessage(message.message),
@@ -163,7 +1031,7 @@ var DEFAULT_SCENARIO_QUERY_PARAM = "scenarioId";
163
1031
  var noop = () => {};
164
1032
  var noopUnsubscribe = () => noop;
165
1033
  var NOOP_CONNECTION = {
166
- start: () => {},
1034
+ callControl: noop,
167
1035
  close: noop,
168
1036
  endTurn: noop,
169
1037
  getReadyState: () => WS_CLOSED,
@@ -171,6 +1039,7 @@ var NOOP_CONNECTION = {
171
1039
  getSessionId: () => "",
172
1040
  send: noop,
173
1041
  sendAudio: noop,
1042
+ start: () => {},
174
1043
  subscribe: noopUnsubscribe
175
1044
  };
176
1045
  var createSessionId = () => crypto.randomUUID();
@@ -192,6 +1061,7 @@ var isVoiceServerMessage = (value) => {
192
1061
  switch (value.type) {
193
1062
  case "audio":
194
1063
  case "assistant":
1064
+ case "call_lifecycle":
195
1065
  case "complete":
196
1066
  case "error":
197
1067
  case "final":
@@ -332,6 +1202,12 @@ var createVoiceConnection = (path, options = {}) => {
332
1202
  const endTurn = () => {
333
1203
  send({ type: "end_turn" });
334
1204
  };
1205
+ const callControl = (message) => {
1206
+ send({
1207
+ ...message,
1208
+ type: "call_control"
1209
+ });
1210
+ };
335
1211
  const close = () => {
336
1212
  clearTimers();
337
1213
  if (state.ws) {
@@ -349,7 +1225,7 @@ var createVoiceConnection = (path, options = {}) => {
349
1225
  };
350
1226
  connect();
351
1227
  return {
352
- start,
1228
+ callControl,
353
1229
  close,
354
1230
  endTurn,
355
1231
  getReadyState: () => state.ws?.readyState ?? WS_CLOSED,
@@ -357,6 +1233,7 @@ var createVoiceConnection = (path, options = {}) => {
357
1233
  getSessionId: () => state.sessionId,
358
1234
  send,
359
1235
  sendAudio,
1236
+ start,
360
1237
  subscribe
361
1238
  };
362
1239
  };
@@ -365,6 +1242,7 @@ var createVoiceConnection = (path, options = {}) => {
365
1242
  var createInitialState = () => ({
366
1243
  assistantAudio: [],
367
1244
  assistantTexts: [],
1245
+ call: null,
368
1246
  error: null,
369
1247
  isConnected: false,
370
1248
  scenarioId: null,
@@ -408,6 +1286,20 @@ var createVoiceStreamStore = () => {
408
1286
  status: "completed"
409
1287
  };
410
1288
  break;
1289
+ case "call_lifecycle":
1290
+ state = {
1291
+ ...state,
1292
+ call: {
1293
+ ...state.call,
1294
+ disposition: action.event.type === "end" ? action.event.disposition : state.call?.disposition,
1295
+ endedAt: action.event.type === "end" ? action.event.at : state.call?.endedAt,
1296
+ events: [...state.call?.events ?? [], action.event],
1297
+ lastEventAt: action.event.at,
1298
+ startedAt: state.call?.startedAt ?? action.event.at
1299
+ },
1300
+ sessionId: action.sessionId
1301
+ };
1302
+ break;
411
1303
  case "connected":
412
1304
  state = {
413
1305
  ...state,
@@ -494,6 +1386,9 @@ var createVoiceStream = (path, options = {}) => {
494
1386
  }
495
1387
  });
496
1388
  return {
1389
+ callControl(message) {
1390
+ connection.callControl(message);
1391
+ },
497
1392
  close() {
498
1393
  unsubscribeConnection();
499
1394
  connection.close();
@@ -537,6 +1432,9 @@ var createVoiceStream = (path, options = {}) => {
537
1432
  get assistantAudio() {
538
1433
  return store.getSnapshot().assistantAudio;
539
1434
  },
1435
+ get call() {
1436
+ return store.getSnapshot().call;
1437
+ },
540
1438
  sendAudio(audio) {
541
1439
  connection.sendAudio(audio);
542
1440
  },
@@ -553,6 +1451,7 @@ var createVoiceStream = (path, options = {}) => {
553
1451
  var EMPTY_SNAPSHOT = {
554
1452
  assistantAudio: [],
555
1453
  assistantTexts: [],
1454
+ call: null,
556
1455
  error: null,
557
1456
  isConnected: false,
558
1457
  partial: "",
@@ -561,22 +1460,23 @@ var EMPTY_SNAPSHOT = {
561
1460
  turns: []
562
1461
  };
563
1462
  var useVoiceStream = (path, options = {}) => {
564
- const streamRef = useRef(null);
1463
+ const streamRef = useRef4(null);
565
1464
  if (!streamRef.current) {
566
1465
  streamRef.current = createVoiceStream(path, options);
567
1466
  }
568
1467
  const stream = streamRef.current;
569
- useEffect(() => () => stream.close(), [stream]);
570
- const snapshot = useSyncExternalStore(stream.subscribe, stream.getSnapshot, stream.getServerSnapshot) ?? EMPTY_SNAPSHOT;
1468
+ useEffect4(() => () => stream.close(), [stream]);
1469
+ const snapshot = useSyncExternalStore4(stream.subscribe, stream.getSnapshot, stream.getServerSnapshot) ?? EMPTY_SNAPSHOT;
571
1470
  return {
572
1471
  ...snapshot,
1472
+ callControl: (message) => stream.callControl(message),
573
1473
  close: () => stream.close(),
574
1474
  endTurn: () => stream.endTurn(),
575
1475
  sendAudio: (audio) => stream.sendAudio(audio)
576
1476
  };
577
1477
  };
578
1478
  // src/react/useVoiceController.tsx
579
- import { useEffect as useEffect2, useRef as useRef2, useSyncExternalStore as useSyncExternalStore2 } from "react";
1479
+ import { useEffect as useEffect5, useRef as useRef5, useSyncExternalStore as useSyncExternalStore5 } from "react";
580
1480
 
581
1481
  // src/client/htmx.ts
582
1482
  var DEFAULT_EVENT_NAME = "voice-refresh";
@@ -1040,6 +1940,7 @@ var resolveVoiceRuntimePreset = (name = "default") => {
1040
1940
  var createInitialState2 = (stream) => ({
1041
1941
  assistantAudio: [...stream.assistantAudio],
1042
1942
  assistantTexts: [...stream.assistantTexts],
1943
+ call: stream.call,
1043
1944
  error: stream.error,
1044
1945
  isConnected: stream.isConnected,
1045
1946
  isRecording: false,
@@ -1069,6 +1970,7 @@ var createVoiceController = (path, options = {}) => {
1069
1970
  ...state,
1070
1971
  assistantAudio: [...stream.assistantAudio],
1071
1972
  assistantTexts: [...stream.assistantTexts],
1973
+ call: stream.call,
1072
1974
  error: stream.error,
1073
1975
  isConnected: stream.isConnected,
1074
1976
  partial: stream.partial,
@@ -1146,6 +2048,7 @@ var createVoiceController = (path, options = {}) => {
1146
2048
  bindHTMX(bindingOptions) {
1147
2049
  return bindVoiceHTMX(stream, bindingOptions);
1148
2050
  },
2051
+ callControl: (message) => stream.callControl(message),
1149
2052
  close,
1150
2053
  endTurn: () => stream.endTurn(),
1151
2054
  get error() {
@@ -1198,6 +2101,9 @@ var createVoiceController = (path, options = {}) => {
1198
2101
  },
1199
2102
  get assistantAudio() {
1200
2103
  return state.assistantAudio;
2104
+ },
2105
+ get call() {
2106
+ return state.call;
1201
2107
  }
1202
2108
  };
1203
2109
  };
@@ -1206,6 +2112,7 @@ var createVoiceController = (path, options = {}) => {
1206
2112
  var EMPTY_SNAPSHOT2 = {
1207
2113
  assistantAudio: [],
1208
2114
  assistantTexts: [],
2115
+ call: null,
1209
2116
  error: null,
1210
2117
  isConnected: false,
1211
2118
  isRecording: false,
@@ -1216,16 +2123,17 @@ var EMPTY_SNAPSHOT2 = {
1216
2123
  turns: []
1217
2124
  };
1218
2125
  var useVoiceController = (path, options = {}) => {
1219
- const controllerRef = useRef2(null);
2126
+ const controllerRef = useRef5(null);
1220
2127
  if (!controllerRef.current) {
1221
2128
  controllerRef.current = createVoiceController(path, options);
1222
2129
  }
1223
2130
  const controller = controllerRef.current;
1224
- useEffect2(() => () => controller.close(), [controller]);
1225
- const snapshot = useSyncExternalStore2(controller.subscribe, controller.getSnapshot, controller.getServerSnapshot) ?? EMPTY_SNAPSHOT2;
2131
+ useEffect5(() => () => controller.close(), [controller]);
2132
+ const snapshot = useSyncExternalStore5(controller.subscribe, controller.getSnapshot, controller.getServerSnapshot) ?? EMPTY_SNAPSHOT2;
1226
2133
  return {
1227
2134
  ...snapshot,
1228
2135
  bindHTMX: controller.bindHTMX,
2136
+ callControl: (message) => controller.callControl(message),
1229
2137
  close: () => controller.close(),
1230
2138
  endTurn: () => controller.endTurn(),
1231
2139
  sendAudio: (audio) => controller.sendAudio(audio),
@@ -1234,7 +2142,112 @@ var useVoiceController = (path, options = {}) => {
1234
2142
  toggleRecording: () => controller.toggleRecording()
1235
2143
  };
1236
2144
  };
2145
+ // src/react/useVoiceWorkflowStatus.tsx
2146
+ import { useEffect as useEffect6, useRef as useRef6, useSyncExternalStore as useSyncExternalStore6 } from "react";
2147
+
2148
+ // src/client/workflowStatus.ts
2149
+ var fetchVoiceWorkflowStatus = async (path = "/evals/scenarios/json", options = {}) => {
2150
+ const fetchImpl = options.fetch ?? globalThis.fetch;
2151
+ const response = await fetchImpl(path);
2152
+ if (!response.ok) {
2153
+ throw new Error(`Voice workflow status failed: HTTP ${response.status}`);
2154
+ }
2155
+ return await response.json();
2156
+ };
2157
+ var createVoiceWorkflowStatusStore = (path = "/evals/scenarios/json", options = {}) => {
2158
+ const listeners = new Set;
2159
+ let closed = false;
2160
+ let timer;
2161
+ let snapshot = {
2162
+ error: null,
2163
+ isLoading: false
2164
+ };
2165
+ const emit = () => {
2166
+ for (const listener of listeners) {
2167
+ listener();
2168
+ }
2169
+ };
2170
+ const refresh = async () => {
2171
+ if (closed) {
2172
+ return snapshot.report;
2173
+ }
2174
+ snapshot = {
2175
+ ...snapshot,
2176
+ error: null,
2177
+ isLoading: true
2178
+ };
2179
+ emit();
2180
+ try {
2181
+ const report = await fetchVoiceWorkflowStatus(path, options);
2182
+ snapshot = {
2183
+ error: null,
2184
+ isLoading: false,
2185
+ report,
2186
+ updatedAt: Date.now()
2187
+ };
2188
+ emit();
2189
+ return report;
2190
+ } catch (error) {
2191
+ snapshot = {
2192
+ ...snapshot,
2193
+ error: error instanceof Error ? error.message : String(error),
2194
+ isLoading: false
2195
+ };
2196
+ emit();
2197
+ throw error;
2198
+ }
2199
+ };
2200
+ const close = () => {
2201
+ closed = true;
2202
+ if (timer) {
2203
+ clearInterval(timer);
2204
+ timer = undefined;
2205
+ }
2206
+ listeners.clear();
2207
+ };
2208
+ if (typeof window !== "undefined" && options.intervalMs && options.intervalMs > 0) {
2209
+ timer = setInterval(() => {
2210
+ refresh().catch(() => {});
2211
+ }, options.intervalMs);
2212
+ }
2213
+ return {
2214
+ close,
2215
+ getServerSnapshot: () => snapshot,
2216
+ getSnapshot: () => snapshot,
2217
+ refresh,
2218
+ subscribe: (listener) => {
2219
+ listeners.add(listener);
2220
+ return () => {
2221
+ listeners.delete(listener);
2222
+ };
2223
+ }
2224
+ };
2225
+ };
2226
+
2227
+ // src/react/useVoiceWorkflowStatus.tsx
2228
+ var useVoiceWorkflowStatus = (path = "/evals/scenarios/json", options = {}) => {
2229
+ const storeRef = useRef6(null);
2230
+ if (!storeRef.current) {
2231
+ storeRef.current = createVoiceWorkflowStatusStore(path, options);
2232
+ }
2233
+ const store = storeRef.current;
2234
+ useEffect6(() => {
2235
+ store.refresh().catch(() => {});
2236
+ return () => store.close();
2237
+ }, [store]);
2238
+ return {
2239
+ ...useSyncExternalStore6(store.subscribe, store.getSnapshot, store.getServerSnapshot),
2240
+ refresh: store.refresh
2241
+ };
2242
+ };
1237
2243
  export {
2244
+ useVoiceWorkflowStatus,
1238
2245
  useVoiceStream,
1239
- useVoiceController
2246
+ useVoiceRoutingStatus,
2247
+ useVoiceProviderStatus,
2248
+ useVoiceController,
2249
+ useVoiceAppKitStatus,
2250
+ VoiceRoutingStatus,
2251
+ VoiceProviderStatus,
2252
+ VoiceOpsStatus
1240
2253
  };