@vanira/sdk 0.0.82

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.
@@ -0,0 +1,1185 @@
1
+ var G = Object.defineProperty;
2
+ var W = (a, e, t) => e in a ? G(a, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : a[e] = t;
3
+ var d = (a, e, t) => W(a, typeof e != "symbol" ? e + "" : e, t);
4
+ class H {
5
+ pause() {
6
+ }
7
+ cleanup() {
8
+ }
9
+ onEnded(e) {
10
+ }
11
+ }
12
+ class z {
13
+ createRemotePlayer(e) {
14
+ return new H();
15
+ }
16
+ }
17
+ class Y {
18
+ async getUserAudio(e) {
19
+ return navigator.mediaDevices.getUserMedia({
20
+ audio: e
21
+ });
22
+ }
23
+ stopStream(e) {
24
+ e.getTracks().forEach((t) => t.stop());
25
+ }
26
+ }
27
+ class q {
28
+ create(e) {
29
+ return new globalThis.RTCPeerConnection({
30
+ iceServers: e.iceServers,
31
+ iceTransportPolicy: e.iceTransportPolicy ?? "all"
32
+ });
33
+ }
34
+ }
35
+ class F {
36
+ bind(e, t) {
37
+ return e.onopen = () => t.onOpen(), e.onerror = (n) => t.onError(n), e.onmessage = (n) => {
38
+ if (typeof n.data == "string") {
39
+ t.onMessage({ text: n.data });
40
+ return;
41
+ }
42
+ if (n.data instanceof ArrayBuffer) {
43
+ try {
44
+ const i = new TextDecoder().decode(n.data), s = JSON.parse(i);
45
+ (s == null ? void 0 : s.event) === "client_tool_call" && t.onMessage({ text: i });
46
+ } catch {
47
+ }
48
+ return;
49
+ }
50
+ }, {
51
+ send(n) {
52
+ e.readyState === "open" && e.send(n);
53
+ },
54
+ isOpen() {
55
+ return e.readyState === "open";
56
+ },
57
+ close() {
58
+ e.close();
59
+ }
60
+ };
61
+ }
62
+ }
63
+ const X = "https://api.vanira.io", h = {
64
+ Form: "vanira_form",
65
+ Calendar: "vanira_calendar",
66
+ Navigate: "vanira_navigate",
67
+ Upload: "vanira_upload",
68
+ Camera: "vanira_camera",
69
+ HighlightElement: "vanira_highlight_element",
70
+ ClickElement: "vanira_click_element",
71
+ SelectOption: "vanira_select_option",
72
+ SetDate: "vanira_set_date",
73
+ TypeText: "vanira_type_text",
74
+ EraseText: "vanira_erase_text",
75
+ Draw: "vanira_draw",
76
+ EraseDraw: "vanira_erase_draw",
77
+ LiveVision: "vanira_live_vision",
78
+ CloseLiveCamera: "vanira_close_live_camera",
79
+ LiveScreen: "vanira_live_screen",
80
+ CloseLiveScreen: "vanira_close_live_screen",
81
+ ClipRegion: "vanira_clip_region",
82
+ TabScreenshot: "vanira_tab_screenshot",
83
+ ScreenInk: "vanira_screen_ink",
84
+ ScreenCursor: "vanira_screen_cursor",
85
+ ScreenClick: "vanira_screen_click",
86
+ ClearScreenOverlay: "vanira_clear_screen_overlay",
87
+ PageScan: "vanira_page_scan",
88
+ PointAt: "vanira_point_at",
89
+ OutlineTargets: "vanira_outline_targets",
90
+ ClearGuide: "vanira_clear_guide"
91
+ }, Z = Object.values(h), Q = new Set(Z);
92
+ function N(a) {
93
+ return !!a && Q.has(a);
94
+ }
95
+ const ee = /* @__PURE__ */ new Set([
96
+ h.ScreenInk,
97
+ h.ScreenCursor,
98
+ h.ScreenClick,
99
+ h.ClearScreenOverlay,
100
+ h.PointAt,
101
+ h.OutlineTargets,
102
+ h.ClearGuide
103
+ ]);
104
+ [
105
+ ...ee,
106
+ h.PageScan,
107
+ h.TabScreenshot
108
+ ];
109
+ function K(a) {
110
+ if (typeof a == "string")
111
+ try {
112
+ return JSON.parse(a);
113
+ } catch {
114
+ return {};
115
+ }
116
+ return a && typeof a == "object" ? a : {};
117
+ }
118
+ const V = {
119
+ type_on_whiteboard: h.TypeText,
120
+ write_on_whiteboard: h.TypeText,
121
+ write_on_white_board: h.TypeText,
122
+ dom_typing_simulation: h.TypeText,
123
+ erase_text: h.EraseText,
124
+ dom_erase: h.EraseText,
125
+ draw_on_board: h.Draw,
126
+ erase_drawing_board: h.EraseDraw,
127
+ open_live_camera: h.LiveVision,
128
+ live_vision: h.LiveVision,
129
+ close_live_camera: h.CloseLiveCamera,
130
+ stop_live_camera: h.CloseLiveCamera,
131
+ open_live_screen: h.LiveScreen,
132
+ close_live_screen: h.CloseLiveScreen,
133
+ screen_ink: h.ScreenInk,
134
+ screen_cursor: h.ScreenCursor,
135
+ screen_click: h.ScreenClick,
136
+ clear_screen_overlay: h.ClearScreenOverlay,
137
+ tab_screenshot: h.TabScreenshot,
138
+ capture_tab: h.TabScreenshot,
139
+ capture_tab_screenshot: h.TabScreenshot,
140
+ screenshot_tab: h.TabScreenshot,
141
+ page_scan: h.PageScan,
142
+ scan_page: h.PageScan,
143
+ point_at: h.PointAt,
144
+ outline_targets: h.OutlineTargets,
145
+ outline_elements: h.OutlineTargets,
146
+ clear_guide: h.ClearGuide
147
+ };
148
+ function B(a, e) {
149
+ const t = a.text_to_type ?? (e == null ? void 0 : e.text_to_type) ?? a.text ?? (e == null ? void 0 : e.text) ?? a.value ?? (e == null ? void 0 : e.value);
150
+ return typeof t == "string" && t.trim().length > 0;
151
+ }
152
+ function te(a, e) {
153
+ const t = a.fields ?? (e == null ? void 0 : e.fields);
154
+ return Array.isArray(t) ? t.length > 0 : typeof t == "string" ? t.split(",").map((n) => n.trim()).filter(Boolean).length > 0 : !1;
155
+ }
156
+ function ne(a, e) {
157
+ const t = a == null ? void 0 : a.preset_id;
158
+ if (typeof t == "string" && N(t))
159
+ return t;
160
+ const i = K(e).preset_id;
161
+ if (typeof i == "string" && N(i))
162
+ return i;
163
+ }
164
+ function ie(a, e, t) {
165
+ const n = K(e);
166
+ let i = ne(a, n);
167
+ if (i === h.Form && B(n, a) && !te(n, a))
168
+ return h.TypeText;
169
+ if (i) return i;
170
+ const s = String(t ?? "").trim().toLowerCase();
171
+ if (s && V[s])
172
+ return V[s];
173
+ if (B(n, a))
174
+ return h.TypeText;
175
+ }
176
+ const g = "vanira_latest_call_id", k = "vanira_prospect_id";
177
+ function C(a) {
178
+ return `vanira_latest_call_id_${a}`;
179
+ }
180
+ function I(a) {
181
+ return `vanira_prospect_id_${a}`;
182
+ }
183
+ function j(a) {
184
+ return `vanira_call_meta_${a}`;
185
+ }
186
+ function S(a, e) {
187
+ if (!a) return null;
188
+ try {
189
+ return a.getItem(e);
190
+ } catch {
191
+ return null;
192
+ }
193
+ }
194
+ function D(a) {
195
+ if (typeof window > "u" || !a) return null;
196
+ const e = [], t = S(window.localStorage, j(a));
197
+ if (t)
198
+ try {
199
+ const c = JSON.parse(t);
200
+ c != null && c.callId && e.push({
201
+ callId: c.callId,
202
+ prospectId: c.prospectId,
203
+ updatedAt: c.updatedAt || 0
204
+ });
205
+ } catch {
206
+ }
207
+ const n = (c, r, u) => {
208
+ c && e.push({
209
+ callId: c,
210
+ prospectId: r || void 0,
211
+ updatedAt: u
212
+ });
213
+ }, i = S(window.localStorage, I(a)), s = S(window.sessionStorage, I(a)), o = S(window.localStorage, k);
214
+ return n(
215
+ S(window.localStorage, C(a)),
216
+ i || o,
217
+ 2
218
+ ), n(
219
+ S(window.sessionStorage, C(a)),
220
+ s || o,
221
+ 1
222
+ ), n(S(window.localStorage, g), o, 0), n(S(window.sessionStorage, g), o, 0), e.length ? (e.sort((c, r) => r.updatedAt - c.updatedAt), e[0]) : null;
223
+ }
224
+ function ae(a, e, t) {
225
+ var i, s, o, c, r, u, p, l, _;
226
+ if (typeof window > "u" || !a || !e) return;
227
+ const n = {
228
+ callId: e,
229
+ prospectId: t,
230
+ updatedAt: Date.now()
231
+ };
232
+ try {
233
+ (i = window.localStorage) == null || i.setItem(j(a), JSON.stringify(n)), (s = window.localStorage) == null || s.setItem(C(a), e), (o = window.sessionStorage) == null || o.setItem(C(a), e), t && ((c = window.localStorage) == null || c.setItem(I(a), t), (r = window.sessionStorage) == null || r.setItem(I(a), t)), (u = window.localStorage) == null || u.setItem(g, e), (p = window.sessionStorage) == null || p.setItem(g, e), t && ((l = window.localStorage) == null || l.setItem(k, t), (_ = window.sessionStorage) == null || _.setItem(k, t));
234
+ } catch {
235
+ }
236
+ }
237
+ const se = /* @__PURE__ */ new Set([
238
+ "client_tool_call",
239
+ "clearAudio",
240
+ "client_tool_cancel"
241
+ ]);
242
+ function re(a) {
243
+ if (!a || typeof a != "object") return !1;
244
+ const e = a.event;
245
+ return typeof e == "string" && se.has(e);
246
+ }
247
+ const T = 16 * 1024, oe = 256 * 1024;
248
+ function E(a, e) {
249
+ return (a == null ? void 0 : a.readyState) === "open" && (e == null ? void 0 : e.readyState) === "open";
250
+ }
251
+ function ce(a, e) {
252
+ return {
253
+ control: (a == null ? void 0 : a.readyState) ?? "missing",
254
+ mediaBytes: (e == null ? void 0 : e.readyState) ?? "missing"
255
+ };
256
+ }
257
+ async function le(a, e, t = 8e3) {
258
+ if (E(a, e))
259
+ return !0;
260
+ const n = Date.now() + t;
261
+ for (; Date.now() < n; ) {
262
+ if (E(a, e))
263
+ return !0;
264
+ await new Promise((i) => setTimeout(i, 50));
265
+ }
266
+ return E(a, e);
267
+ }
268
+ async function $(a) {
269
+ for (; a.bufferedAmount > oe; )
270
+ await new Promise((e) => setTimeout(e, 10));
271
+ }
272
+ async function de(a) {
273
+ const {
274
+ bytes: e,
275
+ filename: t,
276
+ contentType: n,
277
+ reason: i,
278
+ message: s,
279
+ callId: o,
280
+ mediaBytesChannel: c,
281
+ sendControlEvent: r,
282
+ uploadId: u = crypto.randomUUID(),
283
+ uploadKind: p = "media"
284
+ } = a, l = e.byteLength, _ = l === 0 ? 0 : Math.ceil(l / T);
285
+ r("client_media_upload_start", {
286
+ data: {
287
+ upload_id: u,
288
+ call_id: o,
289
+ reason: i,
290
+ message: s,
291
+ filename: t,
292
+ content_type: n,
293
+ size: l,
294
+ chunk_count: _,
295
+ upload_kind: p
296
+ }
297
+ });
298
+ const m = new Uint8Array(e);
299
+ for (let f = 0; f < l; f += T) {
300
+ await $(c);
301
+ const v = Math.min(f + T, l);
302
+ c.send(m.subarray(f, v));
303
+ }
304
+ return await $(c), r("client_media_upload_complete", {
305
+ data: {
306
+ upload_id: u,
307
+ call_id: o,
308
+ reason: i,
309
+ message: s,
310
+ chunk_count: _,
311
+ upload_kind: p
312
+ }
313
+ }), { media_id: u, url: "", content_type: n };
314
+ }
315
+ async function he(a) {
316
+ return typeof a.arrayBuffer == "function" ? a.arrayBuffer() : new Response(a).arrayBuffer();
317
+ }
318
+ function ue(a) {
319
+ return null;
320
+ }
321
+ function y() {
322
+ return typeof window > "u" ? "/" : `${window.location.pathname}${window.location.search}${window.location.hash}` || "/";
323
+ }
324
+ class pe {
325
+ constructor(e) {
326
+ d(this, "active", !1);
327
+ d(this, "initialSent", !1);
328
+ d(this, "lastRoute", "");
329
+ d(this, "sendContext");
330
+ d(this, "isReady");
331
+ d(this, "cleanupFns", []);
332
+ d(this, "initialRetryTimer", null);
333
+ d(this, "routePollTimer", null);
334
+ d(this, "originalPushState", null);
335
+ d(this, "originalReplaceState", null);
336
+ this.sendContext = e.sendContext, this.isReady = e.isReady ?? (() => !0);
337
+ }
338
+ start() {
339
+ typeof window > "u" || this.active || (this.active = !0, this.initialSent = !1, this.lastRoute = y(), this.attachListeners(), this.scheduleInitialContextSend());
340
+ }
341
+ stop() {
342
+ if (!(!this.active && this.cleanupFns.length === 0)) {
343
+ this.active = !1, this.initialSent = !1, this.initialRetryTimer !== null && (clearTimeout(this.initialRetryTimer), this.initialRetryTimer = null), this.routePollTimer !== null && (clearInterval(this.routePollTimer), this.routePollTimer = null);
344
+ for (const e of this.cleanupFns)
345
+ try {
346
+ e();
347
+ } catch {
348
+ }
349
+ this.cleanupFns.length = 0, this.originalPushState && (history.pushState = this.originalPushState, this.originalPushState = null), this.originalReplaceState && (history.replaceState = this.originalReplaceState, this.originalReplaceState = null);
350
+ }
351
+ }
352
+ /** Call when the WebRTC data channel opens (initial context may have been deferred). */
353
+ onChannelReady() {
354
+ this.scheduleInitialContextSend();
355
+ }
356
+ scheduleInitialContextSend(e = 0) {
357
+ if (!(!this.active || this.initialSent)) {
358
+ if (this.isReady()) {
359
+ this.sendRouteContext("call_started"), this.initialSent = !0;
360
+ return;
361
+ }
362
+ e >= 30 || (this.initialRetryTimer = window.setTimeout(
363
+ () => this.scheduleInitialContextSend(e + 1),
364
+ 100
365
+ ));
366
+ }
367
+ }
368
+ attachListeners() {
369
+ const e = (s, o = 0) => {
370
+ const c = () => {
371
+ const r = y();
372
+ r !== this.lastRoute && this.handleRouteChange(r, s);
373
+ };
374
+ o > 0 ? window.setTimeout(c, o) : queueMicrotask(c);
375
+ }, t = () => e("popstate"), n = () => e("hashchange"), i = (s) => {
376
+ const o = s.detail;
377
+ if ((o == null ? void 0 : o.initiated_by) === "agent") {
378
+ e("vanira_navigate_agent", 120);
379
+ return;
380
+ }
381
+ e("vanira_navigate", 120);
382
+ };
383
+ window.addEventListener("popstate", t), window.addEventListener("hashchange", n), window.addEventListener("vanira:navigate", i), this.cleanupFns.push(
384
+ () => window.removeEventListener("popstate", t),
385
+ () => window.removeEventListener("hashchange", n),
386
+ () => window.removeEventListener("vanira:navigate", i)
387
+ ), this.originalPushState = history.pushState.bind(history), this.originalReplaceState = history.replaceState.bind(history), history.pushState = (...s) => {
388
+ this.originalPushState(...s), e("pushState");
389
+ }, history.replaceState = (...s) => {
390
+ this.originalReplaceState(...s), e("replaceState");
391
+ }, this.routePollTimer = window.setInterval(() => {
392
+ if (!this.active) return;
393
+ const s = y();
394
+ s !== this.lastRoute && this.handleRouteChange(s, "location_poll");
395
+ }, 1e3);
396
+ }
397
+ handleRouteChange(e, t) {
398
+ if (!this.active) return;
399
+ const n = this.lastRoute;
400
+ if (n === e) return;
401
+ if (this.lastRoute = e, !this.initialSent) {
402
+ this.scheduleInitialContextSend();
403
+ return;
404
+ }
405
+ !this.isReady() || ue() || this.sendRouteContext("route_changed", n, t, "user");
406
+ }
407
+ sendRouteContext(e, t, n, i = "user") {
408
+ const s = y();
409
+ this.lastRoute = s;
410
+ const o = {
411
+ ui_state: "page_navigation",
412
+ navigation_led_by: i,
413
+ current_route: s,
414
+ current_page: s,
415
+ user_action: e === "call_started" ? "call_started_on_page" : "user_navigated"
416
+ };
417
+ e === "call_started" ? o.message = `User started the call while viewing ${s}.` : (o.previous_route = t ?? "", o.message = `User navigated from ${t || "(unknown)"} to ${s}.`, n && (o.navigation_source = n));
418
+ try {
419
+ this.sendContext(o);
420
+ } catch {
421
+ }
422
+ }
423
+ }
424
+ const w = class w {
425
+ constructor(e) {
426
+ d(this, "serverUrl");
427
+ d(this, "agentId");
428
+ d(this, "callId");
429
+ d(this, "prospectId");
430
+ d(this, "apiKey");
431
+ d(this, "backendUrl");
432
+ d(this, "token");
433
+ d(this, "onConnected");
434
+ d(this, "onDisconnected");
435
+ d(this, "onError");
436
+ d(this, "onTranscription");
437
+ d(this, "onLocalStream");
438
+ // @ts-ignore
439
+ d(this, "onRemoteTrack");
440
+ d(this, "onClientToolCall");
441
+ d(this, "onSessionStarted");
442
+ d(this, "sessionStartedEmitted", !1);
443
+ d(this, "pc", null);
444
+ d(this, "dataChannel", null);
445
+ d(this, "mediaBytesChannel", null);
446
+ d(this, "audioElement", null);
447
+ d(this, "localStream", null);
448
+ d(this, "micMuted", !1);
449
+ d(this, "connected", !1);
450
+ d(this, "icePollInterval", null);
451
+ d(this, "appliedRemoteCandidates", /* @__PURE__ */ new Set());
452
+ d(this, "iceServers");
453
+ d(this, "callSessionLoaded", !1);
454
+ d(this, "connectGeneration", 0);
455
+ d(this, "runtime");
456
+ d(this, "sessionBehavior");
457
+ d(this, "routeTracker", null);
458
+ d(this, "_status", "idle");
459
+ d(this, "listeners", /* @__PURE__ */ new Map());
460
+ /** Live vision config — auto-starts camera on connect when scope is full_call. */
461
+ d(this, "liveVision");
462
+ d(this, "liveVisionAutoStarted", !1);
463
+ if (!e.agentId) throw new Error("agentId is required");
464
+ const t = !!(e.apiKey || e.token), n = !!(e.serverUrl && e.callId), i = w.normalizeIceServers(
465
+ e.iceServers
466
+ );
467
+ if (!t && !n)
468
+ throw new Error(
469
+ "apiKey or token is required — or pass serverUrl and callId from a prior POST /calls/create"
470
+ );
471
+ this.agentId = e.agentId, this.sessionBehavior = e.sessionBehavior, this.serverUrl = (e.serverUrl || "").replace(/\/$/, ""), this.apiKey = e.apiKey, this.token = e.token, this.backendUrl = (e.backendUrl || X).replace(/\/$/, ""), i.length && (this.iceServers = i), n && (this.callSessionLoaded = !0);
472
+ const s = typeof window < "u";
473
+ this.prospectId = e.prospectId;
474
+ const o = e.sessionBehavior === "continue";
475
+ if (o && s && !e.callId) {
476
+ const r = D(this.agentId);
477
+ r && (e.callId || (this.callId = r.callId), !this.prospectId && r.prospectId && (this.prospectId = r.prospectId));
478
+ }
479
+ if (!this.prospectId && s)
480
+ try {
481
+ const r = D(this.agentId);
482
+ this.prospectId = (r == null ? void 0 : r.prospectId) || window.sessionStorage && window.sessionStorage.getItem("vanira_prospect_id") || window.localStorage && window.localStorage.getItem("vanira_prospect_id") || void 0;
483
+ } catch {
484
+ }
485
+ e.callId ? this.callId = e.callId : o || (this.callId = void 0);
486
+ const c = e.onSessionStarted;
487
+ this.onSessionStarted = (r) => {
488
+ c == null || c(r), this.emit("session_started", r);
489
+ }, this.liveVision = e.liveVision, this.onConnected = () => {
490
+ var r;
491
+ this._status = "connected", (r = e.onConnected) == null || r.call(e), this.emit("connected"), this.maybeAutoStartLiveCamera();
492
+ }, this.onDisconnected = () => {
493
+ var r;
494
+ this._status = "disconnected", (r = e.onDisconnected) == null || r.call(e), this.emit("disconnected");
495
+ }, this.onError = (r) => {
496
+ var p;
497
+ this._status = "error";
498
+ const u = typeof r == "string" ? r : (r == null ? void 0 : r.message) || "Connection error";
499
+ (p = e.onError) == null || p.call(e, r), this.emit("error", u);
500
+ }, this.onTranscription = (r, u) => {
501
+ var p;
502
+ (p = e.onTranscription) == null || p.call(e, r, u), this.emit("transcription", { text: r, isFinal: u });
503
+ }, this.onLocalStream = e.onLocalStream || (() => {
504
+ }), this.onRemoteTrack = (r, u) => {
505
+ var p;
506
+ (p = e.onRemoteTrack) == null || p.call(e, r, u), this.emit("track", { track: r, stream: u });
507
+ }, this.onClientToolCall = (r) => {
508
+ var l;
509
+ (l = e.onClientToolCall) == null || l.call(e, r);
510
+ const u = (r == null ? void 0 : r.data) || r, p = {
511
+ name: String(u.name || u.tool_name || ""),
512
+ arguments: u.arguments || u.args || {},
513
+ tool_call_id: String(u.tool_call_id || u.call_id || ""),
514
+ execution_mode: u.execution_mode === "blocking" ? "blocking" : "fire_and_forget",
515
+ client_fields: u.client_fields || {}
516
+ };
517
+ p.name && p.tool_call_id && this.emit("tool_call", p);
518
+ }, this.runtime = e.runtime, typeof window < "u" && e.trackRouteContext !== !1 && (this.routeTracker = new pe({
519
+ sendContext: (r) => this.sendContextUpdate(r),
520
+ isReady: () => {
521
+ var r;
522
+ return ((r = this.dataChannel) == null ? void 0 : r.readyState) === "open";
523
+ }
524
+ }));
525
+ }
526
+ /**
527
+ * Create a call via POST /calls/create, then connect over WebRTC.
528
+ * Alias for connect() when using apiKey.
529
+ */
530
+ async createCall() {
531
+ return this.connect();
532
+ }
533
+ /**
534
+ * Connect to the agent (Talk to Agent — two phases).
535
+ *
536
+ * Phase 1: POST /calls/create → call_id, worker_url, ice_servers (fires onSessionStarted).
537
+ * Phase 2: getUserMedia → WebRTC offer to worker_url → ICE trickle → live audio (fires onConnected).
538
+ *
539
+ * Must be called from a user gesture (button click) for microphone access.
540
+ * onSessionStarted means provisioned only — wait for onConnected before the call is live.
541
+ */
542
+ async connect() {
543
+ var t;
544
+ this._status = "connecting";
545
+ const e = ++this.connectGeneration;
546
+ if (await this.loadCallSession(), !this.isConnectCancelled(e)) {
547
+ if (!this.serverUrl)
548
+ throw new Error("[VaniraClient] worker_url missing from /calls/create");
549
+ if (!this.callId)
550
+ throw new Error("[VaniraClient] call_id missing — must come from POST /calls/create");
551
+ if (this.onSessionStarted && this.prospectId && this.callId && !this.sessionStartedEmitted)
552
+ try {
553
+ this.onSessionStarted({ prospectId: this.prospectId, callId: this.callId, serverUrl: this.serverUrl }), this.sessionStartedEmitted = !0;
554
+ } catch {
555
+ }
556
+ try {
557
+ this.appliedRemoteCandidates.clear(), this.stopRemoteIcePolling();
558
+ const n = this.getIceServers();
559
+ this.pc = new RTCPeerConnection({
560
+ iceServers: n,
561
+ iceTransportPolicy: "all"
562
+ });
563
+ let i;
564
+ try {
565
+ i = await navigator.mediaDevices.getUserMedia({
566
+ audio: {
567
+ echoCancellation: !0,
568
+ noiseSuppression: !0,
569
+ autoGainControl: !0,
570
+ sampleRate: { ideal: 16e3 },
571
+ channelCount: 1
572
+ }
573
+ });
574
+ } catch (l) {
575
+ if (l.name === "NotAllowedError" || l.name === "PermissionDeniedError" || (t = l.message) != null && t.includes("Permission denied")) {
576
+ const _ = navigator.userAgent, m = /iPad|iPhone|iPod/.test(_) || navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1;
577
+ let f = "Microphone access denied. Please allow microphone access in your browser settings.";
578
+ m && (_.includes("CriOS") ? f = "Microphone access blocked. Please enable it in iOS Settings > Chrome > Microphone, then reload the page." : _.includes("FxiOS") ? f = "Microphone access blocked. Please enable it in iOS Settings > Firefox > Microphone, then reload the page." : f = "Microphone access blocked. Please enable it in iOS Settings > Safari > Microphone (or tap 'aA' > Website Settings > Allow Microphone).");
579
+ const v = new Error(f);
580
+ throw v.name = l.name, v;
581
+ }
582
+ throw l;
583
+ }
584
+ if (this.isConnectCancelled(e)) {
585
+ i.getTracks().forEach((l) => l.stop());
586
+ return;
587
+ }
588
+ if (this.localStream = i, !this.pc || this.isConnectCancelled(e)) {
589
+ i.getTracks().forEach((l) => l.stop());
590
+ return;
591
+ }
592
+ if (this.onLocalStream(i), i.getTracks().forEach((l) => {
593
+ var _;
594
+ (_ = this.pc) == null || _.addTrack(l, i);
595
+ }), !this.pc)
596
+ throw new Error("RTCPeerConnection was closed unexpectedly");
597
+ this.dataChannel = this.pc.createDataChannel("control"), this.dataChannel.onopen = () => {
598
+ var l;
599
+ (l = this.routeTracker) == null || l.onChannelReady();
600
+ }, this.dataChannel.onmessage = (l) => {
601
+ if (typeof l.data == "string")
602
+ try {
603
+ this.handleControlEvent(JSON.parse(l.data));
604
+ } catch {
605
+ }
606
+ else if (l.data instanceof ArrayBuffer)
607
+ try {
608
+ const _ = new TextDecoder().decode(l.data);
609
+ try {
610
+ const m = JSON.parse(_);
611
+ re(m) && this.handleControlEvent(m);
612
+ } catch {
613
+ }
614
+ } catch {
615
+ }
616
+ else l.data instanceof Blob && l.data.text().then((_) => {
617
+ try {
618
+ this.handleControlEvent(JSON.parse(_));
619
+ } catch {
620
+ }
621
+ });
622
+ }, this.dataChannel.onerror = (l) => {
623
+ }, this.mediaBytesChannel = this.pc.createDataChannel("media-bytes", { ordered: !0 }), this.mediaBytesChannel.binaryType = "arraybuffer", this.mediaBytesChannel.onopen = () => {
624
+ }, this.mediaBytesChannel.onerror = (l) => {
625
+ }, this.pc.ontrack = (l) => {
626
+ const _ = l.track, m = l.streams[0];
627
+ _.kind === "audio" ? (this.audioElement = new Audio(), this.audioElement.srcObject = m, this.audioElement.play().catch((f) => {
628
+ }), this.audioElement.onended = () => {
629
+ this.sendEvent("playedStream"), typeof window < "u" && window.dispatchEvent(new CustomEvent("vanira:agent-playback-ended"));
630
+ }, this.onRemoteTrack(_, m)) : _.kind === "video" && this.onRemoteTrack(_, m);
631
+ };
632
+ const s = () => {
633
+ var m, f, v, b, P, A, U, x, O, M, L;
634
+ const l = ((m = this.pc) == null ? void 0 : m.connectionState) === "connected" || ((f = this.pc) == null ? void 0 : f.iceConnectionState) === "connected" || ((v = this.pc) == null ? void 0 : v.iceConnectionState) === "completed", _ = ((b = this.pc) == null ? void 0 : b.connectionState) === "failed" || ((P = this.pc) == null ? void 0 : P.iceConnectionState) === "failed" || ((A = this.pc) == null ? void 0 : A.connectionState) === "closed" || ((U = this.pc) == null ? void 0 : U.iceConnectionState) === "closed" || ((x = this.pc) == null ? void 0 : x.connectionState) === "disconnected" || ((O = this.pc) == null ? void 0 : O.iceConnectionState) === "disconnected";
635
+ ((M = this.pc) == null ? void 0 : M.connectionState) === "connected" && this.stopRemoteIcePolling(), l && !this.connected ? (this.connected = !0, (L = this.routeTracker) == null || L.start(), this.onConnected()) : _ && this.connected && (this.connected = !1, this.liveVisionAutoStarted = !1, this.onDisconnected());
636
+ };
637
+ this.pc.onconnectionstatechange = s, this.pc.oniceconnectionstatechange = s;
638
+ const o = this.getIceTrickleUrl();
639
+ this.pc.onicecandidate = (l) => {
640
+ l.candidate && fetch(o, {
641
+ method: "POST",
642
+ headers: { "Content-Type": "application/json" },
643
+ body: JSON.stringify({ candidate: l.candidate.toJSON() })
644
+ }).catch((_) => {
645
+ });
646
+ };
647
+ const c = await this.pc.createOffer();
648
+ if (this.isConnectCancelled(e) || (await this.pc.setLocalDescription(c), this.isConnectCancelled(e))) return;
649
+ const r = this.serverUrl.includes("?") ? this.serverUrl : `${this.serverUrl}/webrtc?agent=${this.agentId}_${this.callId}`, u = await fetch(r, {
650
+ method: "POST",
651
+ headers: { "Content-Type": "application/json" },
652
+ body: JSON.stringify({
653
+ offer: this.pc.localDescription,
654
+ agentId: this.agentId,
655
+ callId: this.callId
656
+ })
657
+ });
658
+ if (this.isConnectCancelled(e)) return;
659
+ if (!u.ok) {
660
+ const l = await u.json();
661
+ throw new Error(l.error || `HTTP ${u.status}`);
662
+ }
663
+ const { answer: p } = await u.json();
664
+ if (this.isConnectCancelled(e) || !this.pc || (await this.pc.setRemoteDescription(p), this.isConnectCancelled(e))) return;
665
+ this.startRemoteIcePolling(o);
666
+ } catch (n) {
667
+ if (this.isConnectCancelled(e)) return;
668
+ throw this.disconnect(), this.onError(n.message || n), n;
669
+ }
670
+ }
671
+ }
672
+ createCallCacheKey(e, t) {
673
+ const n = this.apiKey ? `key:${this.apiKey.slice(0, 16)}` : `token:${(this.token || "").slice(-16)}`;
674
+ return [
675
+ this.backendUrl,
676
+ this.agentId,
677
+ e,
678
+ t || "",
679
+ this.prospectId || "",
680
+ n
681
+ ].join("|");
682
+ }
683
+ async postCreateCall(e, t, n) {
684
+ var c, r, u;
685
+ const i = await fetch(`${this.backendUrl}/calls/create`, {
686
+ method: "POST",
687
+ headers: e,
688
+ body: JSON.stringify({
689
+ agent_id: this.agentId,
690
+ type: ((c = this.runtime) == null ? void 0 : c.callType) || "web",
691
+ mode: t,
692
+ ...(r = this.runtime) != null && r.runtimeName ? { runtime: this.runtime.runtimeName } : {},
693
+ prospect_id: this.prospectId,
694
+ ...n ? { call_id: n } : {}
695
+ })
696
+ });
697
+ if (!i.ok) {
698
+ const p = await i.json().catch(() => ({}));
699
+ throw new Error(`[VaniraClient] createCall failed (${i.status}): ${((u = p == null ? void 0 : p.detail) == null ? void 0 : u.message) || (p == null ? void 0 : p.message) || i.statusText}`);
700
+ }
701
+ const s = await i.json();
702
+ if (!s.call_id || !s.worker_url)
703
+ throw new Error("[VaniraClient] /calls/create response missing call_id or worker_url");
704
+ const o = w.normalizeIceServers(s.ice_servers);
705
+ if (!o.length)
706
+ throw new Error("[VaniraClient] /calls/create response missing ice_servers");
707
+ return {
708
+ callId: s.call_id,
709
+ prospectId: s.prospect_id || this.prospectId,
710
+ serverUrl: s.worker_url.replace(/\/$/, ""),
711
+ iceServers: o
712
+ };
713
+ }
714
+ applyCreateCallSession(e) {
715
+ if (this.callId = e.callId, this.prospectId = e.prospectId || this.prospectId, this.serverUrl = e.serverUrl, this.iceServers = e.iceServers, this.callSessionLoaded = !0, ae(this.agentId, this.callId || "", this.prospectId), this.onSessionStarted && this.prospectId && this.callId && !this.sessionStartedEmitted)
716
+ try {
717
+ this.onSessionStarted({ prospectId: this.prospectId, callId: this.callId, serverUrl: this.serverUrl }), this.sessionStartedEmitted = !0;
718
+ } catch {
719
+ }
720
+ }
721
+ async loadCallSession() {
722
+ if (this.callSessionLoaded) return;
723
+ if (this.serverUrl && this.callId) {
724
+ this.callSessionLoaded = !0;
725
+ return;
726
+ }
727
+ if (!this.apiKey && !this.token)
728
+ throw new Error("[VaniraClient] apiKey or token is required to create a call session");
729
+ const e = { "Content-Type": "application/json" };
730
+ this.apiKey ? e["X-API-Key"] = this.apiKey : this.token && (e.Authorization = this.token.startsWith("Bearer ") ? this.token : `Bearer ${this.token}`);
731
+ const t = this.sessionBehavior === "continue" ? this.callId : void 0, n = this.sessionBehavior === "continue" ? "continue" : "new", i = this.createCallCacheKey(n, t);
732
+ let s = w.inflightCreates.get(i);
733
+ s || (s = this.postCreateCall(e, n, t), w.inflightCreates.set(i, s), s.finally(() => {
734
+ w.inflightCreates.get(i) === s && w.inflightCreates.delete(i);
735
+ }));
736
+ const o = await s;
737
+ this.applyCreateCallSession(o);
738
+ }
739
+ getIceServers() {
740
+ var e;
741
+ if ((e = this.iceServers) != null && e.length) return this.iceServers;
742
+ throw new Error("[VaniraClient] ice_servers missing — pass iceServers from POST /calls/create");
743
+ }
744
+ static normalizeIceServers(e) {
745
+ if (!Array.isArray(e)) return [];
746
+ const t = [];
747
+ for (const n of e) {
748
+ const i = n == null ? void 0 : n.urls, s = Array.isArray(i) ? i.filter(Boolean) : typeof i == "string" && i.trim() ? [i.trim()] : [];
749
+ if (!s.length) continue;
750
+ const o = { urls: s.length === 1 ? s[0] : s }, c = n.username, r = n.credential;
751
+ c && (o.username = c), r && (o.credential = r), t.push(o);
752
+ }
753
+ return t;
754
+ }
755
+ getWorkerOrigin() {
756
+ var e;
757
+ if (!((e = this.serverUrl) != null && e.trim()))
758
+ return "";
759
+ try {
760
+ return new URL(this.serverUrl).origin;
761
+ } catch {
762
+ return this.serverUrl.replace(/\/webrtc.*$/, "").replace(/\/$/, "");
763
+ }
764
+ }
765
+ /**
766
+ * ICE trickle URL derived from worker_url — same host, query params (agent_id, call_id, token), path /webrtc/ice.
767
+ */
768
+ getIceTrickleUrl() {
769
+ try {
770
+ const e = new URL(this.serverUrl);
771
+ return e.pathname.endsWith("/webrtc") ? e.pathname = `${e.pathname}/ice` : e.pathname = "/webrtc/ice", e.toString();
772
+ } catch {
773
+ return `${this.getWorkerOrigin()}/webrtc/ice?agent_id=${this.agentId}&call_id=${this.callId}`;
774
+ }
775
+ }
776
+ startRemoteIcePolling(e) {
777
+ this.stopRemoteIcePolling(), this.icePollInterval = setInterval(async () => {
778
+ if (!this.pc) {
779
+ this.stopRemoteIcePolling();
780
+ return;
781
+ }
782
+ if (this.pc.connectionState === "connected") {
783
+ this.stopRemoteIcePolling();
784
+ return;
785
+ }
786
+ try {
787
+ const t = await fetch(e);
788
+ if (!t.ok) return;
789
+ const { candidates: n } = await t.json();
790
+ for (const i of n || []) {
791
+ const s = JSON.stringify(i);
792
+ if (!this.appliedRemoteCandidates.has(s)) {
793
+ this.appliedRemoteCandidates.add(s);
794
+ try {
795
+ await this.pc.addIceCandidate(i);
796
+ } catch {
797
+ }
798
+ }
799
+ }
800
+ } catch {
801
+ }
802
+ }, 150);
803
+ }
804
+ stopRemoteIcePolling() {
805
+ this.icePollInterval !== null && (clearInterval(this.icePollInterval), this.icePollInterval = null);
806
+ }
807
+ /**
808
+ * Send control event via DataChannel
809
+ */
810
+ sendEvent(e, t = {}) {
811
+ var n;
812
+ if (((n = this.dataChannel) == null ? void 0 : n.readyState) === "open") {
813
+ const i = { event: e, ...t };
814
+ this.dataChannel.send(JSON.stringify(i));
815
+ }
816
+ }
817
+ /** Wait for control DataChannel, then send (live vision lifecycle events). */
818
+ async sendEventWhenReady(e, t = {}, n = 1e4) {
819
+ var s;
820
+ const i = Date.now() + n;
821
+ for (; Date.now() < i; ) {
822
+ if (((s = this.dataChannel) == null ? void 0 : s.readyState) === "open")
823
+ return this.sendEvent(e, t), !0;
824
+ await new Promise((o) => setTimeout(o, 50));
825
+ }
826
+ return !1;
827
+ }
828
+ /** Open live camera session (sends client_live_camera_started via preset handler). */
829
+ startLiveCamera(e = {}) {
830
+ const t = { ...this.liveVision, ...e };
831
+ this.emit("tool_call", this.buildOpenLiveCameraToolCall(t));
832
+ }
833
+ buildOpenLiveCameraToolCall(e = {}) {
834
+ const t = e.scope ?? "until_call_end";
835
+ return {
836
+ name: "open_live_camera",
837
+ tool_call_id: `lv_${crypto.randomUUID()}`,
838
+ execution_mode: "fire_and_forget",
839
+ client_fields: { preset_id: h.LiveVision },
840
+ arguments: {
841
+ mode: "live",
842
+ scope: t,
843
+ target_fps: e.target_fps ?? 1,
844
+ max_fps: e.max_fps ?? 3,
845
+ max_width: e.max_width ?? 640,
846
+ reason: e.reason ?? "camera_capture",
847
+ facing_mode: e.facing_mode ?? "environment",
848
+ show_preview: e.show_preview ?? !0,
849
+ ...t === "timed" && e.duration_sec ? { duration_sec: e.duration_sec } : {}
850
+ }
851
+ };
852
+ }
853
+ maybeAutoStartLiveCamera() {
854
+ const e = this.liveVision;
855
+ !(e != null && e.enabled) || e.scope !== "full_call" || this.liveVisionAutoStarted || (this.liveVisionAutoStarted = !0, this.emit("tool_call", this.buildOpenLiveCameraToolCall(e)));
856
+ }
857
+ /**
858
+ * Handle control events from server
859
+ */
860
+ handleControlEvent(e) {
861
+ var t;
862
+ switch (e.event) {
863
+ case "clearAudio":
864
+ break;
865
+ case "transcription":
866
+ this.onTranscription(e.text, e.isFinal);
867
+ break;
868
+ case "mark":
869
+ break;
870
+ case "client_tool_cancel": {
871
+ const i = (e.data || e).tool_call_id || e.tool_call_id;
872
+ this.dispatchBoardAbort("Tool cancelled by server", i);
873
+ break;
874
+ }
875
+ case "client_tool_call": {
876
+ const n = e.tool_call || e.data || e, i = (n == null ? void 0 : n.tool_call_id) || (n == null ? void 0 : n.call_id) || e.tool_call_id || "";
877
+ i && ((t = this.dataChannel) == null ? void 0 : t.readyState) === "open" && this.dataChannel.send(JSON.stringify({
878
+ event: "client_tool_ack",
879
+ data: { tool_call_id: i }
880
+ }));
881
+ const s = (n == null ? void 0 : n.arguments) || (n == null ? void 0 : n.args) || {}, o = (n == null ? void 0 : n.client_fields) || {}, c = (n == null ? void 0 : n.name) || (n == null ? void 0 : n.tool_name) || "", r = ie(o, s, c);
882
+ if (c === "end_call") {
883
+ i && this.sendToolResult(i, {
884
+ status: "client_ack_end_call",
885
+ message: "Ending call."
886
+ }), window.setTimeout(() => this.disconnect(), 120);
887
+ break;
888
+ }
889
+ if (r) {
890
+ window.dispatchEvent(new CustomEvent("vanira:preset", {
891
+ detail: { toolCall: n, client: this }
892
+ })), this.onClientToolCall(n);
893
+ break;
894
+ }
895
+ this.onClientToolCall(n);
896
+ break;
897
+ }
898
+ }
899
+ }
900
+ /**
901
+ * Disconnect and cleanup
902
+ */
903
+ disconnect() {
904
+ var e;
905
+ this.connectGeneration++, this.stopRemoteIcePolling(), this.appliedRemoteCandidates.clear(), this.audioElement && (this.audioElement.pause(), this.audioElement.srcObject = null), this.dataChannel && (this.dataChannel.close(), this.dataChannel = null), this.mediaBytesChannel && (this.mediaBytesChannel.close(), this.mediaBytesChannel = null), this.pc && (this.pc.getSenders().forEach((t) => {
906
+ t.track && t.track.stop();
907
+ }), this.pc.close(), this.pc = null), this.localStream && (this.localStream.getTracks().forEach((t) => {
908
+ try {
909
+ t.stop();
910
+ } catch {
911
+ }
912
+ }), this.localStream = null), this.connected = !1, (e = this.routeTracker) == null || e.stop(), this.callSessionLoaded = !1, this.sessionStartedEmitted = !1, this.serverUrl = "", this.callId = void 0, this.micMuted = !1, this.iceServers = void 0, this.onDisconnected();
913
+ }
914
+ /**
915
+ * Mute or unmute the local microphone without tearing down the WebRTC session.
916
+ * Uses track.enabled (not pause) so audio resumes correctly when unmuted.
917
+ */
918
+ setMicrophoneMuted(e) {
919
+ var t, n;
920
+ this.micMuted = e, (t = this.localStream) == null || t.getAudioTracks().forEach((i) => {
921
+ i.enabled = !e;
922
+ }), (n = this.pc) == null || n.getSenders().forEach((i) => {
923
+ var s;
924
+ ((s = i.track) == null ? void 0 : s.kind) === "audio" && (i.track.enabled = !e);
925
+ });
926
+ }
927
+ isMicrophoneMuted() {
928
+ return this.micMuted;
929
+ }
930
+ toggleMicrophoneMuted() {
931
+ return this.setMicrophoneMuted(!this.micMuted), this.micMuted;
932
+ }
933
+ isConnectCancelled(e) {
934
+ return e !== this.connectGeneration;
935
+ }
936
+ /** Notify WidgetPresetRenderer to cancel in-flight board animations */
937
+ dispatchBoardAbort(e, t) {
938
+ typeof window > "u" || window.dispatchEvent(new CustomEvent("vanira:board_abort", {
939
+ detail: { reason: e, toolCallId: t }
940
+ }));
941
+ }
942
+ /**
943
+ * Send client-side tool execution result back to the AI server.
944
+ * Use this to unblock a 'blocking' execution mode tool.
945
+ *
946
+ * @param toolCallId - `tool_call_id` from the `client_tool_call` event
947
+ * @param result - Payload returned to the agent
948
+ */
949
+ sendToolResult(e, t) {
950
+ this.callId, this.sendEvent("client_tool_result", {
951
+ call_id: this.callId,
952
+ data: { tool_call_id: e, result: t },
953
+ result: t
954
+ });
955
+ }
956
+ /**
957
+ * Send an acknowledgement that the client received a tool call and rendered it.
958
+ * This MUST be sent immediately upon receiving a 'client_tool_call' event.
959
+ * The SDK now sends this automatically, but this method is exposed for manual use.
960
+ * If the ack is not received within 2.3s, the AI will ask "Did something pop up?"
961
+ * Once acked, the server waits up to 10s for the client_tool_result.
962
+ */
963
+ sendToolAck(e) {
964
+ var t;
965
+ ((t = this.dataChannel) == null ? void 0 : t.readyState) === "open" && this.dataChannel.send(JSON.stringify({
966
+ event: "client_tool_ack",
967
+ data: { tool_call_id: e }
968
+ }));
969
+ }
970
+ /**
971
+ * Silently update the AI's context with what the user is currently viewing.
972
+ * Does NOT interrupt the AI's current speech.
973
+ */
974
+ sendContextUpdate(e) {
975
+ this.sendEvent("client_context_update", { data: { context: e } });
976
+ }
977
+ /**
978
+ * Trigger a client-side action — forces the AI to react to a UI event.
979
+ */
980
+ sendActionTrigger(e, t = {}) {
981
+ this.sendEvent("client_action_trigger", {
982
+ data: {
983
+ action_name: e,
984
+ data: t
985
+ }
986
+ });
987
+ }
988
+ /**
989
+ * Trigger a client-side interrupt — cuts AI audio and forces it to react to a UI event.
990
+ */
991
+ triggerActionInterrupt() {
992
+ this.sendEvent("action_interrupt");
993
+ }
994
+ /**
995
+ * Upload file to media server without notifying the AI (no client_media_update).
996
+ * Use for blocking presets — result goes in client_tool_result only.
997
+ */
998
+ async uploadMediaForToolResult(e, t = "general") {
999
+ return this._postMediaUpload(e, t);
1000
+ }
1001
+ /**
1002
+ * Uploads a file (photo, document, screenshot) to the media server
1003
+ * and notifies the AI agent via the WebRTC control DataChannel.
1004
+ *
1005
+ * @param file The file to upload (image, PDF, document)
1006
+ * @param reason Routing key for backend processing (default: 'general')
1007
+ * @param message Optional user text message about the file
1008
+ */
1009
+ async uploadMedia(e, t = "general", n = "") {
1010
+ return await le(this.dataChannel, this.mediaBytesChannel) ? this._uploadMediaViaDataChannel(e, t, n) : (ce(this.dataChannel, this.mediaBytesChannel), this._uploadMediaViaHTTP(e, t, n));
1011
+ }
1012
+ /**
1013
+ * Upload a live vision / screen frame — HTTP only for now, never sends client_media_update.
1014
+ * Caller sends client_media_frame after this returns.
1015
+ *
1016
+ * Frames require a real media_url (server rejects empty). WebRTC upload returns url="" until
1017
+ * the server adds client_media_upload_ready ack — use HTTP for frame JPEGs until then.
1018
+ */
1019
+ async uploadMediaFrame(e, t = "camera_capture", n) {
1020
+ const i = n && !(e instanceof File) ? new File([e], n, { type: e.type || "image/jpeg" }) : e, { media_id: s, url: o, contentType: c } = await this._postMediaUpload(i, t, !0);
1021
+ if (!s || !o)
1022
+ throw new Error("Frame upload failed: server response missing media_id or url");
1023
+ return { media_id: s, url: o, content_type: c };
1024
+ }
1025
+ /** WebRTC media-bytes path (control start/complete + binary chunks). */
1026
+ async _uploadBytesViaDataChannel(e, t, n, i, s) {
1027
+ if (!this.callId || !this.mediaBytesChannel)
1028
+ throw new Error("Upload failed: call not ready for WebRTC media upload.");
1029
+ const o = s || (e instanceof File ? e.name : i === "frame" ? "frame.jpg" : "upload.bin"), c = e.type || (i === "frame" ? "image/jpeg" : "application/octet-stream"), r = await he(e);
1030
+ return de({
1031
+ bytes: r,
1032
+ filename: o,
1033
+ contentType: c,
1034
+ reason: t,
1035
+ message: n,
1036
+ callId: this.callId,
1037
+ mediaBytesChannel: this.mediaBytesChannel,
1038
+ sendControlEvent: (u, p) => this.sendEvent(u, p),
1039
+ uploadKind: i
1040
+ });
1041
+ }
1042
+ async _uploadMediaViaDataChannel(e, t, n) {
1043
+ const i = await this._uploadBytesViaDataChannel(e, t, n, "media");
1044
+ return { media_id: i.media_id, url: i.url };
1045
+ }
1046
+ /** HTTP fallback — original uploadMedia behavior (unchanged). */
1047
+ async _uploadMediaViaHTTP(e, t = "general", n = "") {
1048
+ const { media_id: i, url: s, contentType: o } = await this._postMediaUpload(e, t, !0);
1049
+ if (this.dataChannel && this.dataChannel.readyState === "open") {
1050
+ const c = {
1051
+ event: "client_media_update",
1052
+ data: {
1053
+ media_id: i,
1054
+ media_url: s,
1055
+ content_type: o,
1056
+ reason: t,
1057
+ message: n
1058
+ }
1059
+ };
1060
+ this.dataChannel.send(JSON.stringify(c));
1061
+ }
1062
+ return { media_id: i, url: s };
1063
+ }
1064
+ async _postMediaUpload(e, t, n = !1) {
1065
+ if (!this.serverUrl)
1066
+ throw new Error("Upload failed: serverUrl is not set. Connect the VaniraClient first.");
1067
+ if (!this.callId)
1068
+ throw new Error("Upload failed: callId is missing.");
1069
+ const i = this.getWorkerOrigin();
1070
+ if (!i)
1071
+ throw new Error(
1072
+ "Upload failed: worker_url is not set. Connect the call before uploading media."
1073
+ );
1074
+ const s = `${i}/media/upload`, o = new FormData();
1075
+ o.append("file", e), o.append("call_id", this.callId), o.append("reason", t);
1076
+ const c = await fetch(s, {
1077
+ method: "POST",
1078
+ body: o
1079
+ });
1080
+ if (!c.ok) {
1081
+ let _ = `HTTP ${c.status}`;
1082
+ try {
1083
+ _ = (await c.json()).error || _;
1084
+ } catch {
1085
+ }
1086
+ throw new Error(`Upload failed: ${_}`);
1087
+ }
1088
+ const r = await c.json(), u = r.media_id, p = r.url, l = r.content_type || e.type;
1089
+ if (!u || !p)
1090
+ throw new Error("Upload failed: server response missing media_id or url");
1091
+ return n ? { media_id: u, url: p, contentType: l } : { media_id: u, url: p, contentType: l };
1092
+ }
1093
+ // ─── Event emitter (replaces retired VaniraAI) ───────────────────────────
1094
+ get status() {
1095
+ return this._status;
1096
+ }
1097
+ get isConnected() {
1098
+ return this._status === "connected";
1099
+ }
1100
+ on(e, t) {
1101
+ return this.listeners.has(e) || this.listeners.set(e, /* @__PURE__ */ new Set()), this.listeners.get(e).add(t), this;
1102
+ }
1103
+ off(e, t) {
1104
+ var n;
1105
+ return (n = this.listeners.get(e)) == null || n.delete(t), this;
1106
+ }
1107
+ emit(e, t) {
1108
+ var n;
1109
+ (n = this.listeners.get(e)) == null || n.forEach((i) => i(t));
1110
+ }
1111
+ /** Alias for connect() — VaniraAI compatibility */
1112
+ async start() {
1113
+ return await this.connect(), {
1114
+ callId: this.callId || "",
1115
+ prospectId: this.prospectId || "",
1116
+ serverUrl: this.serverUrl || ""
1117
+ };
1118
+ }
1119
+ /** Alias for disconnect() — VaniraAI compatibility */
1120
+ stop() {
1121
+ this.disconnect(), this._status = "disconnected";
1122
+ }
1123
+ sendToolError(e, t) {
1124
+ this.sendToolResult(e, { status: "error", error: t });
1125
+ }
1126
+ /** @deprecated Use sendContextUpdate */
1127
+ updateContext(e) {
1128
+ this.sendContextUpdate(e);
1129
+ }
1130
+ /** @deprecated Use triggerActionInterrupt + sendActionTrigger */
1131
+ triggerInterrupt(e, t = {}) {
1132
+ this.triggerActionInterrupt(), this.sendActionTrigger(e, t);
1133
+ }
1134
+ interruptAudioOnly() {
1135
+ this.triggerActionInterrupt();
1136
+ }
1137
+ };
1138
+ /** Dedupes concurrent POST /calls/create (e.g. React StrictMode double-mount). */
1139
+ d(w, "inflightCreates", /* @__PURE__ */ new Map());
1140
+ let R = w;
1141
+ const _e = {
1142
+ supportsAudioEndedEvent: !1,
1143
+ supportsScreenShare: !1,
1144
+ supportsDom: !1,
1145
+ supportsHtmlAudio: !1,
1146
+ supportsCustomElements: !1,
1147
+ supportsLocalStorage: !1,
1148
+ supportsBroadcastChannel: !1
1149
+ }, me = {
1150
+ name: "react-native",
1151
+ callType: "web",
1152
+ runtimeName: "react-native",
1153
+ callIdPrefix: "rn_",
1154
+ capabilities: _e,
1155
+ audio: new z(),
1156
+ media: new Y(),
1157
+ peer: new q(),
1158
+ dataChannel: new F()
1159
+ };
1160
+ function J(a) {
1161
+ return new R({
1162
+ ...a,
1163
+ runtime: me
1164
+ });
1165
+ }
1166
+ const Se = J;
1167
+ function fe(a) {
1168
+ return J(a);
1169
+ }
1170
+ const ve = fe;
1171
+ export {
1172
+ z as RNAudioAdapter,
1173
+ F as RNDataChannelAdapter,
1174
+ Y as RNMediaAdapter,
1175
+ q as RNPeerAdapter,
1176
+ R as VaniraAI,
1177
+ R as VaniraClient,
1178
+ R as WebRTCClient,
1179
+ Se as createReactNativeAI,
1180
+ J as createReactNativeClient,
1181
+ ve as createVaniraAI,
1182
+ fe as createVaniraClient,
1183
+ _e as reactNativeCapabilities,
1184
+ me as reactNativeRuntime
1185
+ };