@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,6 +69,224 @@ 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
+ };
72
290
  // src/client/actions.ts
73
291
  var normalizeErrorMessage = (value) => {
74
292
  if (typeof value === "string" && value.trim()) {
@@ -117,6 +335,12 @@ var serverMessageToAction = (message) => {
117
335
  sessionId: message.sessionId,
118
336
  type: "complete"
119
337
  };
338
+ case "call_lifecycle":
339
+ return {
340
+ event: message.event,
341
+ sessionId: message.sessionId,
342
+ type: "call_lifecycle"
343
+ };
120
344
  case "error":
121
345
  return {
122
346
  message: normalizeErrorMessage(message.message),
@@ -160,7 +384,7 @@ var DEFAULT_SCENARIO_QUERY_PARAM = "scenarioId";
160
384
  var noop = () => {};
161
385
  var noopUnsubscribe = () => noop;
162
386
  var NOOP_CONNECTION = {
163
- start: () => {},
387
+ callControl: noop,
164
388
  close: noop,
165
389
  endTurn: noop,
166
390
  getReadyState: () => WS_CLOSED,
@@ -168,6 +392,7 @@ var NOOP_CONNECTION = {
168
392
  getSessionId: () => "",
169
393
  send: noop,
170
394
  sendAudio: noop,
395
+ start: () => {},
171
396
  subscribe: noopUnsubscribe
172
397
  };
173
398
  var createSessionId = () => crypto.randomUUID();
@@ -189,6 +414,7 @@ var isVoiceServerMessage = (value) => {
189
414
  switch (value.type) {
190
415
  case "audio":
191
416
  case "assistant":
417
+ case "call_lifecycle":
192
418
  case "complete":
193
419
  case "error":
194
420
  case "final":
@@ -329,6 +555,12 @@ var createVoiceConnection = (path, options = {}) => {
329
555
  const endTurn = () => {
330
556
  send({ type: "end_turn" });
331
557
  };
558
+ const callControl = (message) => {
559
+ send({
560
+ ...message,
561
+ type: "call_control"
562
+ });
563
+ };
332
564
  const close = () => {
333
565
  clearTimers();
334
566
  if (state.ws) {
@@ -346,7 +578,7 @@ var createVoiceConnection = (path, options = {}) => {
346
578
  };
347
579
  connect();
348
580
  return {
349
- start,
581
+ callControl,
350
582
  close,
351
583
  endTurn,
352
584
  getReadyState: () => state.ws?.readyState ?? WS_CLOSED,
@@ -354,6 +586,7 @@ var createVoiceConnection = (path, options = {}) => {
354
586
  getSessionId: () => state.sessionId,
355
587
  send,
356
588
  sendAudio,
589
+ start,
357
590
  subscribe
358
591
  };
359
592
  };
@@ -362,6 +595,7 @@ var createVoiceConnection = (path, options = {}) => {
362
595
  var createInitialState = () => ({
363
596
  assistantAudio: [],
364
597
  assistantTexts: [],
598
+ call: null,
365
599
  error: null,
366
600
  isConnected: false,
367
601
  scenarioId: null,
@@ -405,6 +639,20 @@ var createVoiceStreamStore = () => {
405
639
  status: "completed"
406
640
  };
407
641
  break;
642
+ case "call_lifecycle":
643
+ state = {
644
+ ...state,
645
+ call: {
646
+ ...state.call,
647
+ disposition: action.event.type === "end" ? action.event.disposition : state.call?.disposition,
648
+ endedAt: action.event.type === "end" ? action.event.at : state.call?.endedAt,
649
+ events: [...state.call?.events ?? [], action.event],
650
+ lastEventAt: action.event.at,
651
+ startedAt: state.call?.startedAt ?? action.event.at
652
+ },
653
+ sessionId: action.sessionId
654
+ };
655
+ break;
408
656
  case "connected":
409
657
  state = {
410
658
  ...state,
@@ -491,6 +739,9 @@ var createVoiceStream = (path, options = {}) => {
491
739
  }
492
740
  });
493
741
  return {
742
+ callControl(message) {
743
+ connection.callControl(message);
744
+ },
494
745
  close() {
495
746
  unsubscribeConnection();
496
747
  connection.close();
@@ -534,6 +785,9 @@ var createVoiceStream = (path, options = {}) => {
534
785
  get assistantAudio() {
535
786
  return store.getSnapshot().assistantAudio;
536
787
  },
788
+ get call() {
789
+ return store.getSnapshot().call;
790
+ },
537
791
  sendAudio(audio) {
538
792
  connection.sendAudio(audio);
539
793
  },
@@ -548,6 +802,474 @@ var createVoiceStream = (path, options = {}) => {
548
802
 
549
803
  // src/svelte/createVoiceStream.ts
550
804
  var createVoiceStream2 = (path, options = {}) => createVoiceStream(path, options);
805
+ // src/client/providerStatus.ts
806
+ var fetchVoiceProviderStatus = async (path = "/api/provider-status", options = {}) => {
807
+ const fetchImpl = options.fetch ?? globalThis.fetch;
808
+ const response = await fetchImpl(path);
809
+ if (!response.ok) {
810
+ throw new Error(`Voice provider status failed: HTTP ${response.status}`);
811
+ }
812
+ return await response.json();
813
+ };
814
+ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {}) => {
815
+ const listeners = new Set;
816
+ let closed = false;
817
+ let timer;
818
+ let snapshot = {
819
+ error: null,
820
+ isLoading: false,
821
+ providers: []
822
+ };
823
+ const emit = () => {
824
+ for (const listener of listeners) {
825
+ listener();
826
+ }
827
+ };
828
+ const refresh = async () => {
829
+ if (closed) {
830
+ return snapshot.providers;
831
+ }
832
+ snapshot = {
833
+ ...snapshot,
834
+ error: null,
835
+ isLoading: true
836
+ };
837
+ emit();
838
+ try {
839
+ const providers = await fetchVoiceProviderStatus(path, options);
840
+ snapshot = {
841
+ error: null,
842
+ isLoading: false,
843
+ providers,
844
+ updatedAt: Date.now()
845
+ };
846
+ emit();
847
+ return providers;
848
+ } catch (error) {
849
+ snapshot = {
850
+ ...snapshot,
851
+ error: error instanceof Error ? error.message : String(error),
852
+ isLoading: false
853
+ };
854
+ emit();
855
+ throw error;
856
+ }
857
+ };
858
+ const close = () => {
859
+ closed = true;
860
+ if (timer) {
861
+ clearInterval(timer);
862
+ timer = undefined;
863
+ }
864
+ listeners.clear();
865
+ };
866
+ if (options.intervalMs && options.intervalMs > 0) {
867
+ timer = setInterval(() => {
868
+ refresh().catch(() => {});
869
+ }, options.intervalMs);
870
+ }
871
+ return {
872
+ close,
873
+ getServerSnapshot: () => snapshot,
874
+ getSnapshot: () => snapshot,
875
+ refresh,
876
+ subscribe: (listener) => {
877
+ listeners.add(listener);
878
+ return () => {
879
+ listeners.delete(listener);
880
+ };
881
+ }
882
+ };
883
+ };
884
+
885
+ // src/client/providerStatusWidget.ts
886
+ var DEFAULT_TITLE2 = "Voice Providers";
887
+ var DEFAULT_DESCRIPTION2 = "Live provider health, fallback counts, latency, and suppression state from your self-hosted trace store.";
888
+ var escapeHtml2 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
889
+ var formatProvider = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
890
+ var formatStatus = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
891
+ var formatLatency = (value) => typeof value === "number" ? `${value}ms` : "No samples";
892
+ var formatSuppression = (value) => typeof value === "number" ? `${Math.ceil(value / 1000)}s` : "None";
893
+ var getProviderDetail = (provider) => {
894
+ if (provider.status === "suppressed") {
895
+ return provider.lastError ? `Suppressed for ${formatSuppression(provider.suppressionRemainingMs)} after ${provider.lastError}.` : `Suppressed for ${formatSuppression(provider.suppressionRemainingMs)}.`;
896
+ }
897
+ if (provider.status === "recoverable") {
898
+ return "Cooldown expired; ready for recovery traffic.";
899
+ }
900
+ if (provider.status === "rate-limited") {
901
+ return "Rate limit detected; router should avoid this provider.";
902
+ }
903
+ if (provider.status === "degraded") {
904
+ return provider.lastError ?? "Recent provider errors detected.";
905
+ }
906
+ if (provider.status === "healthy") {
907
+ return provider.recommended ? "Healthy and currently recommended." : "Healthy and available for routing.";
908
+ }
909
+ return "No provider traffic observed yet.";
910
+ };
911
+ var isWarningStatus = (status) => status === "degraded" || status === "rate-limited" || status === "recoverable" || status === "suppressed";
912
+ var createVoiceProviderStatusViewModel = (snapshot, options = {}) => {
913
+ const providers = snapshot.providers.map((provider) => ({
914
+ ...provider,
915
+ detail: getProviderDetail(provider),
916
+ label: `${formatProvider(provider.provider)}${provider.recommended ? " recommended" : ""}`,
917
+ rows: [
918
+ { label: "Runs", value: String(provider.runCount) },
919
+ { label: "Avg latency", value: formatLatency(provider.averageElapsedMs) },
920
+ { label: "Errors", value: String(provider.errorCount) },
921
+ { label: "Timeouts", value: String(provider.timeoutCount) },
922
+ { label: "Fallbacks", value: String(provider.fallbackCount) },
923
+ {
924
+ label: "Suppression",
925
+ value: formatSuppression(provider.suppressionRemainingMs)
926
+ }
927
+ ]
928
+ }));
929
+ const warningCount = providers.filter((provider) => isWarningStatus(provider.status)).length;
930
+ const healthyCount = providers.filter((provider) => provider.status === "healthy").length;
931
+ return {
932
+ description: options.description ?? DEFAULT_DESCRIPTION2,
933
+ error: snapshot.error,
934
+ isLoading: snapshot.isLoading,
935
+ label: snapshot.error ? "Unavailable" : providers.length ? warningCount > 0 ? `${warningCount} needs attention` : `${healthyCount} healthy` : snapshot.isLoading ? "Checking" : "No provider traffic",
936
+ providers,
937
+ status: snapshot.error ? "error" : providers.length ? warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
938
+ title: options.title ?? DEFAULT_TITLE2,
939
+ updatedAt: snapshot.updatedAt
940
+ };
941
+ };
942
+ var renderVoiceProviderStatusHTML = (snapshot, options = {}) => {
943
+ const model = createVoiceProviderStatusViewModel(snapshot, options);
944
+ 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)}">
945
+ <header>
946
+ <strong>${escapeHtml2(provider.label)}</strong>
947
+ <span>${escapeHtml2(formatStatus(provider.status))}</span>
948
+ </header>
949
+ <p>${escapeHtml2(provider.detail)}</p>
950
+ <dl>${provider.rows.map((row) => `<div>
951
+ <dt>${escapeHtml2(row.label)}</dt>
952
+ <dd>${escapeHtml2(row.value)}</dd>
953
+ </div>`).join("")}</dl>
954
+ </article>`).join("")}</div>` : '<p class="absolute-voice-provider-status__empty">Run voice traffic to see provider health.</p>';
955
+ return `<section class="absolute-voice-provider-status absolute-voice-provider-status--${escapeHtml2(model.status)}">
956
+ <header class="absolute-voice-provider-status__header">
957
+ <span class="absolute-voice-provider-status__eyebrow">${escapeHtml2(model.title)}</span>
958
+ <strong class="absolute-voice-provider-status__label">${escapeHtml2(model.label)}</strong>
959
+ </header>
960
+ <p class="absolute-voice-provider-status__description">${escapeHtml2(model.description)}</p>
961
+ ${providers}
962
+ ${model.error ? `<p class="absolute-voice-provider-status__error">${escapeHtml2(model.error)}</p>` : ""}
963
+ </section>`;
964
+ };
965
+ 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}`;
966
+ var mountVoiceProviderStatus = (element, path = "/api/provider-status", options = {}) => {
967
+ const store = createVoiceProviderStatusStore(path, options);
968
+ const render = () => {
969
+ element.innerHTML = renderVoiceProviderStatusHTML(store.getSnapshot(), options);
970
+ };
971
+ const unsubscribe = store.subscribe(render);
972
+ render();
973
+ store.refresh().catch(() => {});
974
+ return {
975
+ close: () => {
976
+ unsubscribe();
977
+ store.close();
978
+ },
979
+ refresh: store.refresh
980
+ };
981
+ };
982
+ var defineVoiceProviderStatusElement = (tagName = "absolute-voice-provider-status") => {
983
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
984
+ return;
985
+ }
986
+ customElements.define(tagName, class AbsoluteVoiceProviderStatusElement extends HTMLElement {
987
+ mounted;
988
+ connectedCallback() {
989
+ const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
990
+ this.mounted = mountVoiceProviderStatus(this, this.getAttribute("path") ?? "/api/provider-status", {
991
+ description: this.getAttribute("description") ?? undefined,
992
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
993
+ title: this.getAttribute("title") ?? undefined
994
+ });
995
+ }
996
+ disconnectedCallback() {
997
+ this.mounted?.close();
998
+ this.mounted = undefined;
999
+ }
1000
+ });
1001
+ };
1002
+
1003
+ // src/svelte/createVoiceProviderStatus.ts
1004
+ var createVoiceProviderStatus = (path = "/api/provider-status", options = {}) => {
1005
+ const store = createVoiceProviderStatusStore(path, options);
1006
+ return {
1007
+ ...store,
1008
+ getHTML: () => renderVoiceProviderStatusHTML(store.getSnapshot(), options),
1009
+ getViewModel: () => createVoiceProviderStatusViewModel(store.getSnapshot(), options)
1010
+ };
1011
+ };
1012
+ // src/client/routingStatus.ts
1013
+ var fetchVoiceRoutingStatus = async (path = "/api/routing/latest", options = {}) => {
1014
+ const fetchImpl = options.fetch ?? globalThis.fetch;
1015
+ const response = await fetchImpl(path);
1016
+ if (!response.ok) {
1017
+ throw new Error(`Voice routing status failed: HTTP ${response.status}`);
1018
+ }
1019
+ return await response.json();
1020
+ };
1021
+ var createVoiceRoutingStatusStore = (path = "/api/routing/latest", options = {}) => {
1022
+ const listeners = new Set;
1023
+ let closed = false;
1024
+ let timer;
1025
+ let snapshot = {
1026
+ decision: null,
1027
+ error: null,
1028
+ isLoading: false
1029
+ };
1030
+ const emit = () => {
1031
+ for (const listener of listeners) {
1032
+ listener();
1033
+ }
1034
+ };
1035
+ const refresh = async () => {
1036
+ if (closed) {
1037
+ return snapshot.decision;
1038
+ }
1039
+ snapshot = {
1040
+ ...snapshot,
1041
+ error: null,
1042
+ isLoading: true
1043
+ };
1044
+ emit();
1045
+ try {
1046
+ const decision = await fetchVoiceRoutingStatus(path, options);
1047
+ snapshot = {
1048
+ decision,
1049
+ error: null,
1050
+ isLoading: false,
1051
+ updatedAt: Date.now()
1052
+ };
1053
+ emit();
1054
+ return decision;
1055
+ } catch (error) {
1056
+ snapshot = {
1057
+ ...snapshot,
1058
+ error: error instanceof Error ? error.message : String(error),
1059
+ isLoading: false
1060
+ };
1061
+ emit();
1062
+ throw error;
1063
+ }
1064
+ };
1065
+ const close = () => {
1066
+ closed = true;
1067
+ if (timer) {
1068
+ clearInterval(timer);
1069
+ timer = undefined;
1070
+ }
1071
+ listeners.clear();
1072
+ };
1073
+ if (options.intervalMs && options.intervalMs > 0) {
1074
+ timer = setInterval(() => {
1075
+ refresh().catch(() => {});
1076
+ }, options.intervalMs);
1077
+ }
1078
+ return {
1079
+ close,
1080
+ getServerSnapshot: () => snapshot,
1081
+ getSnapshot: () => snapshot,
1082
+ refresh,
1083
+ subscribe: (listener) => {
1084
+ listeners.add(listener);
1085
+ return () => {
1086
+ listeners.delete(listener);
1087
+ };
1088
+ }
1089
+ };
1090
+ };
1091
+
1092
+ // src/client/routingStatusWidget.ts
1093
+ var DEFAULT_TITLE3 = "Voice Routing";
1094
+ var DEFAULT_DESCRIPTION3 = "Latest provider routing decision from the self-hosted trace store.";
1095
+ var escapeHtml3 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
1096
+ var formatValue = (value, fallback = "None") => typeof value === "string" && value.trim() ? value : typeof value === "number" && Number.isFinite(value) ? String(value) : fallback;
1097
+ var createVoiceRoutingStatusViewModel = (snapshot, options = {}) => {
1098
+ const decision = snapshot.decision;
1099
+ const rows = decision ? [
1100
+ { label: "Kind", value: decision.kind.toUpperCase() },
1101
+ { label: "Policy", value: formatValue(decision.routing, "Unknown") },
1102
+ { label: "Provider", value: formatValue(decision.provider, "Unknown") },
1103
+ {
1104
+ label: "Selected",
1105
+ value: formatValue(decision.selectedProvider, "Unknown")
1106
+ },
1107
+ {
1108
+ label: "Fallback",
1109
+ value: formatValue(decision.fallbackProvider)
1110
+ },
1111
+ { label: "Status", value: formatValue(decision.status, "unknown") },
1112
+ {
1113
+ label: "Latency budget",
1114
+ value: typeof decision.latencyBudgetMs === "number" ? `${decision.latencyBudgetMs}ms` : "None"
1115
+ }
1116
+ ] : [];
1117
+ return {
1118
+ decision,
1119
+ description: options.description ?? DEFAULT_DESCRIPTION3,
1120
+ error: snapshot.error,
1121
+ isLoading: snapshot.isLoading,
1122
+ label: snapshot.error ? "Unavailable" : decision ? `${formatValue(decision.kind).toUpperCase()} ${formatValue(decision.status, "unknown")}` : snapshot.isLoading ? "Checking" : "No routing yet",
1123
+ rows,
1124
+ status: snapshot.error ? "error" : decision ? "ready" : snapshot.isLoading ? "loading" : "empty",
1125
+ title: options.title ?? DEFAULT_TITLE3,
1126
+ updatedAt: snapshot.updatedAt
1127
+ };
1128
+ };
1129
+ var renderVoiceRoutingStatusHTML = (snapshot, options = {}) => {
1130
+ const model = createVoiceRoutingStatusViewModel(snapshot, options);
1131
+ const rows = model.rows.length ? `<div class="absolute-voice-routing-status__grid">${model.rows.map((row) => `<div>
1132
+ <span>${escapeHtml3(row.label)}</span>
1133
+ <strong>${escapeHtml3(row.value)}</strong>
1134
+ </div>`).join("")}</div>` : '<p class="absolute-voice-routing-status__empty">Start a voice session to see the selected provider.</p>';
1135
+ return `<section class="absolute-voice-routing-status absolute-voice-routing-status--${escapeHtml3(model.status)}">
1136
+ <header class="absolute-voice-routing-status__header">
1137
+ <span class="absolute-voice-routing-status__eyebrow">${escapeHtml3(model.title)}</span>
1138
+ <strong class="absolute-voice-routing-status__label">${escapeHtml3(model.label)}</strong>
1139
+ </header>
1140
+ <p class="absolute-voice-routing-status__description">${escapeHtml3(model.description)}</p>
1141
+ ${rows}
1142
+ ${model.error ? `<p class="absolute-voice-routing-status__error">${escapeHtml3(model.error)}</p>` : ""}
1143
+ </section>`;
1144
+ };
1145
+ 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}`;
1146
+ var mountVoiceRoutingStatus = (element, path = "/api/routing/latest", options = {}) => {
1147
+ const store = createVoiceRoutingStatusStore(path, options);
1148
+ const render = () => {
1149
+ element.innerHTML = renderVoiceRoutingStatusHTML(store.getSnapshot(), options);
1150
+ };
1151
+ const unsubscribe = store.subscribe(render);
1152
+ render();
1153
+ store.refresh().catch(() => {});
1154
+ return {
1155
+ close: () => {
1156
+ unsubscribe();
1157
+ store.close();
1158
+ },
1159
+ refresh: store.refresh
1160
+ };
1161
+ };
1162
+ var defineVoiceRoutingStatusElement = (tagName = "absolute-voice-routing-status") => {
1163
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
1164
+ return;
1165
+ }
1166
+ customElements.define(tagName, class AbsoluteVoiceRoutingStatusElement extends HTMLElement {
1167
+ mounted;
1168
+ connectedCallback() {
1169
+ const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
1170
+ this.mounted = mountVoiceRoutingStatus(this, this.getAttribute("path") ?? "/api/routing/latest", {
1171
+ description: this.getAttribute("description") ?? undefined,
1172
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
1173
+ title: this.getAttribute("title") ?? undefined
1174
+ });
1175
+ }
1176
+ disconnectedCallback() {
1177
+ this.mounted?.close();
1178
+ this.mounted = undefined;
1179
+ }
1180
+ });
1181
+ };
1182
+
1183
+ // src/svelte/createVoiceRoutingStatus.ts
1184
+ var createVoiceRoutingStatus = (path = "/api/routing/latest", options = {}) => {
1185
+ const store = createVoiceRoutingStatusStore(path, options);
1186
+ return {
1187
+ ...store,
1188
+ getHTML: () => renderVoiceRoutingStatusHTML(store.getSnapshot(), options),
1189
+ getViewModel: () => createVoiceRoutingStatusViewModel(store.getSnapshot(), options)
1190
+ };
1191
+ };
1192
+ // src/client/workflowStatus.ts
1193
+ var fetchVoiceWorkflowStatus = async (path = "/evals/scenarios/json", options = {}) => {
1194
+ const fetchImpl = options.fetch ?? globalThis.fetch;
1195
+ const response = await fetchImpl(path);
1196
+ if (!response.ok) {
1197
+ throw new Error(`Voice workflow status failed: HTTP ${response.status}`);
1198
+ }
1199
+ return await response.json();
1200
+ };
1201
+ var createVoiceWorkflowStatusStore = (path = "/evals/scenarios/json", options = {}) => {
1202
+ const listeners = new Set;
1203
+ let closed = false;
1204
+ let timer;
1205
+ let snapshot = {
1206
+ error: null,
1207
+ isLoading: false
1208
+ };
1209
+ const emit = () => {
1210
+ for (const listener of listeners) {
1211
+ listener();
1212
+ }
1213
+ };
1214
+ const refresh = async () => {
1215
+ if (closed) {
1216
+ return snapshot.report;
1217
+ }
1218
+ snapshot = {
1219
+ ...snapshot,
1220
+ error: null,
1221
+ isLoading: true
1222
+ };
1223
+ emit();
1224
+ try {
1225
+ const report = await fetchVoiceWorkflowStatus(path, options);
1226
+ snapshot = {
1227
+ error: null,
1228
+ isLoading: false,
1229
+ report,
1230
+ updatedAt: Date.now()
1231
+ };
1232
+ emit();
1233
+ return report;
1234
+ } catch (error) {
1235
+ snapshot = {
1236
+ ...snapshot,
1237
+ error: error instanceof Error ? error.message : String(error),
1238
+ isLoading: false
1239
+ };
1240
+ emit();
1241
+ throw error;
1242
+ }
1243
+ };
1244
+ const close = () => {
1245
+ closed = true;
1246
+ if (timer) {
1247
+ clearInterval(timer);
1248
+ timer = undefined;
1249
+ }
1250
+ listeners.clear();
1251
+ };
1252
+ if (typeof window !== "undefined" && options.intervalMs && options.intervalMs > 0) {
1253
+ timer = setInterval(() => {
1254
+ refresh().catch(() => {});
1255
+ }, options.intervalMs);
1256
+ }
1257
+ return {
1258
+ close,
1259
+ getServerSnapshot: () => snapshot,
1260
+ getSnapshot: () => snapshot,
1261
+ refresh,
1262
+ subscribe: (listener) => {
1263
+ listeners.add(listener);
1264
+ return () => {
1265
+ listeners.delete(listener);
1266
+ };
1267
+ }
1268
+ };
1269
+ };
1270
+
1271
+ // src/svelte/createVoiceWorkflowStatus.ts
1272
+ var createVoiceWorkflowStatus = (path = "/evals/scenarios/json", options = {}) => createVoiceWorkflowStatusStore(path, options);
551
1273
  // src/client/htmx.ts
552
1274
  var DEFAULT_EVENT_NAME = "voice-refresh";
553
1275
  var DEFAULT_QUERY_PARAM = "sessionId";
@@ -1010,6 +1732,7 @@ var resolveVoiceRuntimePreset = (name = "default") => {
1010
1732
  var createInitialState2 = (stream) => ({
1011
1733
  assistantAudio: [...stream.assistantAudio],
1012
1734
  assistantTexts: [...stream.assistantTexts],
1735
+ call: stream.call,
1013
1736
  error: stream.error,
1014
1737
  isConnected: stream.isConnected,
1015
1738
  isRecording: false,
@@ -1039,6 +1762,7 @@ var createVoiceController = (path, options = {}) => {
1039
1762
  ...state,
1040
1763
  assistantAudio: [...stream.assistantAudio],
1041
1764
  assistantTexts: [...stream.assistantTexts],
1765
+ call: stream.call,
1042
1766
  error: stream.error,
1043
1767
  isConnected: stream.isConnected,
1044
1768
  partial: stream.partial,
@@ -1116,6 +1840,7 @@ var createVoiceController = (path, options = {}) => {
1116
1840
  bindHTMX(bindingOptions) {
1117
1841
  return bindVoiceHTMX(stream, bindingOptions);
1118
1842
  },
1843
+ callControl: (message) => stream.callControl(message),
1119
1844
  close,
1120
1845
  endTurn: () => stream.endTurn(),
1121
1846
  get error() {
@@ -1168,10 +1893,18 @@ var createVoiceController = (path, options = {}) => {
1168
1893
  },
1169
1894
  get assistantAudio() {
1170
1895
  return state.assistantAudio;
1896
+ },
1897
+ get call() {
1898
+ return state.call;
1171
1899
  }
1172
1900
  };
1173
1901
  };
1174
1902
  export {
1903
+ createVoiceWorkflowStatus,
1175
1904
  createVoiceStream2 as createVoiceStream,
1176
- createVoiceController
1905
+ createVoiceRoutingStatus,
1906
+ createVoiceProviderStatus,
1907
+ createVoiceOpsStatus,
1908
+ createVoiceController,
1909
+ createVoiceAppKitStatus
1177
1910
  };