@realtimex/sdk 1.0.9 → 1.1.2

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 CHANGED
@@ -14,12 +14,19 @@ var PermissionRequiredError = class extends Error {
14
14
  }
15
15
  };
16
16
  var ApiModule = class {
17
- constructor(realtimexUrl, appId, appName) {
17
+ constructor(realtimexUrl, appId, appName, apiKey) {
18
18
  this.realtimexUrl = realtimexUrl.replace(/\/$/, "");
19
19
  this.appId = appId;
20
20
  this.appName = appName || process.env.RTX_APP_NAME || "Local App";
21
+ this.apiKey = apiKey;
21
22
  }
22
23
  getHeaders() {
24
+ if (this.apiKey) {
25
+ return {
26
+ "Content-Type": "application/json",
27
+ "Authorization": `Bearer ${this.apiKey}`
28
+ };
29
+ }
23
30
  return {
24
31
  "Content-Type": "application/json",
25
32
  "x-app-id": this.appId
@@ -98,10 +105,11 @@ var ApiModule = class {
98
105
 
99
106
  // src/modules/activities.ts
100
107
  var ActivitiesModule = class {
101
- constructor(realtimexUrl, appId, appName) {
108
+ constructor(realtimexUrl, appId, appName, apiKey) {
102
109
  this.baseUrl = realtimexUrl.replace(/\/$/, "");
103
110
  this.appId = appId;
104
111
  this.appName = appName || process.env.RTX_APP_NAME || "Local App";
112
+ this.apiKey = apiKey;
105
113
  }
106
114
  /**
107
115
  * Request a single permission from Electron via internal API
@@ -129,8 +137,10 @@ var ActivitiesModule = class {
129
137
  const headers = {
130
138
  "Content-Type": "application/json"
131
139
  };
132
- if (this.appId) {
133
- headers["X-App-Id"] = this.appId;
140
+ if (this.apiKey) {
141
+ headers["Authorization"] = `Bearer ${this.apiKey}`;
142
+ } else if (this.appId) {
143
+ headers["x-app-id"] = this.appId;
134
144
  }
135
145
  const response = await fetch(url, {
136
146
  ...options,
@@ -217,10 +227,11 @@ var ActivitiesModule = class {
217
227
 
218
228
  // src/modules/webhook.ts
219
229
  var WebhookModule = class {
220
- constructor(realtimexUrl, appName, appId) {
230
+ constructor(realtimexUrl, appName, appId, apiKey) {
221
231
  this.realtimexUrl = realtimexUrl.replace(/\/$/, "");
222
232
  this.appName = appName;
223
233
  this.appId = appId;
234
+ this.apiKey = apiKey;
224
235
  }
225
236
  /**
226
237
  * Request a single permission from Electron via internal API
@@ -245,12 +256,18 @@ var WebhookModule = class {
245
256
  }
246
257
  async request(path, options = {}) {
247
258
  const url = `${this.realtimexUrl}${path}`;
259
+ const headers = {
260
+ "Content-Type": "application/json",
261
+ ...options.headers
262
+ };
263
+ if (this.apiKey) {
264
+ headers["Authorization"] = `Bearer ${this.apiKey}`;
265
+ } else if (this.appId) {
266
+ headers["x-app-id"] = this.appId;
267
+ }
248
268
  const response = await fetch(url, {
249
269
  ...options,
250
- headers: {
251
- "Content-Type": "application/json",
252
- ...options.headers
253
- }
270
+ headers
254
271
  });
255
272
  const data = await response.json();
256
273
  if (response.status === 403) {
@@ -309,10 +326,11 @@ var WebhookModule = class {
309
326
 
310
327
  // src/modules/task.ts
311
328
  var TaskModule = class {
312
- constructor(realtimexUrl, appName, appId) {
329
+ constructor(realtimexUrl, appName, appId, apiKey) {
313
330
  this.realtimexUrl = realtimexUrl.replace(/\/$/, "");
314
331
  this.appName = appName;
315
332
  this.appId = appId;
333
+ this.apiKey = apiKey;
316
334
  }
317
335
  /**
318
336
  * Mark task as processing
@@ -333,9 +351,15 @@ var TaskModule = class {
333
351
  return this._sendEvent("task-fail", taskUuid, { error, machine_id: machineId });
334
352
  }
335
353
  async _sendEvent(event, taskUuid, extra) {
354
+ const headers = { "Content-Type": "application/json" };
355
+ if (this.apiKey) {
356
+ headers["Authorization"] = `Bearer ${this.apiKey}`;
357
+ } else if (this.appId) {
358
+ headers["x-app-id"] = this.appId;
359
+ }
336
360
  const response = await fetch(`${this.realtimexUrl}/webhooks/realtimex`, {
337
361
  method: "POST",
338
- headers: { "Content-Type": "application/json" },
362
+ headers,
339
363
  body: JSON.stringify({
340
364
  app_name: this.appName,
341
365
  app_id: this.appId,
@@ -429,21 +453,445 @@ var PortModule = class {
429
453
  }
430
454
  };
431
455
 
456
+ // src/modules/llm.ts
457
+ var LLMPermissionError = class extends Error {
458
+ constructor(permission, code = "PERMISSION_REQUIRED") {
459
+ super(`Permission required: ${permission}`);
460
+ this.permission = permission;
461
+ this.code = code;
462
+ this.name = "LLMPermissionError";
463
+ }
464
+ };
465
+ var LLMProviderError = class extends Error {
466
+ constructor(message, code = "LLM_ERROR") {
467
+ super(message);
468
+ this.code = code;
469
+ this.name = "LLMProviderError";
470
+ }
471
+ };
472
+ var VectorStore = class {
473
+ constructor(baseUrl, appId, apiKey) {
474
+ this.baseUrl = baseUrl;
475
+ this.appId = appId;
476
+ this.apiKey = apiKey;
477
+ }
478
+ get headers() {
479
+ if (this.apiKey) {
480
+ return {
481
+ "Content-Type": "application/json",
482
+ "Authorization": `Bearer ${this.apiKey}`
483
+ };
484
+ }
485
+ return {
486
+ "Content-Type": "application/json",
487
+ "x-app-id": this.appId
488
+ };
489
+ }
490
+ /**
491
+ * Upsert (insert or update) vectors into storage
492
+ *
493
+ * @example
494
+ * ```ts
495
+ * await sdk.llm.vectors.upsert([
496
+ * { id: 'chunk-1', vector: embeddings[0], metadata: { text: 'Hello', documentId: 'doc-1' } }
497
+ * ], { workspaceId: 'ws-123' });
498
+ * ```
499
+ */
500
+ async upsert(vectors, options = {}) {
501
+ const response = await fetch(`${this.baseUrl}/sdk/llm/vectors/upsert`, {
502
+ method: "POST",
503
+ headers: this.headers,
504
+ body: JSON.stringify({
505
+ vectors,
506
+ workspaceId: options.workspaceId
507
+ })
508
+ });
509
+ const data = await response.json();
510
+ if (data.code === "PERMISSION_REQUIRED") {
511
+ throw new LLMPermissionError(data.permission || "vectors.write");
512
+ }
513
+ return data;
514
+ }
515
+ /**
516
+ * Query similar vectors by embedding
517
+ *
518
+ * @example
519
+ * ```ts
520
+ * const results = await sdk.llm.vectors.query(queryVector, {
521
+ * topK: 5,
522
+ * filter: { documentId: 'doc-1' },
523
+ * workspaceId: 'ws-123'
524
+ * });
525
+ * ```
526
+ */
527
+ async query(vector, options = {}) {
528
+ const response = await fetch(`${this.baseUrl}/sdk/llm/vectors/query`, {
529
+ method: "POST",
530
+ headers: this.headers,
531
+ body: JSON.stringify({
532
+ vector,
533
+ topK: options.topK ?? 5,
534
+ filter: options.filter,
535
+ workspaceId: options.workspaceId
536
+ })
537
+ });
538
+ const data = await response.json();
539
+ if (data.code === "PERMISSION_REQUIRED") {
540
+ throw new LLMPermissionError(data.permission || "vectors.read");
541
+ }
542
+ return data;
543
+ }
544
+ /**
545
+ * Delete vectors from storage
546
+ *
547
+ * Note: Currently only supports deleteAll: true
548
+ * Use workspaceId to scope deletion to a specific workspace
549
+ *
550
+ * @example
551
+ * ```ts
552
+ * await sdk.llm.vectors.delete({ deleteAll: true, workspaceId: 'ws-123' });
553
+ * ```
554
+ */
555
+ async delete(options) {
556
+ const response = await fetch(`${this.baseUrl}/sdk/llm/vectors/delete`, {
557
+ method: "POST",
558
+ headers: this.headers,
559
+ body: JSON.stringify(options)
560
+ });
561
+ const data = await response.json();
562
+ if (data.code === "PERMISSION_REQUIRED") {
563
+ throw new LLMPermissionError(data.permission || "vectors.write");
564
+ }
565
+ return data;
566
+ }
567
+ /**
568
+ * List all available workspaces (namespaces) for this app
569
+ *
570
+ * @example
571
+ * ```ts
572
+ * const { workspaces } = await sdk.llm.vectors.listWorkspaces();
573
+ * console.log('Workspaces:', workspaces);
574
+ * ```
575
+ */
576
+ async listWorkspaces() {
577
+ const response = await fetch(`${this.baseUrl}/sdk/llm/vectors/workspaces`, {
578
+ method: "GET",
579
+ headers: this.headers
580
+ });
581
+ const data = await response.json();
582
+ if (data.code === "PERMISSION_REQUIRED") {
583
+ throw new LLMPermissionError(data.permission || "vectors.read");
584
+ }
585
+ return data;
586
+ }
587
+ };
588
+ var LLMModule = class {
589
+ constructor(baseUrl, appId, apiKey) {
590
+ this.baseUrl = baseUrl;
591
+ this.appId = appId;
592
+ this.apiKey = apiKey;
593
+ this.vectors = new VectorStore(baseUrl, appId, apiKey);
594
+ }
595
+ get headers() {
596
+ if (this.apiKey) {
597
+ return {
598
+ "Content-Type": "application/json",
599
+ "Authorization": `Bearer ${this.apiKey}`
600
+ };
601
+ }
602
+ return {
603
+ "Content-Type": "application/json",
604
+ "x-app-id": this.appId
605
+ };
606
+ }
607
+ /**
608
+ * Get only configured chat (LLM) providers
609
+ *
610
+ * @example
611
+ * ```ts
612
+ * const { providers } = await sdk.llm.chatProviders();
613
+ * console.log('Available chat models:', providers[0].models);
614
+ * ```
615
+ */
616
+ async chatProviders() {
617
+ const response = await fetch(`${this.baseUrl}/sdk/llm/providers/chat`, {
618
+ method: "GET",
619
+ headers: this.headers
620
+ });
621
+ const data = await response.json();
622
+ if (data.code === "PERMISSION_REQUIRED") {
623
+ throw new LLMPermissionError(data.permission || "llm.providers");
624
+ }
625
+ return data;
626
+ }
627
+ /**
628
+ * Get only configured embedding providers
629
+ *
630
+ * @example
631
+ * ```ts
632
+ * const { providers } = await sdk.llm.embedProviders();
633
+ * console.log('Available embedding models:', providers[0].models);
634
+ * ```
635
+ */
636
+ async embedProviders() {
637
+ const response = await fetch(`${this.baseUrl}/sdk/llm/providers/embed`, {
638
+ method: "GET",
639
+ headers: this.headers
640
+ });
641
+ const data = await response.json();
642
+ if (data.code === "PERMISSION_REQUIRED") {
643
+ throw new LLMPermissionError(data.permission || "llm.providers");
644
+ }
645
+ return data;
646
+ }
647
+ /**
648
+ * Send a chat completion request (synchronous)
649
+ *
650
+ * @example
651
+ * ```ts
652
+ * const response = await sdk.llm.chat([
653
+ * { role: 'system', content: 'You are a helpful assistant.' },
654
+ * { role: 'user', content: 'Hello!' }
655
+ * ], { model: 'gpt-4o', temperature: 0.7 });
656
+ *
657
+ * console.log(response.response?.content);
658
+ * ```
659
+ */
660
+ async chat(messages, options = {}) {
661
+ const response = await fetch(`${this.baseUrl}/sdk/llm/chat`, {
662
+ method: "POST",
663
+ headers: this.headers,
664
+ body: JSON.stringify({
665
+ messages,
666
+ model: options.model,
667
+ provider: options.provider,
668
+ temperature: options.temperature ?? 0.7,
669
+ max_tokens: options.max_tokens ?? 1e3
670
+ })
671
+ });
672
+ const data = await response.json();
673
+ if (data.code === "PERMISSION_REQUIRED") {
674
+ throw new LLMPermissionError(data.permission || "llm.chat");
675
+ }
676
+ if (data.code === "LLM_ERROR") {
677
+ throw new LLMProviderError(data.error || "LLM request failed");
678
+ }
679
+ return data;
680
+ }
681
+ /**
682
+ * Send a streaming chat completion request (SSE)
683
+ *
684
+ * @example
685
+ * ```ts
686
+ * for await (const chunk of sdk.llm.chatStream([
687
+ * { role: 'user', content: 'Tell me a story' }
688
+ * ])) {
689
+ * process.stdout.write(chunk.textResponse || '');
690
+ * }
691
+ * ```
692
+ */
693
+ async *chatStream(messages, options = {}) {
694
+ const response = await fetch(`${this.baseUrl}/sdk/llm/chat/stream`, {
695
+ method: "POST",
696
+ headers: {
697
+ ...this.headers,
698
+ "Accept": "text/event-stream"
699
+ },
700
+ body: JSON.stringify({
701
+ messages,
702
+ model: options.model,
703
+ provider: options.provider,
704
+ temperature: options.temperature ?? 0.7,
705
+ max_tokens: options.max_tokens ?? 1e3
706
+ })
707
+ });
708
+ if (!response.ok) {
709
+ const errorData = await response.json();
710
+ if (errorData.code === "PERMISSION_REQUIRED") {
711
+ throw new LLMPermissionError(errorData.permission || "llm.chat");
712
+ }
713
+ throw new LLMProviderError(errorData.error || "Stream request failed");
714
+ }
715
+ const reader = response.body?.getReader();
716
+ if (!reader) {
717
+ throw new LLMProviderError("Response body is not readable");
718
+ }
719
+ const decoder = new TextDecoder();
720
+ let buffer = "";
721
+ let isErrorEvent = false;
722
+ try {
723
+ while (true) {
724
+ const { done, value } = await reader.read();
725
+ if (done) break;
726
+ buffer += decoder.decode(value, { stream: true });
727
+ const lines = buffer.split("\n");
728
+ buffer = lines.pop() || "";
729
+ for (const line of lines) {
730
+ const trimmedLine = line.trim();
731
+ if (!trimmedLine || trimmedLine.startsWith(":")) continue;
732
+ if (trimmedLine.startsWith("event: error")) {
733
+ isErrorEvent = true;
734
+ continue;
735
+ }
736
+ if (trimmedLine.startsWith("data: ")) {
737
+ const jsonStr = trimmedLine.slice(6);
738
+ if (jsonStr === "[DONE]") {
739
+ isErrorEvent = false;
740
+ continue;
741
+ }
742
+ try {
743
+ const data = JSON.parse(jsonStr);
744
+ if (isErrorEvent) {
745
+ isErrorEvent = false;
746
+ throw new LLMProviderError(
747
+ data.error || "Stream error",
748
+ data.code || "LLM_STREAM_ERROR"
749
+ );
750
+ }
751
+ const chunk = data;
752
+ if (chunk.error) {
753
+ throw new LLMProviderError(
754
+ chunk.message || "Stream error"
755
+ );
756
+ }
757
+ yield chunk;
758
+ } catch (parseError) {
759
+ isErrorEvent = false;
760
+ if (jsonStr !== "[DONE]") {
761
+ console.warn("[LLM Stream] Parse error:", jsonStr);
762
+ }
763
+ if (parseError instanceof LLMProviderError) throw parseError;
764
+ }
765
+ }
766
+ }
767
+ }
768
+ } finally {
769
+ reader.releaseLock();
770
+ }
771
+ }
772
+ /**
773
+ * Generate vector embeddings from text
774
+ *
775
+ * @example
776
+ * ```ts
777
+ * // Single text
778
+ * const { embeddings } = await sdk.llm.embed('Hello world');
779
+ *
780
+ * // Multiple texts
781
+ * const { embeddings } = await sdk.llm.embed(['Hello', 'World']);
782
+ * ```
783
+ */
784
+ async embed(input, options = {}) {
785
+ const inputArray = Array.isArray(input) ? input : [input];
786
+ const response = await fetch(`${this.baseUrl}/sdk/llm/embed`, {
787
+ method: "POST",
788
+ headers: this.headers,
789
+ body: JSON.stringify({
790
+ input: inputArray,
791
+ provider: options.provider,
792
+ model: options.model
793
+ })
794
+ });
795
+ const data = await response.json();
796
+ if (data.code === "PERMISSION_REQUIRED") {
797
+ throw new LLMPermissionError(data.permission || "llm.embed");
798
+ }
799
+ if (data.code === "PROVIDER_UNAVAILABLE") {
800
+ throw new LLMProviderError(data.error || "Embedding provider not available");
801
+ }
802
+ return data;
803
+ }
804
+ /**
805
+ * Helper: Embed text and store as vectors in one call
806
+ *
807
+ * @example
808
+ * ```ts
809
+ * await sdk.llm.embedAndStore({
810
+ * texts: ['Hello world', 'Goodbye world'],
811
+ * documentId: 'doc-123',
812
+ * workspaceId: 'ws-456'
813
+ * });
814
+ * ```
815
+ */
816
+ async embedAndStore(params) {
817
+ const { texts, documentId, workspaceId, idPrefix = "chunk", provider, model } = params;
818
+ const embedResult = await this.embed(texts, { provider, model });
819
+ if (!embedResult.success || !embedResult.embeddings) {
820
+ return {
821
+ success: false,
822
+ error: embedResult.error || "Embedding failed",
823
+ code: embedResult.code
824
+ };
825
+ }
826
+ let uniquePrefix = idPrefix;
827
+ if (idPrefix === "chunk") {
828
+ const randomSuffix = Math.random().toString(36).substring(2, 6);
829
+ uniquePrefix = `chunk_${randomSuffix}`;
830
+ }
831
+ const vectors = texts.map((text, i) => ({
832
+ id: `${uniquePrefix}_${i}`,
833
+ vector: embedResult.embeddings[i],
834
+ metadata: {
835
+ text,
836
+ documentId,
837
+ workspaceId,
838
+ embeddingModel: embedResult.model || model || "unknown"
839
+ }
840
+ }));
841
+ return this.vectors.upsert(vectors, { workspaceId });
842
+ }
843
+ /**
844
+ * Helper: Search similar documents by text query
845
+ *
846
+ * @example
847
+ * ```ts
848
+ * const results = await sdk.llm.search('What is RealtimeX?', {
849
+ * topK: 5,
850
+ * workspaceId: 'ws-123'
851
+ * });
852
+ *
853
+ * for (const result of results) {
854
+ * console.log(result.metadata?.text, result.score);
855
+ * }
856
+ * ```
857
+ */
858
+ async search(query, options = {}) {
859
+ const embedResult = await this.embed(query, {
860
+ provider: options.provider,
861
+ model: options.model
862
+ });
863
+ if (!embedResult.success || !embedResult.embeddings?.[0]) {
864
+ throw new LLMProviderError("Failed to embed query");
865
+ }
866
+ const queryResult = await this.vectors.query(embedResult.embeddings[0], {
867
+ ...options,
868
+ model: options.model || embedResult.model
869
+ });
870
+ if (!queryResult.success) {
871
+ throw new LLMProviderError(queryResult.error || "Vector search failed");
872
+ }
873
+ return queryResult.results || [];
874
+ }
875
+ };
876
+
432
877
  // src/index.ts
433
878
  var _RealtimeXSDK = class _RealtimeXSDK {
434
879
  constructor(config = {}) {
435
880
  const envAppId = this.getEnvVar("RTX_APP_ID");
436
881
  const envAppName = this.getEnvVar("RTX_APP_NAME");
882
+ const envApiKey = this.getEnvVar("RTX_API_KEY");
437
883
  this.appId = config.realtimex?.appId || envAppId || "";
438
884
  this.appName = config.realtimex?.appName || envAppName;
885
+ this.apiKey = config.realtimex?.apiKey || envApiKey;
439
886
  this.permissions = config.permissions || [];
440
887
  this.realtimexUrl = config.realtimex?.url || _RealtimeXSDK.DEFAULT_REALTIMEX_URL;
441
- this.activities = new ActivitiesModule(this.realtimexUrl, this.appId, this.appName);
442
- this.webhook = new WebhookModule(this.realtimexUrl, this.appName, this.appId);
443
- this.api = new ApiModule(this.realtimexUrl, this.appId, this.appName);
444
- this.task = new TaskModule(this.realtimexUrl, this.appName, this.appId);
888
+ this.activities = new ActivitiesModule(this.realtimexUrl, this.appId, this.appName, this.apiKey);
889
+ this.webhook = new WebhookModule(this.realtimexUrl, this.appName, this.appId, this.apiKey);
890
+ this.api = new ApiModule(this.realtimexUrl, this.appId, this.appName, this.apiKey);
891
+ this.task = new TaskModule(this.realtimexUrl, this.appName, this.appId, this.apiKey);
445
892
  this.port = new PortModule(config.defaultPort);
446
- if (this.permissions.length > 0) {
893
+ this.llm = new LLMModule(this.realtimexUrl, this.appId, this.apiKey);
894
+ if (this.permissions.length > 0 && this.appId && !this.apiKey) {
447
895
  this.register().catch((err) => {
448
896
  console.error("[RealtimeX SDK] Auto-registration failed:", err.message);
449
897
  });
@@ -487,16 +935,45 @@ var _RealtimeXSDK = class _RealtimeXSDK {
487
935
  }
488
936
  return void 0;
489
937
  }
938
+ /**
939
+ * Ping RealtimeX server to verify connection and authentication.
940
+ * Works in both development (API Key) and production (App ID) modes.
941
+ */
942
+ async ping() {
943
+ try {
944
+ const headers = { "Content-Type": "application/json" };
945
+ if (this.apiKey) {
946
+ headers["Authorization"] = `Bearer ${this.apiKey}`;
947
+ } else if (this.appId) {
948
+ headers["x-app-id"] = this.appId;
949
+ }
950
+ const response = await fetch(`${this.realtimexUrl.replace(/\/$/, "")}/sdk/ping`, {
951
+ method: "GET",
952
+ headers
953
+ });
954
+ const data = await response.json();
955
+ if (!response.ok) {
956
+ throw new Error(data.error || "Ping failed");
957
+ }
958
+ return data;
959
+ } catch (error) {
960
+ throw new Error(`Connection failed: ${error.message}`);
961
+ }
962
+ }
490
963
  };
491
964
  _RealtimeXSDK.DEFAULT_REALTIMEX_URL = "http://localhost:3001";
492
965
  var RealtimeXSDK = _RealtimeXSDK;
493
966
  export {
494
967
  ActivitiesModule,
495
968
  ApiModule,
969
+ LLMModule,
970
+ LLMPermissionError,
971
+ LLMProviderError,
496
972
  PermissionDeniedError,
497
973
  PermissionRequiredError,
498
974
  PortModule,
499
975
  RealtimeXSDK,
500
976
  TaskModule,
977
+ VectorStore,
501
978
  WebhookModule
502
979
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@realtimex/sdk",
3
- "version": "1.0.9",
3
+ "version": "1.1.2",
4
4
  "description": "SDK for building Local Apps that integrate with RealtimeX",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",