@omnikit-ai/sdk 2.0.9 → 2.2.0

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 CHANGED
@@ -435,6 +435,53 @@ var LiveVoiceSessionImpl = class {
435
435
  }
436
436
  };
437
437
 
438
+ // src/connectors.ts
439
+ function createConnectorsModule(makeRequest, appId, baseUrl, getServiceToken) {
440
+ return {
441
+ async getAccessToken(connectorType) {
442
+ const serviceToken = getServiceToken();
443
+ if (!serviceToken) {
444
+ throw new Error(
445
+ "Service token is required to get connector access token. This method is only available in backend functions. Make sure you created the client with a serviceToken."
446
+ );
447
+ }
448
+ return makeRequest(
449
+ `${baseUrl}/apps/${appId}/connectors/${connectorType}/access-token`,
450
+ "GET",
451
+ null,
452
+ { useServiceToken: true }
453
+ );
454
+ },
455
+ async isConnected(connectorType) {
456
+ try {
457
+ const response = await makeRequest(
458
+ `${baseUrl}/apps/${appId}/connectors/${connectorType}`,
459
+ "GET"
460
+ );
461
+ return response?.connector?.status === "connected";
462
+ } catch {
463
+ return false;
464
+ }
465
+ },
466
+ async getStatus(connectorType) {
467
+ try {
468
+ const response = await makeRequest(
469
+ `${baseUrl}/apps/${appId}/connectors/${connectorType}`,
470
+ "GET"
471
+ );
472
+ return {
473
+ success: true,
474
+ connector: response?.connector
475
+ };
476
+ } catch (error) {
477
+ return {
478
+ success: false
479
+ };
480
+ }
481
+ }
482
+ };
483
+ }
484
+
438
485
  // src/client.ts
439
486
  var LLM_MODEL_MAP = {
440
487
  // Gemini 2.5 models
@@ -529,10 +576,96 @@ var APIClient = class {
529
576
  this.userToken = detectedToken;
530
577
  }
531
578
  }
579
+ this.initializeBuiltInServices();
532
580
  this.ensureInitialized().catch((err) => {
533
581
  console.warn("[Omnikit SDK] Background initialization failed:", err);
534
582
  });
535
583
  }
584
+ /**
585
+ * Initialize built-in services with their definitions
586
+ * These services don't require backend schema - they're defined in the SDK
587
+ */
588
+ initializeBuiltInServices() {
589
+ const builtInServices = [
590
+ {
591
+ name: "SendEmail",
592
+ path: "/services/email",
593
+ description: "Send an email",
594
+ method: "POST",
595
+ params: { to: "string", subject: "string", body: "string" }
596
+ },
597
+ {
598
+ name: "InvokeLLM",
599
+ path: "/services/llm",
600
+ description: "Invoke a language model",
601
+ method: "POST",
602
+ params: { messages: "array", model: "string" }
603
+ },
604
+ {
605
+ name: "GenerateImage",
606
+ path: "/services/images",
607
+ description: "Generate an image from a prompt",
608
+ method: "POST",
609
+ async: true,
610
+ params: { prompt: "string" }
611
+ },
612
+ {
613
+ name: "GenerateSpeech",
614
+ path: "/services/speech",
615
+ description: "Generate speech from text",
616
+ method: "POST",
617
+ async: true,
618
+ params: { text: "string" }
619
+ },
620
+ {
621
+ name: "GenerateVideo",
622
+ path: "/services/video",
623
+ description: "Generate a video from a prompt",
624
+ method: "POST",
625
+ async: true,
626
+ params: { prompt: "string" }
627
+ },
628
+ {
629
+ name: "ExtractData",
630
+ path: "/services/extract-text",
631
+ description: "Extract structured data from text or files",
632
+ method: "POST",
633
+ async: true,
634
+ params: { file_url: "string" }
635
+ },
636
+ {
637
+ name: "SendSMS",
638
+ path: "/services/sms",
639
+ description: "Send an SMS message",
640
+ method: "POST",
641
+ params: { to: "string", body: "string" }
642
+ },
643
+ {
644
+ name: "UploadFile",
645
+ path: "/services/files",
646
+ description: "Upload a file (public)",
647
+ method: "POST",
648
+ params: { file: "File" }
649
+ },
650
+ {
651
+ name: "UploadPrivateFile",
652
+ path: "/services/files/private",
653
+ description: "Upload a private file",
654
+ method: "POST",
655
+ params: { file: "File" }
656
+ },
657
+ {
658
+ name: "CreateFileSignedUrl",
659
+ path: "/services/files/signed-url",
660
+ description: "Create a signed URL for a private file",
661
+ method: "POST",
662
+ params: { file_uri: "string" }
663
+ }
664
+ ];
665
+ builtInServices.forEach((service) => {
666
+ this._services[service.name] = this.createServiceMethod(service);
667
+ });
668
+ }
536
669
  /**
537
670
  * Load metadata from localStorage cache, falling back to initial config
538
671
  * Guards localStorage access for Deno/Node compatibility
@@ -672,13 +805,13 @@ var APIClient = class {
672
805
  return this.createAuthProxy();
673
806
  }
674
807
  /**
675
- * Lazy getter for service role operations
676
- * Only available when serviceToken is provided
808
+ * Service-level operations (elevated privileges).
809
+ * Only available when serviceToken is provided (e.g., via createServerClient).
677
810
  */
678
- get asServiceRole() {
811
+ get service() {
679
812
  if (!this._serviceToken) {
680
813
  throw new OmnikitError(
681
- "Service token is required to use asServiceRole. Provide serviceToken in config.",
814
+ "Service token is required. Use createServerClient(req) in backend functions.",
682
815
  403,
683
816
  "SERVICE_TOKEN_REQUIRED"
684
817
  );
@@ -691,12 +824,51 @@ var APIClient = class {
691
824
  // Service role collections
692
825
  services: this.createServicesProxy(true),
693
826
  // Service role services (new flat structure)
694
- integrations: this.createIntegrationsProxy(true)
827
+ integrations: this.createIntegrationsProxy(true),
695
828
  // Service role integrations (legacy)
829
+ connectors: this.connectors
830
+ // Connectors module (requires service token)
696
831
  // Note: auth not available in service role for security
697
832
  };
698
833
  return this._asServiceRole;
699
834
  }
835
+ /**
836
+ * @deprecated Use service instead
837
+ */
838
+ get asServiceRole() {
839
+ return this.service;
840
+ }
841
+ /**
842
+ * Lazy getter for connectors module
843
+ * Like Base44's connectors - provides access to external service tokens
844
+ *
845
+ * SECURITY: getAccessToken requires service token authentication.
846
+ * Only use in backend functions, not frontend code.
847
+ *
848
+ * @example
849
+ * ```typescript
850
+ * // In a backend function
851
+ * const { access_token } = await omnikit.connectors.getAccessToken('slack');
852
+ *
853
+ * // Make direct Slack API call
854
+ * const response = await fetch('https://slack.com/api/chat.postMessage', {
855
+ * method: 'POST',
856
+ * headers: { Authorization: `Bearer ${access_token}` },
857
+ * body: JSON.stringify({ channel: '#general', text: 'Hello!' })
858
+ * });
859
+ * ```
860
+ */
861
+ get connectors() {
862
+ if (!this._connectors) {
863
+ this._connectors = createConnectorsModule(
864
+ this.makeRequest.bind(this),
865
+ this.appId,
866
+ this.baseUrl,
867
+ () => this._serviceToken
868
+ );
869
+ }
870
+ return this._connectors;
871
+ }
700
872
  /**
701
873
  * Create auth proxy that auto-initializes
702
874
  */
@@ -1191,57 +1363,11 @@ Example: await ${collectionName}.list({ limit: 100, sort: '-created_at' })`,
1191
1363
  if (method) {
1192
1364
  response = await method(params, useServiceToken);
1193
1365
  } else {
1194
- const servicePathMap = {
1195
- "SendEmail": "services/email",
1196
- "InvokeLLM": "services/llm",
1197
- "GenerateImage": "services/images",
1198
- "GenerateSpeech": "services/speech",
1199
- "GenerateVideo": "services/video",
1200
- "ExtractData": "services/extract-text",
1201
- "SendSMS": "services/sms",
1202
- "UploadFile": "services/files",
1203
- "UploadPrivateFile": "services/files/private/init",
1204
- "CreateFileSignedUrl": "services/files/signed-url"
1205
- };
1206
- const servicePath = servicePathMap[normalizedName];
1207
- if (!servicePath) {
1208
- throw new OmnikitError(
1209
- `Service '${serviceName}' not found. Known services: ${Object.keys(servicePathMap).join(", ")} (camelCase also supported)`,
1210
- 404,
1211
- "SERVICE_NOT_FOUND"
1212
- );
1213
- }
1214
- const headers = {
1215
- "Content-Type": "application/json"
1216
- };
1217
- if (client._apiKey) {
1218
- headers["X-API-Key"] = client._apiKey;
1219
- } else if (client.userToken) {
1220
- headers["Authorization"] = `Bearer ${client.userToken}`;
1221
- }
1222
- if ((normalizedName === "UploadFile" || normalizedName === "uploadFile") && params?.file instanceof File) {
1223
- return client.handleFileUpload(params.file, servicePath, useServiceToken);
1224
- }
1225
- if ((normalizedName === "UploadPrivateFile" || normalizedName === "uploadPrivateFile") && params?.file instanceof File) {
1226
- return client.handleFileUpload(params.file, servicePath, useServiceToken);
1227
- }
1228
- const fetchResponse = await fetch(
1229
- `${client.baseUrl}/apps/${client.appId}/${servicePath}`,
1230
- {
1231
- method: "POST",
1232
- headers,
1233
- body: JSON.stringify(params || {})
1234
- }
1366
+ throw new OmnikitError(
1367
+ `Service '${serviceName}' not found. Known services: SendEmail, InvokeLLM, GenerateImage, GenerateSpeech, GenerateVideo, ExtractData, SendSMS, UploadFile, UploadPrivateFile, CreateFileSignedUrl (camelCase also supported)`,
1368
+ 404,
1369
+ "SERVICE_NOT_FOUND"
1235
1370
  );
1236
- if (!fetchResponse.ok) {
1237
- const error = await fetchResponse.json().catch(() => ({}));
1238
- throw new OmnikitError(
1239
- error.detail || `Service call failed: ${fetchResponse.statusText}`,
1240
- fetchResponse.status,
1241
- "SERVICE_CALL_FAILED"
1242
- );
1243
- }
1244
- response = await fetchResponse.json();
1245
1371
  }
1246
1372
  if (response && response.async && response.job_id) {
1247
1373
  if (asyncOptions?.returnJobId) {
@@ -2196,12 +2322,298 @@ Example: await ${collectionName}.list({ limit: 100, sort: '-created_at' })`,
2196
2322
  function createClient(config) {
2197
2323
  return new APIClient(config);
2198
2324
  }
2325
+ function createServerClient(request) {
2326
+ const getHeader = (name) => {
2327
+ if (typeof request.headers.get === "function") {
2328
+ return request.headers.get(name);
2329
+ }
2330
+ return request.headers[name] || request.headers[name.toLowerCase()] || null;
2331
+ };
2332
+ const authHeader = getHeader("Authorization") || getHeader("authorization");
2333
+ const userToken = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null;
2334
+ const serviceToken = getHeader("X-Omnikit-Service-Authorization") || getHeader("x-omnikit-service-authorization");
2335
+ const appId = getHeader("X-Omnikit-App-Id") || getHeader("x-omnikit-app-id");
2336
+ const serverUrl = getHeader("X-Omnikit-Server-Url") || getHeader("x-omnikit-server-url") || "https://omnikit.ai/api";
2337
+ if (!appId) {
2338
+ throw new OmnikitError(
2339
+ "X-Omnikit-App-Id header is required. This function should be invoked through the Omnikit platform.",
2340
+ 400,
2341
+ "MISSING_APP_ID"
2342
+ );
2343
+ }
2344
+ return createClient({
2345
+ appId,
2346
+ serverUrl,
2347
+ token: userToken || void 0,
2348
+ serviceToken: serviceToken || void 0,
2349
+ autoInitAuth: false
2350
+ // Don't auto-detect from localStorage in backend
2351
+ });
2352
+ }
2353
+ var createClientFromRequest = createServerClient;
2354
+
2355
+ // src/analytics.ts
2356
+ var STORAGE_KEY = "omnikit_session";
2357
+ var SESSION_TIMEOUT = 30 * 60 * 1e3;
2358
+ var FLUSH_INTERVAL = 5e3;
2359
+ var MAX_RETRIES = 3;
2360
+ var Analytics = class {
2361
+ constructor(config) {
2362
+ this.eventQueue = [];
2363
+ this.config = config;
2364
+ this.enabled = config.enabled !== false;
2365
+ this.sessionId = config.sessionId || this.initSession();
2366
+ this.userId = config.userId;
2367
+ if (this.enabled && typeof window !== "undefined") {
2368
+ this.startFlushTimer();
2369
+ window.addEventListener("beforeunload", () => this.flush());
2370
+ window.addEventListener("pagehide", () => this.flush());
2371
+ }
2372
+ }
2373
+ // ==========================================================================
2374
+ // Session Management
2375
+ // ==========================================================================
2376
+ initSession() {
2377
+ if (typeof window === "undefined" || typeof localStorage === "undefined") {
2378
+ return this.generateId();
2379
+ }
2380
+ try {
2381
+ const stored = localStorage.getItem(STORAGE_KEY);
2382
+ if (stored) {
2383
+ const { id, timestamp } = JSON.parse(stored);
2384
+ if (Date.now() - timestamp < SESSION_TIMEOUT) {
2385
+ localStorage.setItem(STORAGE_KEY, JSON.stringify({
2386
+ id,
2387
+ timestamp: Date.now()
2388
+ }));
2389
+ return id;
2390
+ }
2391
+ }
2392
+ } catch {
2393
+ }
2394
+ const newId = this.generateId();
2395
+ this.saveSession(newId);
2396
+ return newId;
2397
+ }
2398
+ generateId() {
2399
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
2400
+ return crypto.randomUUID();
2401
+ }
2402
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
2403
+ const r = Math.random() * 16 | 0;
2404
+ const v = c === "x" ? r : r & 3 | 8;
2405
+ return v.toString(16);
2406
+ });
2407
+ }
2408
+ saveSession(id) {
2409
+ if (typeof localStorage === "undefined") return;
2410
+ try {
2411
+ localStorage.setItem(STORAGE_KEY, JSON.stringify({
2412
+ id,
2413
+ timestamp: Date.now()
2414
+ }));
2415
+ } catch {
2416
+ }
2417
+ }
2418
+ /**
2419
+ * Get the current session ID
2420
+ */
2421
+ getSessionId() {
2422
+ return this.sessionId;
2423
+ }
2424
+ /**
2425
+ * Start a new session (e.g., after logout)
2426
+ */
2427
+ startNewSession() {
2428
+ this.sessionId = this.generateId();
2429
+ this.saveSession(this.sessionId);
2430
+ return this.sessionId;
2431
+ }
2432
+ // ==========================================================================
2433
+ // User Identification
2434
+ // ==========================================================================
2435
+ /**
2436
+ * Associate events with a user ID
2437
+ */
2438
+ setUserId(userId) {
2439
+ this.userId = userId;
2440
+ }
2441
+ /**
2442
+ * Clear user association (e.g., on logout)
2443
+ */
2444
+ clearUserId() {
2445
+ this.userId = void 0;
2446
+ }
2447
+ // ==========================================================================
2448
+ // Core Logging Methods
2449
+ // ==========================================================================
2450
+ /**
2451
+ * Log a custom event
2452
+ */
2453
+ async logEvent(eventType, payload = {}) {
2454
+ if (!this.enabled) return;
2455
+ this.eventQueue.push({
2456
+ eventType,
2457
+ payload,
2458
+ timestamp: Date.now()
2459
+ });
2460
+ if (eventType === "error" || eventType === "api_error") {
2461
+ await this.flush();
2462
+ }
2463
+ }
2464
+ /**
2465
+ * Log a page view event
2466
+ */
2467
+ async logPageView(pageName, metadata) {
2468
+ if (!this.enabled) return;
2469
+ const url = typeof window !== "undefined" ? window.location.href : "";
2470
+ const referrer = typeof document !== "undefined" ? document.referrer : "";
2471
+ await this.logEvent("page_view", {
2472
+ page_name: pageName,
2473
+ metadata: {
2474
+ url,
2475
+ referrer,
2476
+ ...metadata
2477
+ }
2478
+ });
2479
+ }
2480
+ /**
2481
+ * Log an error event
2482
+ */
2483
+ async logError(error, componentStack) {
2484
+ if (!this.enabled) return;
2485
+ const url = typeof window !== "undefined" ? window.location.href : "";
2486
+ await this.logEvent("error", {
2487
+ error_message: error.message,
2488
+ error_stack: error.stack,
2489
+ metadata: {
2490
+ name: error.name,
2491
+ component_stack: componentStack,
2492
+ url
2493
+ }
2494
+ });
2495
+ }
2496
+ /**
2497
+ * Log an API error event
2498
+ */
2499
+ async logApiError(endpoint, statusCode, errorMessage, metadata) {
2500
+ if (!this.enabled) return;
2501
+ await this.logEvent("api_error", {
2502
+ error_message: errorMessage,
2503
+ metadata: {
2504
+ endpoint,
2505
+ status_code: statusCode,
2506
+ ...metadata
2507
+ }
2508
+ });
2509
+ }
2510
+ // ==========================================================================
2511
+ // Event Batching & Flushing
2512
+ // ==========================================================================
2513
+ startFlushTimer() {
2514
+ if (this.flushTimer) return;
2515
+ this.flushTimer = setInterval(() => {
2516
+ if (this.eventQueue.length > 0) {
2517
+ this.flush();
2518
+ }
2519
+ }, FLUSH_INTERVAL);
2520
+ }
2521
+ /**
2522
+ * Flush all queued events to the server
2523
+ */
2524
+ async flush() {
2525
+ if (this.eventQueue.length === 0) return;
2526
+ const events = [...this.eventQueue];
2527
+ this.eventQueue = [];
2528
+ const sendPromises = events.map(
2529
+ ({ eventType, payload }) => this.sendEvent(eventType, payload)
2530
+ );
2531
+ await Promise.allSettled(sendPromises);
2532
+ }
2533
+ async sendEvent(eventType, payload) {
2534
+ const pageName = payload.page_name || (typeof window !== "undefined" ? window.location.pathname : "/");
2535
+ const url = `${this.config.apiUrl}/api/app-logs/${this.config.appId}/log-event`;
2536
+ const body = {
2537
+ session_id: this.sessionId,
2538
+ user_id: this.userId,
2539
+ event_type: eventType,
2540
+ page_name: pageName,
2541
+ action: payload.action,
2542
+ inputs: payload.inputs,
2543
+ metadata: payload.metadata,
2544
+ is_error: eventType === "error" || eventType === "api_error",
2545
+ error_message: payload.error_message,
2546
+ error_stack: payload.error_stack
2547
+ };
2548
+ for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
2549
+ try {
2550
+ const response = await fetch(url, {
2551
+ method: "POST",
2552
+ headers: { "Content-Type": "application/json" },
2553
+ body: JSON.stringify(body),
2554
+ keepalive: true
2555
+ // Ensures request completes even on page unload
2556
+ });
2557
+ if (response.ok) {
2558
+ return;
2559
+ }
2560
+ if (response.status >= 400 && response.status < 500) {
2561
+ console.warn(`[Omnikit Analytics] Event rejected: ${response.status}`);
2562
+ return;
2563
+ }
2564
+ } catch (err) {
2565
+ }
2566
+ if (attempt < MAX_RETRIES - 1) {
2567
+ await new Promise((resolve) => setTimeout(resolve, Math.pow(2, attempt) * 1e3));
2568
+ }
2569
+ }
2570
+ console.warn("[Omnikit Analytics] Failed to send event after retries");
2571
+ }
2572
+ // ==========================================================================
2573
+ // Lifecycle
2574
+ // ==========================================================================
2575
+ /**
2576
+ * Enable or disable analytics
2577
+ */
2578
+ setEnabled(enabled) {
2579
+ this.enabled = enabled;
2580
+ if (enabled && typeof window !== "undefined") {
2581
+ this.startFlushTimer();
2582
+ } else if (this.flushTimer) {
2583
+ clearInterval(this.flushTimer);
2584
+ this.flushTimer = void 0;
2585
+ }
2586
+ }
2587
+ /**
2588
+ * Check if analytics is enabled
2589
+ */
2590
+ isEnabled() {
2591
+ return this.enabled;
2592
+ }
2593
+ /**
2594
+ * Clean up resources
2595
+ */
2596
+ destroy() {
2597
+ if (this.flushTimer) {
2598
+ clearInterval(this.flushTimer);
2599
+ this.flushTimer = void 0;
2600
+ }
2601
+ this.flush();
2602
+ }
2603
+ };
2604
+ function createAnalytics(config) {
2605
+ return new Analytics(config);
2606
+ }
2199
2607
 
2200
2608
  exports.APIClient = APIClient;
2609
+ exports.Analytics = Analytics;
2201
2610
  exports.LiveVoiceSessionImpl = LiveVoiceSessionImpl;
2202
2611
  exports.OmnikitError = OmnikitError;
2203
2612
  exports.cleanTokenFromUrl = cleanTokenFromUrl;
2613
+ exports.createAnalytics = createAnalytics;
2204
2614
  exports.createClient = createClient;
2615
+ exports.createClientFromRequest = createClientFromRequest;
2616
+ exports.createServerClient = createServerClient;
2205
2617
  exports.getAccessToken = getAccessToken;
2206
2618
  exports.isTokenInUrl = isTokenInUrl;
2207
2619
  exports.removeAccessToken = removeAccessToken;