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