@omnikit-ai/sdk 2.0.3

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.
package/dist/index.js ADDED
@@ -0,0 +1,2096 @@
1
+ 'use strict';
2
+
3
+ // src/auth-utils.ts
4
+ var ACCESS_TOKEN_KEY = "access_token";
5
+ var TOKEN_URL_PARAM = "token";
6
+ function setAccessTokenKey(appId) {
7
+ ACCESS_TOKEN_KEY = `omnikit_token_${appId}`;
8
+ }
9
+ function isBrowser() {
10
+ return typeof window !== "undefined" && typeof window.localStorage !== "undefined";
11
+ }
12
+ function getTokenFromUrl() {
13
+ if (!isBrowser()) return null;
14
+ try {
15
+ const urlParams = new URLSearchParams(window.location.search);
16
+ return urlParams.get(TOKEN_URL_PARAM);
17
+ } catch (error) {
18
+ console.warn("Failed to get token from URL:", error);
19
+ return null;
20
+ }
21
+ }
22
+ function getTokenFromStorage() {
23
+ if (!isBrowser()) return null;
24
+ try {
25
+ return localStorage.getItem(ACCESS_TOKEN_KEY);
26
+ } catch (error) {
27
+ console.warn("Failed to get token from localStorage:", error);
28
+ return null;
29
+ }
30
+ }
31
+ function isTokenInUrl() {
32
+ return getTokenFromUrl() !== null;
33
+ }
34
+ function cleanTokenFromUrl() {
35
+ if (!isBrowser()) return;
36
+ try {
37
+ const url = new URL(window.location.href);
38
+ if (url.searchParams.has(TOKEN_URL_PARAM)) {
39
+ url.searchParams.delete(TOKEN_URL_PARAM);
40
+ window.history.replaceState({}, "", url.toString());
41
+ }
42
+ } catch (error) {
43
+ console.warn("Failed to clean token from URL:", error);
44
+ }
45
+ }
46
+ function getTokenFromHash() {
47
+ if (!isBrowser()) return null;
48
+ const hash = window.location.hash;
49
+ if (!hash) return null;
50
+ try {
51
+ const params = new URLSearchParams(hash.substring(1));
52
+ return params.get("_auth_token");
53
+ } catch (error) {
54
+ console.warn("Failed to get token from hash:", error);
55
+ return null;
56
+ }
57
+ }
58
+ function cleanTokenFromHash() {
59
+ if (!isBrowser()) return;
60
+ try {
61
+ window.history.replaceState(
62
+ null,
63
+ "",
64
+ window.location.pathname + window.location.search
65
+ );
66
+ } catch (error) {
67
+ console.warn("Failed to clean token from hash:", error);
68
+ }
69
+ }
70
+ function saveAccessToken(token) {
71
+ if (!isBrowser()) {
72
+ console.warn("Cannot save token: not in browser environment");
73
+ return;
74
+ }
75
+ try {
76
+ localStorage.setItem(ACCESS_TOKEN_KEY, token);
77
+ } catch (error) {
78
+ console.error("Failed to save token to localStorage:", error);
79
+ }
80
+ }
81
+ function removeAccessToken() {
82
+ if (!isBrowser()) return;
83
+ try {
84
+ localStorage.removeItem(ACCESS_TOKEN_KEY);
85
+ } catch (error) {
86
+ console.warn("Failed to remove token from localStorage:", error);
87
+ }
88
+ }
89
+ function getAccessToken() {
90
+ const hashToken = getTokenFromHash();
91
+ if (hashToken) {
92
+ saveAccessToken(hashToken);
93
+ cleanTokenFromHash();
94
+ console.log("\u2705 OAuth token captured from URL hash");
95
+ if (isBrowser()) {
96
+ window.dispatchEvent(new CustomEvent("omnikit:auth-change"));
97
+ }
98
+ return hashToken;
99
+ }
100
+ const urlToken = getTokenFromUrl();
101
+ if (urlToken) {
102
+ saveAccessToken(urlToken);
103
+ cleanTokenFromUrl();
104
+ if (isBrowser()) {
105
+ window.dispatchEvent(new CustomEvent("omnikit:auth-change"));
106
+ }
107
+ return urlToken;
108
+ }
109
+ return getTokenFromStorage();
110
+ }
111
+ function setAccessToken(token) {
112
+ saveAccessToken(token);
113
+ }
114
+
115
+ // src/live-voice.ts
116
+ var INPUT_SAMPLE_RATE = 16e3;
117
+ var OUTPUT_SAMPLE_RATE = 24e3;
118
+ var CHUNK_SIZE = 4096;
119
+ var LiveVoiceSessionImpl = class {
120
+ constructor(baseUrl, appId, token, config) {
121
+ this.config = config;
122
+ this.ws = null;
123
+ this.audioContext = null;
124
+ this.mediaStream = null;
125
+ this.scriptProcessor = null;
126
+ this.sourceNode = null;
127
+ this.gainNode = null;
128
+ // Audio playback queue
129
+ this.playbackQueue = [];
130
+ this.isPlaying = false;
131
+ // Session state
132
+ this._isActive = false;
133
+ this._status = "idle";
134
+ this._sessionId = null;
135
+ this.baseUrl = baseUrl;
136
+ this.appId = appId;
137
+ this.token = token;
138
+ }
139
+ get isActive() {
140
+ return this._isActive;
141
+ }
142
+ get status() {
143
+ return this._status;
144
+ }
145
+ get sessionId() {
146
+ return this._sessionId;
147
+ }
148
+ /**
149
+ * Start the voice session
150
+ */
151
+ async start() {
152
+ if (this._isActive) {
153
+ throw new OmnikitError("Session already active", 400, "SESSION_ACTIVE");
154
+ }
155
+ this.setStatus("connecting");
156
+ try {
157
+ this.mediaStream = await navigator.mediaDevices.getUserMedia({
158
+ audio: {
159
+ sampleRate: INPUT_SAMPLE_RATE,
160
+ channelCount: 1,
161
+ echoCancellation: true,
162
+ noiseSuppression: true,
163
+ autoGainControl: true
164
+ }
165
+ });
166
+ this.audioContext = new AudioContext({ sampleRate: OUTPUT_SAMPLE_RATE });
167
+ const wsUrl = this.buildWebSocketUrl();
168
+ await this.connectWebSocket(wsUrl);
169
+ await this.setupAudioCapture();
170
+ this._isActive = true;
171
+ } catch (error) {
172
+ this.setStatus("error");
173
+ this.config?.onError?.(error);
174
+ await this.cleanup();
175
+ throw error;
176
+ }
177
+ }
178
+ /**
179
+ * Stop the voice session
180
+ */
181
+ async stop() {
182
+ if (!this._isActive) return;
183
+ if (this.ws?.readyState === WebSocket.OPEN) {
184
+ this.ws.send(JSON.stringify({ type: "end" }));
185
+ }
186
+ await this.cleanup();
187
+ }
188
+ /**
189
+ * Interrupt the AI while speaking
190
+ */
191
+ interrupt() {
192
+ if (!this._isActive || !this.ws || this.ws.readyState !== WebSocket.OPEN) {
193
+ return;
194
+ }
195
+ this.playbackQueue = [];
196
+ this.ws.send(JSON.stringify({ type: "interrupt" }));
197
+ }
198
+ /**
199
+ * Build WebSocket URL with auth and config
200
+ */
201
+ buildWebSocketUrl() {
202
+ const wsProtocol = this.baseUrl.startsWith("https") ? "wss" : "ws";
203
+ const wsBase = this.baseUrl.replace(/^https?/, wsProtocol);
204
+ const params = new URLSearchParams();
205
+ if (this.token) {
206
+ params.set("token", this.token);
207
+ }
208
+ if (this.config?.systemInstruction) {
209
+ params.set("system_instruction", this.config.systemInstruction);
210
+ }
211
+ if (this.config?.voice) {
212
+ params.set("voice", this.config.voice);
213
+ }
214
+ return `${wsBase}/apps/${this.appId}/live-voice?${params.toString()}`;
215
+ }
216
+ /**
217
+ * Connect to WebSocket server
218
+ */
219
+ connectWebSocket(url) {
220
+ return new Promise((resolve, reject) => {
221
+ this.ws = new WebSocket(url);
222
+ this.ws.binaryType = "arraybuffer";
223
+ const timeoutId = setTimeout(() => {
224
+ reject(new OmnikitError("WebSocket connection timeout", 408, "CONNECT_TIMEOUT"));
225
+ }, 1e4);
226
+ this.ws.onopen = () => {
227
+ clearTimeout(timeoutId);
228
+ };
229
+ this.ws.onmessage = (event) => {
230
+ this.handleMessage(event);
231
+ if (this._sessionId && this._status === "listening") {
232
+ resolve();
233
+ }
234
+ };
235
+ this.ws.onerror = (event) => {
236
+ clearTimeout(timeoutId);
237
+ const error = new OmnikitError("WebSocket connection error", 500, "WS_ERROR");
238
+ reject(error);
239
+ };
240
+ this.ws.onclose = (event) => {
241
+ clearTimeout(timeoutId);
242
+ if (this._isActive) {
243
+ this.setStatus("disconnected");
244
+ this.config?.onError?.(new OmnikitError(
245
+ `Connection closed: ${event.reason || "Unknown reason"}`,
246
+ event.code,
247
+ "CONNECTION_CLOSED"
248
+ ));
249
+ this.cleanup();
250
+ }
251
+ };
252
+ });
253
+ }
254
+ /**
255
+ * Handle incoming WebSocket messages
256
+ */
257
+ handleMessage(event) {
258
+ if (event.data instanceof ArrayBuffer) {
259
+ this.handleAudioData(event.data);
260
+ return;
261
+ }
262
+ try {
263
+ const message = JSON.parse(event.data);
264
+ switch (message.type) {
265
+ case "session_started":
266
+ this._sessionId = message.session_id || null;
267
+ this.setStatus("listening");
268
+ this.config?.onSessionStarted?.(this._sessionId);
269
+ break;
270
+ case "session_ended":
271
+ this.config?.onSessionEnded?.(message.duration_seconds || 0);
272
+ this.cleanup();
273
+ break;
274
+ case "transcript":
275
+ if (message.text && message.role) {
276
+ this.config?.onTranscript?.(message.text, message.role);
277
+ }
278
+ break;
279
+ case "status":
280
+ if (message.status) {
281
+ this.setStatus(message.status);
282
+ }
283
+ break;
284
+ case "error":
285
+ const error = new OmnikitError(
286
+ message.message || "Unknown error",
287
+ 500,
288
+ message.code
289
+ );
290
+ this.config?.onError?.(error);
291
+ break;
292
+ }
293
+ } catch (e) {
294
+ console.warn("[LiveVoice] Failed to parse message:", e);
295
+ }
296
+ }
297
+ /**
298
+ * Handle incoming audio data from Gemini
299
+ */
300
+ handleAudioData(arrayBuffer) {
301
+ if (!this.audioContext) return;
302
+ if (this._status !== "speaking") {
303
+ this.setStatus("speaking");
304
+ }
305
+ const pcmData = new Int16Array(arrayBuffer);
306
+ const floatData = new Float32Array(pcmData.length);
307
+ for (let i = 0; i < pcmData.length; i++) {
308
+ floatData[i] = pcmData[i] / 32768;
309
+ }
310
+ this.playbackQueue.push(floatData);
311
+ if (!this.isPlaying) {
312
+ this.playNextChunk();
313
+ }
314
+ }
315
+ /**
316
+ * Play next audio chunk from queue
317
+ */
318
+ playNextChunk() {
319
+ if (!this.audioContext || this.playbackQueue.length === 0) {
320
+ this.isPlaying = false;
321
+ if (this._status === "speaking") {
322
+ this.setStatus("listening");
323
+ }
324
+ return;
325
+ }
326
+ this.isPlaying = true;
327
+ const floatData = this.playbackQueue.shift();
328
+ const audioBuffer = this.audioContext.createBuffer(
329
+ 1,
330
+ // mono
331
+ floatData.length,
332
+ OUTPUT_SAMPLE_RATE
333
+ );
334
+ audioBuffer.getChannelData(0).set(floatData);
335
+ const source = this.audioContext.createBufferSource();
336
+ source.buffer = audioBuffer;
337
+ source.connect(this.audioContext.destination);
338
+ source.onended = () => {
339
+ this.playNextChunk();
340
+ };
341
+ source.start();
342
+ }
343
+ /**
344
+ * Set up audio capture from microphone
345
+ */
346
+ async setupAudioCapture() {
347
+ if (!this.audioContext || !this.mediaStream) return;
348
+ this.sourceNode = this.audioContext.createMediaStreamSource(this.mediaStream);
349
+ this.scriptProcessor = this.audioContext.createScriptProcessor(CHUNK_SIZE, 1, 1);
350
+ const inputSampleRate = this.audioContext.sampleRate;
351
+ this.scriptProcessor.onaudioprocess = (event) => {
352
+ if (!this._isActive || !this.ws || this.ws.readyState !== WebSocket.OPEN) {
353
+ return;
354
+ }
355
+ const inputData = event.inputBuffer.getChannelData(0);
356
+ const resampledData = this.resample(inputData, inputSampleRate, INPUT_SAMPLE_RATE);
357
+ const pcmData = new Int16Array(resampledData.length);
358
+ for (let i = 0; i < resampledData.length; i++) {
359
+ const sample = Math.max(-1, Math.min(1, resampledData[i]));
360
+ pcmData[i] = sample < 0 ? sample * 32768 : sample * 32767;
361
+ }
362
+ this.ws.send(pcmData.buffer);
363
+ };
364
+ this.sourceNode.connect(this.scriptProcessor);
365
+ this.scriptProcessor.connect(this.audioContext.destination);
366
+ this.gainNode = this.audioContext.createGain();
367
+ this.gainNode.gain.value = 0;
368
+ this.scriptProcessor.disconnect();
369
+ this.scriptProcessor.connect(this.gainNode);
370
+ this.gainNode.connect(this.audioContext.destination);
371
+ }
372
+ /**
373
+ * Simple linear resampling
374
+ */
375
+ resample(inputData, inputRate, outputRate) {
376
+ if (inputRate === outputRate) {
377
+ return inputData;
378
+ }
379
+ const ratio = inputRate / outputRate;
380
+ const outputLength = Math.floor(inputData.length / ratio);
381
+ const output = new Float32Array(outputLength);
382
+ for (let i = 0; i < outputLength; i++) {
383
+ const srcIndex = i * ratio;
384
+ const srcIndexFloor = Math.floor(srcIndex);
385
+ const srcIndexCeil = Math.min(srcIndexFloor + 1, inputData.length - 1);
386
+ const fraction = srcIndex - srcIndexFloor;
387
+ output[i] = inputData[srcIndexFloor] * (1 - fraction) + inputData[srcIndexCeil] * fraction;
388
+ }
389
+ return output;
390
+ }
391
+ /**
392
+ * Update status and notify callback
393
+ */
394
+ setStatus(status) {
395
+ if (this._status !== status) {
396
+ this._status = status;
397
+ this.config?.onStatusChange?.(status);
398
+ }
399
+ }
400
+ /**
401
+ * Clean up all resources
402
+ */
403
+ async cleanup() {
404
+ this._isActive = false;
405
+ this.playbackQueue = [];
406
+ this.isPlaying = false;
407
+ if (this.scriptProcessor) {
408
+ this.scriptProcessor.disconnect();
409
+ this.scriptProcessor = null;
410
+ }
411
+ if (this.sourceNode) {
412
+ this.sourceNode.disconnect();
413
+ this.sourceNode = null;
414
+ }
415
+ if (this.gainNode) {
416
+ this.gainNode.disconnect();
417
+ this.gainNode = null;
418
+ }
419
+ if (this.mediaStream) {
420
+ this.mediaStream.getTracks().forEach((track) => track.stop());
421
+ this.mediaStream = null;
422
+ }
423
+ if (this.audioContext && this.audioContext.state !== "closed") {
424
+ await this.audioContext.close();
425
+ this.audioContext = null;
426
+ }
427
+ if (this.ws) {
428
+ if (this.ws.readyState === WebSocket.OPEN) {
429
+ this.ws.close(1e3, "Session ended");
430
+ }
431
+ this.ws = null;
432
+ }
433
+ this._sessionId = null;
434
+ this.setStatus("idle");
435
+ }
436
+ };
437
+
438
+ // src/client.ts
439
+ var LLM_MODEL_MAP = {
440
+ "gemini-flash": "vertex_ai/gemini-2.5-flash",
441
+ "gemini-pro": "vertex_ai/gemini-2.5-pro"
442
+ };
443
+ function mapLLMModel(model) {
444
+ if (!model) return void 0;
445
+ return LLM_MODEL_MAP[model] || model;
446
+ }
447
+ function makeArrayForgiving(arr) {
448
+ if (!Array.isArray(arr)) return arr;
449
+ Object.defineProperty(arr, "items", {
450
+ get() {
451
+ return this;
452
+ },
453
+ enumerable: false,
454
+ configurable: true
455
+ });
456
+ return arr;
457
+ }
458
+ var OmnikitError = class _OmnikitError extends Error {
459
+ constructor(message, status, code, data) {
460
+ super(message);
461
+ this.name = "OmnikitError";
462
+ this.status = status;
463
+ this.code = code;
464
+ this.data = data;
465
+ this.isAuthError = status === 401 || status === 403;
466
+ if (Error.captureStackTrace) {
467
+ Error.captureStackTrace(this, _OmnikitError);
468
+ }
469
+ }
470
+ isNotFound() {
471
+ return this.status === 404;
472
+ }
473
+ isForbidden() {
474
+ return this.status === 403;
475
+ }
476
+ isUnauthorized() {
477
+ return this.status === 401;
478
+ }
479
+ isBadRequest() {
480
+ return this.status === 400;
481
+ }
482
+ isServerError() {
483
+ return this.status !== void 0 && this.status >= 500;
484
+ }
485
+ };
486
+ var getMetadataCacheKey = (appId) => `omnikit_metadata_${appId}`;
487
+ var APIClient = class {
488
+ constructor(config) {
489
+ this.userToken = null;
490
+ this._serviceToken = null;
491
+ this._apiKey = null;
492
+ this.initialized = false;
493
+ this.initPromise = null;
494
+ this._collections = {};
495
+ this._services = {};
496
+ // New flat services
497
+ this._integrations = {};
498
+ // Callbacks for metadata updates (for React re-render triggers)
499
+ this._metadataListeners = /* @__PURE__ */ new Set();
500
+ // Callbacks for user state changes (for React auth state sync)
501
+ this._userListeners = /* @__PURE__ */ new Set();
502
+ this.appId = config.appId;
503
+ this.baseUrl = config.serverUrl || config.baseUrl || "http://localhost:8001/api";
504
+ this._serviceToken = config.serviceToken || null;
505
+ this._apiKey = config.apiKey || null;
506
+ const isBrowser2 = typeof window !== "undefined" && typeof localStorage !== "undefined";
507
+ this._metadata = this.loadCachedMetadata(config.initialMetadata);
508
+ if (isBrowser2) {
509
+ setAccessTokenKey(this.appId);
510
+ }
511
+ const autoInitAuth = config.autoInitAuth !== false;
512
+ if (config.token) {
513
+ this.userToken = config.token;
514
+ if (isBrowser2) {
515
+ saveAccessToken(config.token);
516
+ }
517
+ } else if (autoInitAuth && isBrowser2) {
518
+ const detectedToken = getAccessToken();
519
+ if (detectedToken) {
520
+ this.userToken = detectedToken;
521
+ }
522
+ }
523
+ this.ensureInitialized().catch((err) => {
524
+ console.warn("[Omnikit SDK] Background initialization failed:", err);
525
+ });
526
+ }
527
+ /**
528
+ * Load metadata from localStorage cache, falling back to initial config
529
+ * Guards localStorage access for Deno/Node compatibility
530
+ */
531
+ loadCachedMetadata(initialMetadata) {
532
+ if (typeof window !== "undefined" && typeof localStorage !== "undefined") {
533
+ try {
534
+ const cached = localStorage.getItem(getMetadataCacheKey(this.appId));
535
+ if (cached) {
536
+ const parsed = JSON.parse(cached);
537
+ return {
538
+ id: this.appId,
539
+ name: parsed.name || "",
540
+ logoUrl: parsed.logoUrl || "",
541
+ thumbnailUrl: parsed.thumbnailUrl || "",
542
+ visibility: parsed.visibility || "private",
543
+ platformAuthProviders: parsed.platformAuthProviders || ["google"]
544
+ };
545
+ }
546
+ } catch (e) {
547
+ }
548
+ }
549
+ return {
550
+ id: this.appId,
551
+ name: initialMetadata?.name || "",
552
+ logoUrl: initialMetadata?.logoUrl || "",
553
+ thumbnailUrl: initialMetadata?.thumbnailUrl || "",
554
+ visibility: "private",
555
+ // Default to private, updated after fetch
556
+ platformAuthProviders: ["google"]
557
+ // Default to Google only
558
+ };
559
+ }
560
+ /**
561
+ * Update metadata cache in localStorage
562
+ * IMPORTANT: Mutate in place to preserve object reference for exports
563
+ */
564
+ updateMetadataCache(data) {
565
+ this._metadata.name = data.name || this._metadata.name;
566
+ this._metadata.logoUrl = data.logoUrl || this._metadata.logoUrl;
567
+ this._metadata.thumbnailUrl = data.thumbnailUrl || this._metadata.thumbnailUrl;
568
+ this._metadata.visibility = data.visibility || this._metadata.visibility || "private";
569
+ this._metadata.platformAuthProviders = data.platformAuthProviders || this._metadata.platformAuthProviders || ["google"];
570
+ if (typeof window !== "undefined" && typeof localStorage !== "undefined") {
571
+ try {
572
+ localStorage.setItem(getMetadataCacheKey(this.appId), JSON.stringify(this._metadata));
573
+ } catch (e) {
574
+ }
575
+ }
576
+ this._metadataListeners.forEach((callback) => callback());
577
+ }
578
+ /**
579
+ * App metadata - available instantly without API call (no flicker!)
580
+ */
581
+ get metadata() {
582
+ return this._metadata;
583
+ }
584
+ /**
585
+ * Subscribe to metadata updates (for React components to trigger re-render)
586
+ * @returns Unsubscribe function
587
+ */
588
+ onMetadataChange(callback) {
589
+ this._metadataListeners.add(callback);
590
+ return () => this._metadataListeners.delete(callback);
591
+ }
592
+ /**
593
+ * Subscribe to user state changes (for React auth state sync)
594
+ * Called when user logs in, logs out, or updates profile
595
+ * @returns Unsubscribe function
596
+ */
597
+ onUserChange(callback) {
598
+ this._userListeners.add(callback);
599
+ return () => this._userListeners.delete(callback);
600
+ }
601
+ /**
602
+ * Emit user change event to all listeners
603
+ */
604
+ emitUserChange(user) {
605
+ this._userListeners.forEach((cb) => {
606
+ try {
607
+ cb(user);
608
+ } catch (e) {
609
+ console.error("[Omnikit SDK] User listener error:", e);
610
+ }
611
+ });
612
+ }
613
+ /**
614
+ * Lazy getter for collections - auto-initializes on first access
615
+ */
616
+ get collections() {
617
+ return this.createCollectionsProxy(false);
618
+ }
619
+ /**
620
+ * @deprecated Use collections instead. This alias exists for backward compatibility.
621
+ */
622
+ get entities() {
623
+ return this.collections;
624
+ }
625
+ /**
626
+ * Lazy getter for services (new flat structure) - auto-initializes on first access
627
+ * Usage: omnikit.services.SendEmail({ to, subject, body })
628
+ */
629
+ get services() {
630
+ return this.createServicesProxy(false);
631
+ }
632
+ /**
633
+ * @deprecated Use services instead for flat access
634
+ * Lazy getter for integrations - auto-initializes on first access
635
+ */
636
+ get integrations() {
637
+ return this.createIntegrationsProxy(false);
638
+ }
639
+ /**
640
+ * Lazy getter for auth - auto-initializes on first access
641
+ */
642
+ get auth() {
643
+ return this.createAuthProxy();
644
+ }
645
+ /**
646
+ * Lazy getter for service role operations
647
+ * Only available when serviceToken is provided
648
+ */
649
+ get asServiceRole() {
650
+ if (!this._serviceToken) {
651
+ throw new OmnikitError(
652
+ "Service token is required to use asServiceRole. Provide serviceToken in config.",
653
+ 403,
654
+ "SERVICE_TOKEN_REQUIRED"
655
+ );
656
+ }
657
+ if (this._asServiceRole) {
658
+ return this._asServiceRole;
659
+ }
660
+ this._asServiceRole = {
661
+ collections: this.createCollectionsProxy(true),
662
+ // Service role collections
663
+ services: this.createServicesProxy(true),
664
+ // Service role services (new flat structure)
665
+ integrations: this.createIntegrationsProxy(true)
666
+ // Service role integrations (legacy)
667
+ // Note: auth not available in service role for security
668
+ };
669
+ return this._asServiceRole;
670
+ }
671
+ /**
672
+ * Create auth proxy that auto-initializes
673
+ */
674
+ createAuthProxy() {
675
+ const client = this;
676
+ const authHandler = {
677
+ async isAuthenticated() {
678
+ await client.ensureInitialized();
679
+ try {
680
+ const response = await client.makeRequest(
681
+ `${client.baseUrl}/auth/is-authenticated?app_id=${client.appId}`,
682
+ "GET"
683
+ );
684
+ return response?.authenticated === true;
685
+ } catch (error) {
686
+ return false;
687
+ }
688
+ },
689
+ async me() {
690
+ await client.ensureInitialized();
691
+ const response = await client.makeRequest(
692
+ `${client.baseUrl}/apps/${client.appId}/collections/user/me`,
693
+ "GET"
694
+ );
695
+ client.emitUserChange(response);
696
+ return response;
697
+ },
698
+ login(returnUrl) {
699
+ const fullReturnUrl = returnUrl || (typeof window !== "undefined" ? window.location.href : "/");
700
+ const encodedReturnUrl = encodeURIComponent(fullReturnUrl);
701
+ if (typeof window !== "undefined") {
702
+ const currentPath = window.location.pathname;
703
+ const apiSitesMatch = currentPath.match(/^\/api\/sites\/([^\/]+)/);
704
+ if (apiSitesMatch) {
705
+ window.location.href = `/api/sites/${client.appId}/login?return_url=${encodedReturnUrl}`;
706
+ } else {
707
+ window.location.href = `/login?return_url=${encodedReturnUrl}`;
708
+ }
709
+ }
710
+ },
711
+ /**
712
+ * Request a passwordless login code to email
713
+ */
714
+ async requestLoginCode(email, returnUrl) {
715
+ return await client.makeRequest(
716
+ `${client.baseUrl}/auth/email/request-code`,
717
+ "POST",
718
+ { email, return_url: returnUrl }
719
+ );
720
+ },
721
+ /**
722
+ * Verify the login code and set the session token
723
+ */
724
+ async verifyLoginCode(email, code) {
725
+ const response = await client.makeRequest(
726
+ `${client.baseUrl}/auth/email/verify-code`,
727
+ "POST",
728
+ { email, code }
729
+ );
730
+ if (response.access_token) {
731
+ client.setAuthToken(response.access_token);
732
+ try {
733
+ const user = await this.me();
734
+ client.emitUserChange(user);
735
+ } catch (e) {
736
+ client.emitUserChange(null);
737
+ }
738
+ }
739
+ return response;
740
+ },
741
+ /**
742
+ * Get available OAuth providers for this app
743
+ * Returns both platform providers (zero-config) and custom SSO providers
744
+ */
745
+ async getAvailableProviders() {
746
+ await client.ensureInitialized();
747
+ try {
748
+ const response = await client.makeRequest(
749
+ `${client.baseUrl}/apps/${client.appId}/auth/providers`,
750
+ "GET"
751
+ );
752
+ return response;
753
+ } catch (error) {
754
+ throw new OmnikitError(
755
+ "Failed to fetch available OAuth providers",
756
+ error.status || 500,
757
+ "PROVIDERS_FETCH_FAILED"
758
+ );
759
+ }
760
+ },
761
+ /**
762
+ * Initiate OAuth login with any provider
763
+ * Redirects to the backend OAuth endpoint for the specified provider
764
+ */
765
+ loginWithProvider(providerId, returnUrl) {
766
+ const currentUrl = returnUrl || (typeof window !== "undefined" ? window.location.href : "/");
767
+ const redirectUrl = `${client.baseUrl}/auth/${providerId}?redirect_url=${encodeURIComponent(currentUrl)}`;
768
+ if (typeof window !== "undefined") {
769
+ const inIframe = window.self !== window.top;
770
+ if (inIframe) {
771
+ const width = 600;
772
+ const height = 700;
773
+ const left = window.screen.width / 2 - width / 2;
774
+ const top = window.screen.height / 2 - height / 2;
775
+ const popup = window.open(
776
+ redirectUrl,
777
+ "oauth_popup",
778
+ `width=${width},height=${height},left=${left},top=${top},resizable=yes,scrollbars=yes,status=yes`
779
+ );
780
+ if (popup) {
781
+ const checkTimer = setInterval(() => {
782
+ const token = getAccessToken();
783
+ if (token) {
784
+ clearInterval(checkTimer);
785
+ popup.close();
786
+ window.location.reload();
787
+ return;
788
+ }
789
+ if (popup.closed) {
790
+ clearInterval(checkTimer);
791
+ const finalToken = getAccessToken();
792
+ if (finalToken) {
793
+ window.location.reload();
794
+ }
795
+ }
796
+ }, 1e3);
797
+ return;
798
+ }
799
+ }
800
+ window.location.href = redirectUrl;
801
+ } else {
802
+ throw new OmnikitError("loginWithProvider() can only be called in browser environment", 400, "NOT_BROWSER");
803
+ }
804
+ },
805
+ /**
806
+ * Initiate Google OAuth Login
807
+ * @deprecated Use loginWithProvider("google", returnUrl) instead
808
+ * Redirects to the backend OAuth endpoint
809
+ */
810
+ loginWithGoogle(returnUrl) {
811
+ console.warn('loginWithGoogle() is deprecated. Use loginWithProvider("google", returnUrl) instead.');
812
+ this.loginWithProvider("google", returnUrl);
813
+ },
814
+ async logout() {
815
+ await client.makeRequest(
816
+ `${client.baseUrl}/auth/logout`,
817
+ "POST"
818
+ );
819
+ client.clearAuthToken();
820
+ client.emitUserChange(null);
821
+ },
822
+ async refreshToken() {
823
+ const response = await client.makeRequest(
824
+ `${client.baseUrl}/auth/refresh`,
825
+ "POST"
826
+ );
827
+ if (response.access_token || response.token) {
828
+ const newToken = response.access_token || response.token;
829
+ client.setAuthToken(newToken);
830
+ }
831
+ return response;
832
+ },
833
+ async updateMe(data) {
834
+ await client.ensureInitialized();
835
+ const response = await client.makeRequest(
836
+ `${client.baseUrl}/apps/${client.appId}/collections/user/me`,
837
+ "PUT",
838
+ data
839
+ );
840
+ client.emitUserChange(response);
841
+ return response;
842
+ },
843
+ // Alias for updateMe with clearer naming
844
+ async updateProfile(data) {
845
+ return this.updateMe(data);
846
+ },
847
+ // Backward compatibility
848
+ async getCurrentUser() {
849
+ return this.me();
850
+ },
851
+ // Subscribe to user state changes (for React auth state sync)
852
+ onUserChange(callback) {
853
+ return client.onUserChange(callback);
854
+ }
855
+ };
856
+ return authHandler;
857
+ }
858
+ /**
859
+ * Convert PascalCase to snake_case
860
+ * Example: PdfDocument -> pdf_document, UserProfile -> user_profile
861
+ */
862
+ toSnakeCase(str) {
863
+ return str.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
864
+ }
865
+ /**
866
+ * Create collections proxy that auto-initializes
867
+ * @param useServiceToken - Whether to use service token for requests
868
+ */
869
+ createCollectionsProxy(useServiceToken) {
870
+ const client = this;
871
+ return new Proxy({}, {
872
+ get(target, collectionName) {
873
+ if (typeof collectionName === "string" && !collectionName.startsWith("_")) {
874
+ return new Proxy({}, {
875
+ get(obj, method) {
876
+ if (typeof method === "string") {
877
+ return async function(...args) {
878
+ await client.ensureInitialized();
879
+ const normalizedCollectionName = client.toSnakeCase(collectionName);
880
+ const collection = client._collections[normalizedCollectionName];
881
+ if (!collection) {
882
+ throw new OmnikitError(
883
+ `Collection '${collectionName}' not found in app schema`,
884
+ 404,
885
+ "COLLECTION_NOT_FOUND"
886
+ );
887
+ }
888
+ if (typeof collection[method] !== "function") {
889
+ const availableMethods = [
890
+ "get(id)",
891
+ "list(options?)",
892
+ "filter(filters?)",
893
+ "findOne(filters?)",
894
+ "create(data)",
895
+ "update(id, data)",
896
+ "delete(id)",
897
+ "count(filters?)",
898
+ "bulkCreate(items)",
899
+ "bulkDelete(ids)",
900
+ "import(file, format)",
901
+ "deleteAll(confirm)"
902
+ ];
903
+ throw new OmnikitError(
904
+ `Method '${method}()' does not exist on collection '${collectionName}'.
905
+
906
+ Available methods:
907
+ ${availableMethods.map((m) => ` - ${m}`).join("\n")}
908
+
909
+ Common mistake: There is NO 'read()' method. Use 'list()' instead.
910
+ Example: await ${collectionName}.list({ limit: 100, sort: '-created_at' })`,
911
+ 400,
912
+ "METHOD_NOT_FOUND"
913
+ );
914
+ }
915
+ if (useServiceToken) {
916
+ const originalGetToken = client.getAuthToken.bind(client);
917
+ client.getAuthToken = () => client._serviceToken;
918
+ try {
919
+ const result = await collection[method](...args);
920
+ client.getAuthToken = originalGetToken;
921
+ return result;
922
+ } catch (error) {
923
+ client.getAuthToken = originalGetToken;
924
+ throw error;
925
+ }
926
+ } else {
927
+ return collection[method](...args);
928
+ }
929
+ };
930
+ }
931
+ return void 0;
932
+ }
933
+ });
934
+ }
935
+ return target[collectionName];
936
+ }
937
+ });
938
+ }
939
+ /**
940
+ * Poll for job completion with progress callbacks
941
+ * @param jobId - Job ID to poll
942
+ * @param options - Async options (onStatusChange, pollInterval, timeout)
943
+ * @returns Final job result
944
+ */
945
+ async pollJobUntilComplete(jobId, options) {
946
+ const pollInterval = options?.pollInterval || 2e3;
947
+ const timeout = options?.timeout || 3e5;
948
+ const startTime = Date.now();
949
+ while (true) {
950
+ const status = await this.makeRequest(
951
+ `${this.baseUrl}/apps/${this.appId}/jobs/${jobId}`,
952
+ "GET"
953
+ );
954
+ if (options?.onStatusChange) {
955
+ options.onStatusChange(status.status, status.status_message);
956
+ }
957
+ if (status.status === "completed") {
958
+ return status.result;
959
+ }
960
+ if (status.status === "failed") {
961
+ throw new OmnikitError(
962
+ status.error || "Job failed",
963
+ 500,
964
+ "JOB_FAILED",
965
+ { job_id: jobId, status }
966
+ );
967
+ }
968
+ if (status.status === "cancelled") {
969
+ throw new OmnikitError(
970
+ "Job was cancelled",
971
+ 400,
972
+ "JOB_CANCELLED",
973
+ { job_id: jobId }
974
+ );
975
+ }
976
+ if (Date.now() - startTime > timeout) {
977
+ throw new OmnikitError(
978
+ `Job timed out after ${timeout / 1e3} seconds`,
979
+ 408,
980
+ "JOB_TIMEOUT",
981
+ { job_id: jobId }
982
+ );
983
+ }
984
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
985
+ }
986
+ }
987
+ /**
988
+ * Stream LLM response token by token via SSE
989
+ * @param params - LLM request parameters with streaming callbacks
990
+ * @returns Promise that resolves to the complete response string
991
+ */
992
+ async streamLLMResponse(params) {
993
+ await this.ensureInitialized();
994
+ const headers = {
995
+ "Content-Type": "application/json"
996
+ };
997
+ const token = this.getAuthToken();
998
+ if (token) {
999
+ headers["Authorization"] = `Bearer ${token}`;
1000
+ }
1001
+ if (this._serviceToken) {
1002
+ headers["X-Service-Token"] = this._serviceToken;
1003
+ }
1004
+ if (this._apiKey) {
1005
+ headers["X-API-Key"] = this._apiKey;
1006
+ }
1007
+ const requestBody = {
1008
+ ...params,
1009
+ stream: true,
1010
+ // Map friendly model names to full model names
1011
+ model: mapLLMModel(params.model),
1012
+ // Remove callback functions from request body
1013
+ onToken: void 0,
1014
+ onComplete: void 0,
1015
+ onError: void 0
1016
+ };
1017
+ Object.keys(requestBody).forEach((key) => {
1018
+ if (requestBody[key] === void 0) {
1019
+ delete requestBody[key];
1020
+ }
1021
+ });
1022
+ const response = await fetch(
1023
+ `${this.baseUrl}/apps/${this.appId}/services/llm`,
1024
+ {
1025
+ method: "POST",
1026
+ headers,
1027
+ body: JSON.stringify(requestBody)
1028
+ }
1029
+ );
1030
+ if (!response.ok) {
1031
+ const errorText = await response.text();
1032
+ let errorMessage = `LLM streaming failed: ${response.statusText}`;
1033
+ try {
1034
+ const errorJson = JSON.parse(errorText);
1035
+ errorMessage = errorJson.detail || errorJson.message || errorMessage;
1036
+ } catch {
1037
+ errorMessage = errorText || errorMessage;
1038
+ }
1039
+ const err = new OmnikitError(errorMessage, response.status, "STREAM_ERROR");
1040
+ params.onError?.(err);
1041
+ throw err;
1042
+ }
1043
+ const reader = response.body?.getReader();
1044
+ if (!reader) {
1045
+ const err = new OmnikitError("No response body", 500, "NO_BODY");
1046
+ params.onError?.(err);
1047
+ throw err;
1048
+ }
1049
+ const decoder = new TextDecoder();
1050
+ let fullResponse = "";
1051
+ let buffer = "";
1052
+ try {
1053
+ while (true) {
1054
+ const { done, value } = await reader.read();
1055
+ if (done) break;
1056
+ buffer += decoder.decode(value, { stream: true });
1057
+ const lines = buffer.split("\n");
1058
+ buffer = lines.pop() || "";
1059
+ for (const line of lines) {
1060
+ if (line.startsWith("data: ")) {
1061
+ try {
1062
+ const event = JSON.parse(line.slice(6));
1063
+ if (event.type === "token" && event.content) {
1064
+ fullResponse += event.content;
1065
+ params.onToken?.(event.content);
1066
+ } else if (event.type === "done") {
1067
+ params.onComplete?.({
1068
+ result: event.result || fullResponse,
1069
+ model_used: event.model_used || "",
1070
+ usage: event.usage
1071
+ });
1072
+ } else if (event.type === "error") {
1073
+ const err = new OmnikitError(
1074
+ event.message || "Stream error",
1075
+ 500,
1076
+ "STREAM_ERROR"
1077
+ );
1078
+ params.onError?.(err);
1079
+ throw err;
1080
+ }
1081
+ } catch (parseError) {
1082
+ if (parseError instanceof OmnikitError) {
1083
+ throw parseError;
1084
+ }
1085
+ }
1086
+ }
1087
+ }
1088
+ }
1089
+ } finally {
1090
+ reader.releaseLock();
1091
+ }
1092
+ return fullResponse;
1093
+ }
1094
+ /**
1095
+ * Create services proxy that auto-initializes (new flat structure)
1096
+ * @param useServiceToken - Whether to use service token for requests
1097
+ */
1098
+ createServicesProxy(useServiceToken) {
1099
+ const client = this;
1100
+ return new Proxy({}, {
1101
+ get(target, serviceName) {
1102
+ if (typeof serviceName === "string" && !serviceName.startsWith("_")) {
1103
+ if (serviceName === "CheckJobStatus") {
1104
+ return async function(params) {
1105
+ await client.ensureInitialized();
1106
+ if (!params?.job_id) {
1107
+ throw new OmnikitError(
1108
+ "job_id is required for CheckJobStatus",
1109
+ 400,
1110
+ "MISSING_JOB_ID"
1111
+ );
1112
+ }
1113
+ return client.makeRequest(
1114
+ `${client.baseUrl}/apps/${client.appId}/jobs/${params.job_id}`,
1115
+ "GET"
1116
+ );
1117
+ };
1118
+ }
1119
+ if (serviceName === "DownloadPrivateFile") {
1120
+ return async function(params) {
1121
+ await client.ensureInitialized();
1122
+ if (!params?.file_uri) {
1123
+ throw new OmnikitError(
1124
+ "file_uri is required for DownloadPrivateFile",
1125
+ 400,
1126
+ "MISSING_FILE_URI"
1127
+ );
1128
+ }
1129
+ const downloadUrl = new URL(`${client.baseUrl}/apps/${client.appId}/services/files/download`);
1130
+ downloadUrl.searchParams.set("file_uri", params.file_uri);
1131
+ if (params.filename) {
1132
+ downloadUrl.searchParams.set("filename", params.filename);
1133
+ }
1134
+ const token = client.getAuthToken();
1135
+ if (token) {
1136
+ downloadUrl.searchParams.set("token", token);
1137
+ }
1138
+ window.open(downloadUrl.toString(), "_blank");
1139
+ };
1140
+ }
1141
+ return async function(params, asyncOptions) {
1142
+ await client.ensureInitialized();
1143
+ if (serviceName === "InvokeLLM") {
1144
+ if (params?.model) {
1145
+ params = { ...params, model: mapLLMModel(params.model) };
1146
+ }
1147
+ if (params?.stream) {
1148
+ return client.streamLLMResponse(params);
1149
+ }
1150
+ }
1151
+ const method = client._services[serviceName];
1152
+ if (!method) {
1153
+ throw new OmnikitError(
1154
+ `Service '${serviceName}' not found. Available services: ${Object.keys(client._services).join(", ")}`,
1155
+ 404,
1156
+ "SERVICE_NOT_FOUND"
1157
+ );
1158
+ }
1159
+ const response = await method(params, useServiceToken);
1160
+ if (response && response.async && response.job_id) {
1161
+ if (asyncOptions?.returnJobId) {
1162
+ return response;
1163
+ }
1164
+ return client.pollJobUntilComplete(response.job_id, asyncOptions);
1165
+ }
1166
+ return response;
1167
+ };
1168
+ }
1169
+ return target[serviceName];
1170
+ }
1171
+ });
1172
+ }
1173
+ /**
1174
+ * @deprecated Use createServicesProxy instead
1175
+ * Create integrations proxy that auto-initializes
1176
+ * @param useServiceToken - Whether to use service token for requests
1177
+ */
1178
+ createIntegrationsProxy(useServiceToken) {
1179
+ const client = this;
1180
+ return new Proxy({}, {
1181
+ get(target, packageName) {
1182
+ if (typeof packageName === "string" && !packageName.startsWith("_")) {
1183
+ return new Proxy({}, {
1184
+ get(obj, methodName) {
1185
+ if (typeof methodName === "string") {
1186
+ if (methodName === "CheckJobStatus") {
1187
+ return async function(params) {
1188
+ await client.ensureInitialized();
1189
+ if (!params?.job_id) {
1190
+ throw new OmnikitError(
1191
+ "job_id is required for CheckJobStatus",
1192
+ 400,
1193
+ "MISSING_JOB_ID"
1194
+ );
1195
+ }
1196
+ return client.makeRequest(
1197
+ `${client.baseUrl}/apps/${client.appId}/jobs/${params.job_id}`,
1198
+ "GET"
1199
+ );
1200
+ };
1201
+ }
1202
+ return async function(params, asyncOptions) {
1203
+ await client.ensureInitialized();
1204
+ if (methodName === "InvokeLLM") {
1205
+ if (params?.model) {
1206
+ params = { ...params, model: mapLLMModel(params.model) };
1207
+ }
1208
+ }
1209
+ const integrationPackage = client._integrations[packageName];
1210
+ if (!integrationPackage) {
1211
+ throw new OmnikitError(
1212
+ `Integration package '${packageName}' not found`,
1213
+ 404,
1214
+ "INTEGRATION_NOT_FOUND"
1215
+ );
1216
+ }
1217
+ const method = integrationPackage[methodName];
1218
+ if (!method) {
1219
+ throw new OmnikitError(
1220
+ `Integration method '${methodName}' not found in package '${packageName}'`,
1221
+ 404,
1222
+ "INTEGRATION_METHOD_NOT_FOUND"
1223
+ );
1224
+ }
1225
+ const response = await method(params, useServiceToken);
1226
+ if (response && response.async && response.job_id) {
1227
+ if (asyncOptions?.returnJobId) {
1228
+ return response;
1229
+ }
1230
+ return client.pollJobUntilComplete(response.job_id, asyncOptions);
1231
+ }
1232
+ return response;
1233
+ };
1234
+ }
1235
+ return void 0;
1236
+ }
1237
+ });
1238
+ }
1239
+ return target[packageName];
1240
+ }
1241
+ });
1242
+ }
1243
+ /**
1244
+ * Ensure SDK is initialized (auto-initializes once on first call)
1245
+ */
1246
+ async ensureInitialized() {
1247
+ if (this.initialized) return;
1248
+ if (this.initPromise) {
1249
+ return this.initPromise;
1250
+ }
1251
+ this.initPromise = this.initialize();
1252
+ await this.initPromise;
1253
+ this.initPromise = null;
1254
+ }
1255
+ /**
1256
+ * Initialize SDK by fetching app schema and integration endpoints
1257
+ * This happens automatically on first use - no need to call explicitly
1258
+ */
1259
+ async initialize() {
1260
+ if (this.initialized) return;
1261
+ try {
1262
+ const [appSchema, integrationSchema] = await Promise.all([
1263
+ this.fetchAppSchema(),
1264
+ this.fetchIntegrationSchema()
1265
+ ]);
1266
+ if (appSchema.app) {
1267
+ this.updateMetadataCache({
1268
+ name: appSchema.app.name,
1269
+ logoUrl: appSchema.app.logo_url,
1270
+ thumbnailUrl: appSchema.app.thumbnail_url,
1271
+ visibility: appSchema.app.visibility,
1272
+ platformAuthProviders: appSchema.app.platform_auth_providers
1273
+ });
1274
+ }
1275
+ if (appSchema.entities) {
1276
+ this.createCollections(appSchema.entities);
1277
+ }
1278
+ if (integrationSchema && "services" in integrationSchema) {
1279
+ this.createServicesFromSchema(integrationSchema);
1280
+ } else if (integrationSchema && "installed_packages" in integrationSchema) {
1281
+ this.createIntegrationsFromSchema(integrationSchema);
1282
+ }
1283
+ this.initialized = true;
1284
+ } catch (error) {
1285
+ console.error("Failed to initialize Omnikit SDK:", error);
1286
+ throw new OmnikitError(
1287
+ `SDK initialization failed: ${error.message}`,
1288
+ 500,
1289
+ "INIT_FAILED",
1290
+ error
1291
+ );
1292
+ }
1293
+ }
1294
+ /**
1295
+ * Fetch app schema from backend
1296
+ */
1297
+ async fetchAppSchema() {
1298
+ const response = await fetch(`${this.baseUrl}/apps/${this.appId}`);
1299
+ if (!response.ok) {
1300
+ throw new Error(`Failed to fetch app schema: ${response.statusText}`);
1301
+ }
1302
+ return await response.json();
1303
+ }
1304
+ /**
1305
+ * Fetch integration schema from backend
1306
+ * Returns ServicesSchema (new flat format) or IntegrationSchema (legacy)
1307
+ */
1308
+ async fetchIntegrationSchema() {
1309
+ try {
1310
+ const response = await fetch(
1311
+ `${this.baseUrl}/apps/${this.appId}/services`
1312
+ );
1313
+ if (!response.ok) {
1314
+ return null;
1315
+ }
1316
+ return await response.json();
1317
+ } catch (error) {
1318
+ return null;
1319
+ }
1320
+ }
1321
+ /**
1322
+ * Create collection classes from schema
1323
+ */
1324
+ createCollections(collectionsData) {
1325
+ const collections = Array.isArray(collectionsData) ? collectionsData.reduce((acc, collection) => ({ ...acc, [collection.name]: collection }), {}) : collectionsData;
1326
+ Object.entries(collections).forEach(([collectionName, collectionDef]) => {
1327
+ this._collections[collectionName.toLowerCase()] = this.createCollectionClass(collectionName, collectionDef);
1328
+ });
1329
+ }
1330
+ /**
1331
+ * Create a single collection class with all CRUD operations
1332
+ * Automatically enhances User collection with convenience methods
1333
+ */
1334
+ createCollectionClass(collectionName, collectionDef) {
1335
+ const client = this;
1336
+ const isUserCollection = collectionName.toLowerCase() === "user";
1337
+ const baseCollection = {
1338
+ // Get single record by ID
1339
+ async get(id) {
1340
+ const response = await client.makeRequest(
1341
+ `${client.baseUrl}/apps/${client.appId}/collections/${collectionName}/${id}`,
1342
+ "GET"
1343
+ );
1344
+ return response;
1345
+ },
1346
+ // List all records with MongoDB/Mongoose-style filtering
1347
+ async list(...args) {
1348
+ const queryParams = new URLSearchParams();
1349
+ let filter;
1350
+ let options;
1351
+ if (args.length === 0) {
1352
+ filter = void 0;
1353
+ options = void 0;
1354
+ } else if (args.length === 1) {
1355
+ const arg = args[0];
1356
+ if (arg && (arg.q !== void 0 || arg.sort !== void 0 || arg.limit !== void 0 || arg.offset !== void 0 || arg._count !== void 0)) {
1357
+ Object.entries(arg).forEach(([key, value]) => {
1358
+ if (value !== void 0 && value !== null) {
1359
+ queryParams.append(key, typeof value === "object" ? JSON.stringify(value) : String(value));
1360
+ }
1361
+ });
1362
+ } else {
1363
+ filter = arg;
1364
+ options = void 0;
1365
+ }
1366
+ } else if (args.length === 2) {
1367
+ [filter, options] = args;
1368
+ }
1369
+ if (filter !== void 0 || options !== void 0) {
1370
+ if (filter && Object.keys(filter).length > 0) {
1371
+ queryParams.append("q", JSON.stringify(filter));
1372
+ }
1373
+ if (options) {
1374
+ if (options.sort !== void 0) {
1375
+ let sortString;
1376
+ if (typeof options.sort === "object") {
1377
+ const sortField = Object.keys(options.sort)[0];
1378
+ const sortOrder = options.sort[sortField];
1379
+ sortString = sortOrder === -1 ? `-${sortField}` : sortField;
1380
+ } else {
1381
+ sortString = options.sort;
1382
+ }
1383
+ queryParams.append("sort", sortString);
1384
+ }
1385
+ if (options.limit !== void 0) {
1386
+ queryParams.append("limit", String(options.limit));
1387
+ }
1388
+ if (options.offset !== void 0) {
1389
+ queryParams.append("offset", String(options.offset));
1390
+ }
1391
+ }
1392
+ }
1393
+ const url = queryParams.toString() ? `${client.baseUrl}/apps/${client.appId}/collections/${collectionName}?${queryParams}` : `${client.baseUrl}/apps/${client.appId}/collections/${collectionName}`;
1394
+ const response = await client.makeRequest(url, "GET");
1395
+ return makeArrayForgiving(Array.isArray(response) ? response : []);
1396
+ },
1397
+ // Filter records by query (alias for list)
1398
+ async filter(...args) {
1399
+ return this.list(...args);
1400
+ },
1401
+ // Find single record matching query
1402
+ async findOne(...args) {
1403
+ if (args.length === 0) {
1404
+ const results = await this.list({}, { limit: 1 });
1405
+ return results[0] || null;
1406
+ } else if (args.length === 1) {
1407
+ const arg = args[0];
1408
+ if (arg && (arg.q !== void 0 || arg._count !== void 0)) {
1409
+ const results = await this.list({ ...arg, limit: 1 });
1410
+ return results[0] || null;
1411
+ } else {
1412
+ const results = await this.list(arg, { limit: 1 });
1413
+ return results[0] || null;
1414
+ }
1415
+ } else {
1416
+ const [filter, options] = args;
1417
+ const results = await this.list(filter, { ...options, limit: 1 });
1418
+ return results[0] || null;
1419
+ }
1420
+ },
1421
+ // Create new record
1422
+ async create(data) {
1423
+ const response = await client.makeRequest(
1424
+ `${client.baseUrl}/apps/${client.appId}/collections/${collectionName}`,
1425
+ "POST",
1426
+ data
1427
+ );
1428
+ return response;
1429
+ },
1430
+ // Update existing record (overridden for User collection)
1431
+ async update(...args) {
1432
+ if (isUserCollection) {
1433
+ if (args.length === 1) {
1434
+ const response = await client.makeRequest(
1435
+ `${client.baseUrl}/apps/${client.appId}/collections/user/me`,
1436
+ "PUT",
1437
+ args[0]
1438
+ );
1439
+ return response;
1440
+ } else if (args.length === 2) {
1441
+ const [id, data] = args;
1442
+ const response = await client.makeRequest(
1443
+ `${client.baseUrl}/apps/${client.appId}/collections/${collectionName}/${id}`,
1444
+ "PATCH",
1445
+ data
1446
+ );
1447
+ return response;
1448
+ } else {
1449
+ throw new OmnikitError(
1450
+ "User.update() expects 1 argument (current user data) or 2 arguments (id, data)",
1451
+ 400,
1452
+ "INVALID_ARGUMENTS"
1453
+ );
1454
+ }
1455
+ } else {
1456
+ const [id, data] = args;
1457
+ const response = await client.makeRequest(
1458
+ `${client.baseUrl}/apps/${client.appId}/collections/${collectionName}/${id}`,
1459
+ "PATCH",
1460
+ data
1461
+ );
1462
+ return response;
1463
+ }
1464
+ },
1465
+ // Delete record by ID
1466
+ async delete(id) {
1467
+ await client.makeRequest(
1468
+ `${client.baseUrl}/apps/${client.appId}/collections/${collectionName}/${id}`,
1469
+ "DELETE"
1470
+ );
1471
+ return true;
1472
+ },
1473
+ // Count records matching query
1474
+ async count(...args) {
1475
+ const queryParams = new URLSearchParams();
1476
+ if (args.length === 0) {
1477
+ queryParams.append("_count", "true");
1478
+ } else if (args.length === 1) {
1479
+ const arg = args[0];
1480
+ if (arg && (arg.q !== void 0 || arg.sort !== void 0)) {
1481
+ Object.entries(arg).forEach(([key, value]) => {
1482
+ if (value !== void 0 && value !== null) {
1483
+ queryParams.append(key, typeof value === "object" ? JSON.stringify(value) : String(value));
1484
+ }
1485
+ });
1486
+ } else {
1487
+ if (arg && Object.keys(arg).length > 0) {
1488
+ queryParams.append("q", JSON.stringify(arg));
1489
+ }
1490
+ }
1491
+ queryParams.append("_count", "true");
1492
+ } else if (args.length === 2) {
1493
+ const [filter] = args;
1494
+ if (filter && Object.keys(filter).length > 0) {
1495
+ queryParams.append("q", JSON.stringify(filter));
1496
+ }
1497
+ queryParams.append("_count", "true");
1498
+ }
1499
+ const response = await client.makeRequest(
1500
+ `${client.baseUrl}/apps/${client.appId}/collections/${collectionName}?${queryParams}`,
1501
+ "GET"
1502
+ );
1503
+ return response.total || response.count || 0;
1504
+ },
1505
+ // Bulk create multiple records
1506
+ async bulkCreate(items) {
1507
+ const response = await client.makeRequest(
1508
+ `${client.baseUrl}/apps/${client.appId}/collections/${collectionName}/bulk`,
1509
+ "POST",
1510
+ { items }
1511
+ );
1512
+ return response;
1513
+ },
1514
+ // Bulk delete multiple records by ID
1515
+ async bulkDelete(ids) {
1516
+ const response = await client.makeRequest(
1517
+ `${client.baseUrl}/apps/${client.appId}/collections/${collectionName}/bulk-delete`,
1518
+ "POST",
1519
+ ids
1520
+ );
1521
+ return response;
1522
+ },
1523
+ // Import records from CSV or JSON file
1524
+ async import(file, format) {
1525
+ const formData = new FormData();
1526
+ formData.append("file", file);
1527
+ formData.append("format", format);
1528
+ const response = await client.makeRequestWithFormData(
1529
+ `${client.baseUrl}/apps/${client.appId}/collections/${collectionName}/import`,
1530
+ formData
1531
+ );
1532
+ return response;
1533
+ },
1534
+ // Delete all records (requires explicit confirmation)
1535
+ async deleteAll(confirm) {
1536
+ if (!confirm) {
1537
+ throw new OmnikitError(
1538
+ "deleteAll requires explicit confirmation. Pass true to confirm.",
1539
+ 400,
1540
+ "CONFIRMATION_REQUIRED"
1541
+ );
1542
+ }
1543
+ const response = await client.makeRequest(
1544
+ `${client.baseUrl}/apps/${client.appId}/collections/${collectionName}?confirm=true`,
1545
+ "DELETE"
1546
+ );
1547
+ return response;
1548
+ },
1549
+ // Backward compatibility: findById → get
1550
+ async findById(id) {
1551
+ console.warn("findById() is deprecated. Use get() instead.");
1552
+ return this.get(id);
1553
+ }
1554
+ };
1555
+ if (isUserCollection) {
1556
+ baseCollection.me = async () => {
1557
+ const response = await client.makeRequest(
1558
+ `${client.baseUrl}/apps/${client.appId}/collections/user/me`,
1559
+ "GET"
1560
+ );
1561
+ return response;
1562
+ };
1563
+ baseCollection.updateMe = async (data) => {
1564
+ const response = await client.makeRequest(
1565
+ `${client.baseUrl}/apps/${client.appId}/collections/user/me`,
1566
+ "PUT",
1567
+ data
1568
+ );
1569
+ return response;
1570
+ };
1571
+ }
1572
+ return baseCollection;
1573
+ }
1574
+ /**
1575
+ * Create services from new flat schema (recommended)
1576
+ * Services are exposed as omnikit.services.ServiceName
1577
+ */
1578
+ createServicesFromSchema(schema) {
1579
+ schema.services.forEach((service) => {
1580
+ this._services[service.name] = this.createServiceMethod(service);
1581
+ });
1582
+ this._integrations["BuiltIn"] = {};
1583
+ schema.services.forEach((service) => {
1584
+ this._integrations["BuiltIn"][service.name] = this._services[service.name];
1585
+ });
1586
+ }
1587
+ /**
1588
+ * Create a service method from ServiceDefinition
1589
+ */
1590
+ createServiceMethod(service) {
1591
+ const client = this;
1592
+ const { path, method = "POST", path_params } = service;
1593
+ return async (params, useServiceToken) => {
1594
+ let normalizedParams = params;
1595
+ if (params instanceof File) {
1596
+ console.warn("[Omnikit SDK] UploadFile called with File directly. Auto-wrapping to { file }. Please use UploadFile({ file }) for best practice.");
1597
+ normalizedParams = { file: params };
1598
+ }
1599
+ let finalPath = path;
1600
+ const remainingParams = { ...normalizedParams };
1601
+ if (path_params && path_params.length > 0 && normalizedParams) {
1602
+ path_params.forEach((paramName) => {
1603
+ if (normalizedParams[paramName] !== void 0) {
1604
+ finalPath = finalPath.replace(`{${paramName}}`, String(normalizedParams[paramName]));
1605
+ delete remainingParams[paramName];
1606
+ }
1607
+ });
1608
+ }
1609
+ const fullUrl = `${client.baseUrl}/apps/${client.appId}${finalPath}`;
1610
+ if (remainingParams && remainingParams.file instanceof File) {
1611
+ return client.handleFileUpload(remainingParams.file, path, useServiceToken);
1612
+ }
1613
+ const response = await client.makeRequest(
1614
+ fullUrl,
1615
+ method,
1616
+ method === "GET" ? void 0 : remainingParams,
1617
+ {
1618
+ useServiceToken,
1619
+ queryParams: method === "GET" ? remainingParams : void 0
1620
+ }
1621
+ );
1622
+ return client.normalizeIntegrationResponse(response, path);
1623
+ };
1624
+ }
1625
+ /**
1626
+ * Handle file upload via presigned URL flow
1627
+ * Supports both public and private file uploads based on path
1628
+ */
1629
+ async handleFileUpload(file, path, useServiceToken) {
1630
+ const isPrivate = path.includes("/files/private");
1631
+ const initEndpoint = isPrivate ? "/services/files/private/init" : "/services/files/init";
1632
+ const completeEndpoint = isPrivate ? "/services/files/private/complete" : "/services/files/complete";
1633
+ const initResponse = await this.makeRequest(
1634
+ `${this.baseUrl}/apps/${this.appId}${initEndpoint}`,
1635
+ "POST",
1636
+ {
1637
+ filename: file.name,
1638
+ content_type: file.type || "application/octet-stream",
1639
+ size: file.size
1640
+ },
1641
+ { useServiceToken }
1642
+ );
1643
+ const { file_id, upload_url } = initResponse;
1644
+ const uploadResponse = await fetch(upload_url, {
1645
+ method: "PUT",
1646
+ body: file,
1647
+ headers: {
1648
+ "Content-Type": file.type || "application/octet-stream"
1649
+ }
1650
+ });
1651
+ if (!uploadResponse.ok) {
1652
+ throw new OmnikitError(
1653
+ `Failed to upload file to storage: ${uploadResponse.statusText}`,
1654
+ uploadResponse.status,
1655
+ "UPLOAD_FAILED"
1656
+ );
1657
+ }
1658
+ const completeResponse = await this.makeRequest(
1659
+ `${this.baseUrl}/apps/${this.appId}${completeEndpoint}/${file_id}`,
1660
+ "POST",
1661
+ null,
1662
+ { useServiceToken }
1663
+ );
1664
+ if (isPrivate) {
1665
+ return completeResponse;
1666
+ }
1667
+ return this.normalizeIntegrationResponse(completeResponse, path);
1668
+ }
1669
+ /**
1670
+ * @deprecated Use createServicesFromSchema instead
1671
+ * Create integration methods from legacy backend schema
1672
+ */
1673
+ createIntegrationsFromSchema(schema) {
1674
+ schema.installed_packages.forEach((pkg) => {
1675
+ const packageName = pkg.package_name;
1676
+ this._integrations[packageName] = {};
1677
+ pkg.endpoints.forEach((endpoint) => {
1678
+ const methodName = this.formatMethodName(endpoint.name);
1679
+ this._integrations[packageName][methodName] = this.createIntegrationMethod(endpoint);
1680
+ this._services[methodName] = this._integrations[packageName][methodName];
1681
+ });
1682
+ });
1683
+ }
1684
+ /**
1685
+ * @deprecated Backend now returns final method names
1686
+ * Format endpoint name to PascalCase method name (legacy)
1687
+ */
1688
+ formatMethodName(name) {
1689
+ const methodMap = {
1690
+ "email": "SendEmail",
1691
+ "llm": "InvokeLLM",
1692
+ "files": "UploadFile",
1693
+ "files/private": "UploadPrivateFile",
1694
+ "files/signed-url": "CreateFileSignedUrl",
1695
+ "images": "GenerateImage",
1696
+ "speech": "GenerateSpeech",
1697
+ "video": "GenerateVideo",
1698
+ "video_status": "CheckVideoStatus",
1699
+ "extract-text": "ExtractData",
1700
+ "extract-text_status": "CheckExtractStatus",
1701
+ "sms": "SendSMS"
1702
+ };
1703
+ return methodMap[name] || name.charAt(0).toUpperCase() + name.slice(1);
1704
+ }
1705
+ /**
1706
+ * Create integration method that calls backend endpoint
1707
+ */
1708
+ createIntegrationMethod(endpoint) {
1709
+ const client = this;
1710
+ const { path, method = "POST", path_params = [] } = endpoint;
1711
+ return async (params, useServiceToken) => {
1712
+ let normalizedParams = params;
1713
+ if (params instanceof File) {
1714
+ console.warn("[Omnikit SDK] UploadFile called with File directly. Auto-wrapping to { file }. Please use UploadFile({ file }) for best practice.");
1715
+ normalizedParams = { file: params };
1716
+ }
1717
+ let finalPath = path;
1718
+ const remainingParams = { ...normalizedParams };
1719
+ if (path_params && path_params.length > 0 && normalizedParams) {
1720
+ path_params.forEach((paramName) => {
1721
+ if (normalizedParams[paramName] !== void 0) {
1722
+ finalPath = finalPath.replace(`{${paramName}}`, String(normalizedParams[paramName]));
1723
+ delete remainingParams[paramName];
1724
+ }
1725
+ });
1726
+ }
1727
+ const fullUrl = `${client.baseUrl}/apps/${client.appId}${finalPath}`;
1728
+ if (path.includes("/services/files/private") && remainingParams && remainingParams.file instanceof File) {
1729
+ const file = remainingParams.file;
1730
+ try {
1731
+ const initResponse = await client.makeRequest(
1732
+ `${client.baseUrl}/apps/${client.appId}/services/files/private/init`,
1733
+ "POST",
1734
+ {
1735
+ filename: file.name,
1736
+ content_type: file.type || "application/octet-stream",
1737
+ size: file.size
1738
+ },
1739
+ { useServiceToken }
1740
+ );
1741
+ const { file_id, upload_url, file_uri } = initResponse;
1742
+ const uploadResponse = await fetch(upload_url, {
1743
+ method: "PUT",
1744
+ body: file,
1745
+ headers: {
1746
+ "Content-Type": file.type || "application/octet-stream"
1747
+ }
1748
+ });
1749
+ if (!uploadResponse.ok) {
1750
+ throw new OmnikitError(
1751
+ `Failed to upload private file to storage: ${uploadResponse.statusText}`,
1752
+ uploadResponse.status,
1753
+ "UPLOAD_FAILED"
1754
+ );
1755
+ }
1756
+ const completeResponse = await client.makeRequest(
1757
+ `${client.baseUrl}/apps/${client.appId}/services/files/private/complete/${file_id}`,
1758
+ "POST",
1759
+ null,
1760
+ { useServiceToken }
1761
+ );
1762
+ return completeResponse;
1763
+ } catch (error) {
1764
+ if (error instanceof OmnikitError) {
1765
+ throw error;
1766
+ }
1767
+ throw new OmnikitError(
1768
+ `Private file upload failed: ${error.message}`,
1769
+ 500,
1770
+ "UPLOAD_ERROR",
1771
+ error
1772
+ );
1773
+ }
1774
+ }
1775
+ if (remainingParams && remainingParams.file instanceof File) {
1776
+ const file = remainingParams.file;
1777
+ try {
1778
+ const initResponse = await client.makeRequest(
1779
+ `${client.baseUrl}/apps/${client.appId}/services/files/init`,
1780
+ "POST",
1781
+ {
1782
+ filename: file.name,
1783
+ content_type: file.type || "application/octet-stream",
1784
+ size: file.size
1785
+ },
1786
+ { useServiceToken }
1787
+ );
1788
+ const { file_id, upload_url, public_url } = initResponse;
1789
+ const uploadResponse = await fetch(upload_url, {
1790
+ method: "PUT",
1791
+ body: file,
1792
+ headers: {
1793
+ "Content-Type": file.type || "application/octet-stream"
1794
+ }
1795
+ });
1796
+ if (!uploadResponse.ok) {
1797
+ throw new OmnikitError(
1798
+ `Failed to upload file to storage: ${uploadResponse.statusText}`,
1799
+ uploadResponse.status,
1800
+ "UPLOAD_FAILED"
1801
+ );
1802
+ }
1803
+ const completeResponse = await client.makeRequest(
1804
+ `${client.baseUrl}/apps/${client.appId}/services/files/complete/${file_id}`,
1805
+ "POST",
1806
+ null,
1807
+ { useServiceToken }
1808
+ );
1809
+ return client.normalizeIntegrationResponse(completeResponse, path);
1810
+ } catch (error) {
1811
+ if (error instanceof OmnikitError) {
1812
+ throw error;
1813
+ }
1814
+ throw new OmnikitError(
1815
+ `File upload failed: ${error.message}`,
1816
+ 500,
1817
+ "UPLOAD_ERROR",
1818
+ error
1819
+ );
1820
+ }
1821
+ }
1822
+ const response = await client.makeRequest(
1823
+ fullUrl,
1824
+ method,
1825
+ method === "GET" ? null : remainingParams,
1826
+ { useServiceToken }
1827
+ );
1828
+ return client.normalizeIntegrationResponse(response, path);
1829
+ };
1830
+ }
1831
+ /**
1832
+ * Normalize integration response to add common aliases for bulletproof access
1833
+ * Prevents runtime errors from AI-generated code using wrong property names
1834
+ */
1835
+ normalizeIntegrationResponse(response, path) {
1836
+ if (!response || typeof response !== "object") {
1837
+ return response;
1838
+ }
1839
+ const normalized = { ...response };
1840
+ if (path.includes("/services/llm") && "result" in normalized && !("content" in normalized)) {
1841
+ Object.defineProperty(normalized, "content", {
1842
+ get() {
1843
+ return this.result;
1844
+ },
1845
+ enumerable: false
1846
+ // Don't show in JSON.stringify or Object.keys
1847
+ });
1848
+ }
1849
+ if (path.includes("/services/files") && "file_url" in normalized && !("url" in normalized)) {
1850
+ Object.defineProperty(normalized, "url", {
1851
+ get() {
1852
+ return this.file_url;
1853
+ },
1854
+ enumerable: false
1855
+ });
1856
+ }
1857
+ if (path.includes("/services/speech") && "url" in normalized && !("audio_url" in normalized)) {
1858
+ Object.defineProperty(normalized, "audio_url", {
1859
+ get() {
1860
+ return this.url;
1861
+ },
1862
+ enumerable: false
1863
+ });
1864
+ }
1865
+ if (path.includes("/services/extract") && "results" in normalized && Array.isArray(normalized.results)) {
1866
+ if (!("text" in normalized)) {
1867
+ Object.defineProperty(normalized, "text", {
1868
+ get() {
1869
+ const firstSuccess = this.results?.find((r) => r.success);
1870
+ return firstSuccess?.text || "";
1871
+ },
1872
+ enumerable: false
1873
+ });
1874
+ }
1875
+ }
1876
+ return normalized;
1877
+ }
1878
+ /**
1879
+ * HTTP request helper with JSON
1880
+ */
1881
+ async makeRequest(url, method = "GET", data = null, options) {
1882
+ let finalUrl = url;
1883
+ if (options?.queryParams && Object.keys(options.queryParams).length > 0) {
1884
+ const searchParams = new URLSearchParams();
1885
+ Object.entries(options.queryParams).forEach(([key, value]) => {
1886
+ if (value !== void 0 && value !== null) {
1887
+ searchParams.append(key, String(value));
1888
+ }
1889
+ });
1890
+ const queryString = searchParams.toString();
1891
+ if (queryString) {
1892
+ finalUrl = url.includes("?") ? `${url}&${queryString}` : `${url}?${queryString}`;
1893
+ }
1894
+ }
1895
+ const fetchOptions = {
1896
+ method,
1897
+ headers: {
1898
+ "Content-Type": "application/json"
1899
+ }
1900
+ // Removed credentials: 'include' to prevent sending cookies (switching to pure Bearer token)
1901
+ };
1902
+ const userToken = this.getAuthToken();
1903
+ if (userToken) {
1904
+ fetchOptions.headers.Authorization = `Bearer ${userToken}`;
1905
+ }
1906
+ if (this._serviceToken) {
1907
+ fetchOptions.headers["X-Service-Token"] = this._serviceToken;
1908
+ }
1909
+ if (this._apiKey) {
1910
+ fetchOptions.headers["X-API-Key"] = this._apiKey;
1911
+ }
1912
+ if (options?.headers) {
1913
+ Object.assign(fetchOptions.headers, options.headers);
1914
+ }
1915
+ if (data && (method === "POST" || method === "PUT" || method === "PATCH")) {
1916
+ fetchOptions.body = JSON.stringify(data);
1917
+ }
1918
+ const response = await fetch(finalUrl, fetchOptions);
1919
+ if (response.status === 401) {
1920
+ this.clearAuthToken();
1921
+ throw new OmnikitError(
1922
+ "Authentication required or token expired",
1923
+ 401,
1924
+ "UNAUTHORIZED"
1925
+ );
1926
+ }
1927
+ if (!response.ok) {
1928
+ const error = await response.json().catch(() => ({}));
1929
+ throw new OmnikitError(
1930
+ error.detail || error.message || `HTTP ${response.status}: ${response.statusText}`,
1931
+ response.status,
1932
+ error.code || "HTTP_ERROR",
1933
+ error
1934
+ );
1935
+ }
1936
+ if (response.status === 204 || method === "DELETE") {
1937
+ return null;
1938
+ }
1939
+ return await response.json();
1940
+ }
1941
+ /**
1942
+ * HTTP request helper with FormData (for file uploads)
1943
+ */
1944
+ async makeRequestWithFormData(url, formData, useServiceToken) {
1945
+ const fetchOptions = {
1946
+ method: "POST",
1947
+ body: formData,
1948
+ headers: {}
1949
+ // Removed credentials: 'include'
1950
+ };
1951
+ const userToken = this.getAuthToken();
1952
+ if (userToken) {
1953
+ fetchOptions.headers.Authorization = `Bearer ${userToken}`;
1954
+ }
1955
+ if (this._serviceToken) {
1956
+ fetchOptions.headers["X-Service-Token"] = this._serviceToken;
1957
+ }
1958
+ if (this._apiKey) {
1959
+ fetchOptions.headers["X-API-Key"] = this._apiKey;
1960
+ }
1961
+ const response = await fetch(url, fetchOptions);
1962
+ if (!response.ok) {
1963
+ const error = await response.json().catch(() => ({}));
1964
+ throw new OmnikitError(
1965
+ error.detail || error.message || `HTTP ${response.status}: ${response.statusText}`,
1966
+ response.status,
1967
+ error.code || "HTTP_ERROR",
1968
+ error
1969
+ );
1970
+ }
1971
+ return await response.json();
1972
+ }
1973
+ /**
1974
+ * Get app metadata (for internal use)
1975
+ */
1976
+ async getAppMetadata() {
1977
+ const response = await this.makeRequest(
1978
+ `${this.baseUrl}/apps/${this.appId}`,
1979
+ "GET"
1980
+ );
1981
+ if (response?.app || response?.name) {
1982
+ const app = response.app || response;
1983
+ this.updateMetadataCache({
1984
+ name: app.name,
1985
+ logoUrl: app.logo_url,
1986
+ thumbnailUrl: app.thumbnail_url
1987
+ });
1988
+ }
1989
+ return response;
1990
+ }
1991
+ /**
1992
+ * Set auth token
1993
+ */
1994
+ setAuthToken(token) {
1995
+ this.userToken = token;
1996
+ saveAccessToken(token);
1997
+ }
1998
+ /**
1999
+ * Get current auth token
2000
+ */
2001
+ getAuthToken() {
2002
+ return this.userToken;
2003
+ }
2004
+ /**
2005
+ * Clear auth token
2006
+ */
2007
+ clearAuthToken() {
2008
+ this.userToken = null;
2009
+ removeAccessToken();
2010
+ }
2011
+ /**
2012
+ * Create a live voice session for real-time voice conversation with AI.
2013
+ *
2014
+ * The session manages WebSocket communication, microphone capture, and audio playback.
2015
+ *
2016
+ * @example
2017
+ * ```typescript
2018
+ * const session = omnikit.createLiveVoiceSession({
2019
+ * systemInstruction: 'You are a helpful assistant.',
2020
+ * voice: 'Puck',
2021
+ * onTranscript: (text, role) => console.log(`${role}: ${text}`),
2022
+ * onStatusChange: (status) => console.log(`Status: ${status}`),
2023
+ * onError: (error) => console.error(error),
2024
+ * });
2025
+ *
2026
+ * await session.start();
2027
+ * // ... user speaks, AI responds ...
2028
+ * await session.stop();
2029
+ * ```
2030
+ *
2031
+ * @param config - Optional configuration for the voice session
2032
+ * @returns A LiveVoiceSession object to control the session
2033
+ */
2034
+ createLiveVoiceSession(config) {
2035
+ return new LiveVoiceSessionImpl(
2036
+ this.baseUrl,
2037
+ this.appId,
2038
+ this.getAuthToken(),
2039
+ config
2040
+ );
2041
+ }
2042
+ /**
2043
+ * Invoke a backend function by name.
2044
+ *
2045
+ * Backend functions are deployed to Supabase Edge Functions and can be invoked
2046
+ * from the frontend using this method.
2047
+ *
2048
+ * @example
2049
+ * ```typescript
2050
+ * // Invoke a function
2051
+ * const result = await omnikit.invokeFunction('processPayment', {
2052
+ * amount: 100,
2053
+ * userId: 'abc123'
2054
+ * });
2055
+ * console.log(result.transactionId); // Direct access to function response
2056
+ * ```
2057
+ *
2058
+ * @param functionName - Name of the function to invoke (matches filename without .js)
2059
+ * @param body - Optional request body to send to the function
2060
+ * @returns Response data from the function (unwrapped)
2061
+ * @throws Error if the function invocation fails
2062
+ */
2063
+ async invokeFunction(functionName, body) {
2064
+ await this.ensureInitialized();
2065
+ const response = await this.makeRequest(
2066
+ `${this.baseUrl}/apps/${this.appId}/functions/invoke/${functionName}`,
2067
+ "POST",
2068
+ { body }
2069
+ );
2070
+ if (response && typeof response === "object") {
2071
+ if (response.success === false && response.error) {
2072
+ throw new Error(response.error);
2073
+ }
2074
+ if ("data" in response) {
2075
+ return response.data;
2076
+ }
2077
+ }
2078
+ return response;
2079
+ }
2080
+ };
2081
+ function createClient(config) {
2082
+ return new APIClient(config);
2083
+ }
2084
+
2085
+ exports.APIClient = APIClient;
2086
+ exports.LiveVoiceSessionImpl = LiveVoiceSessionImpl;
2087
+ exports.OmnikitError = OmnikitError;
2088
+ exports.cleanTokenFromUrl = cleanTokenFromUrl;
2089
+ exports.createClient = createClient;
2090
+ exports.getAccessToken = getAccessToken;
2091
+ exports.isTokenInUrl = isTokenInUrl;
2092
+ exports.removeAccessToken = removeAccessToken;
2093
+ exports.saveAccessToken = saveAccessToken;
2094
+ exports.setAccessToken = setAccessToken;
2095
+ //# sourceMappingURL=index.js.map
2096
+ //# sourceMappingURL=index.js.map