@realtimex/sdk 1.0.8 → 1.1.1
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/README.md +155 -2
- package/dist/index.d.mts +336 -7
- package/dist/index.d.ts +336 -7
- package/dist/index.js +619 -62
- package/dist/index.mjs +613 -62
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,128 @@
|
|
|
1
|
+
// src/modules/api.ts
|
|
2
|
+
var PermissionDeniedError = class extends Error {
|
|
3
|
+
constructor(permission, message) {
|
|
4
|
+
super(message || `Permission '${permission}' was denied`);
|
|
5
|
+
this.name = "PermissionDeniedError";
|
|
6
|
+
this.permission = permission;
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
var PermissionRequiredError = class extends Error {
|
|
10
|
+
constructor(permission, message) {
|
|
11
|
+
super(message || `Permission '${permission}' is required`);
|
|
12
|
+
this.name = "PermissionRequiredError";
|
|
13
|
+
this.permission = permission;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
var ApiModule = class {
|
|
17
|
+
constructor(realtimexUrl, appId, appName) {
|
|
18
|
+
this.realtimexUrl = realtimexUrl.replace(/\/$/, "");
|
|
19
|
+
this.appId = appId;
|
|
20
|
+
this.appName = appName || process.env.RTX_APP_NAME || "Local App";
|
|
21
|
+
}
|
|
22
|
+
getHeaders() {
|
|
23
|
+
return {
|
|
24
|
+
"Content-Type": "application/json",
|
|
25
|
+
"x-app-id": this.appId
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Request a single permission from Electron via internal API
|
|
30
|
+
*/
|
|
31
|
+
async requestPermission(permission) {
|
|
32
|
+
try {
|
|
33
|
+
const response = await fetch(`${this.realtimexUrl}/api/local-apps/request-permission`, {
|
|
34
|
+
method: "POST",
|
|
35
|
+
headers: { "Content-Type": "application/json" },
|
|
36
|
+
body: JSON.stringify({
|
|
37
|
+
app_id: this.appId,
|
|
38
|
+
app_name: this.appName,
|
|
39
|
+
permission
|
|
40
|
+
})
|
|
41
|
+
});
|
|
42
|
+
const data = await response.json();
|
|
43
|
+
return data.granted === true;
|
|
44
|
+
} catch (error) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Make an API call with automatic permission handling
|
|
50
|
+
*/
|
|
51
|
+
async apiCall(method, endpoint, options) {
|
|
52
|
+
const url = `${this.realtimexUrl}${endpoint}`;
|
|
53
|
+
const response = await fetch(url, {
|
|
54
|
+
method,
|
|
55
|
+
headers: this.getHeaders(),
|
|
56
|
+
...options
|
|
57
|
+
});
|
|
58
|
+
const data = await response.json();
|
|
59
|
+
if (response.status === 403) {
|
|
60
|
+
const errorCode = data.error;
|
|
61
|
+
const permission = data.permission;
|
|
62
|
+
const message = data.message;
|
|
63
|
+
if (errorCode === "PERMISSION_REQUIRED" && permission) {
|
|
64
|
+
const granted = await this.requestPermission(permission);
|
|
65
|
+
if (granted) {
|
|
66
|
+
return this.apiCall(method, endpoint, options);
|
|
67
|
+
} else {
|
|
68
|
+
throw new PermissionDeniedError(permission, message);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (errorCode === "PERMISSION_DENIED") {
|
|
72
|
+
throw new PermissionDeniedError(permission, message);
|
|
73
|
+
}
|
|
74
|
+
throw new Error(data.error || "Permission denied");
|
|
75
|
+
}
|
|
76
|
+
if (!response.ok) {
|
|
77
|
+
throw new Error(data.error || `API call failed: ${response.status}`);
|
|
78
|
+
}
|
|
79
|
+
return data;
|
|
80
|
+
}
|
|
81
|
+
async getAgents() {
|
|
82
|
+
const data = await this.apiCall("GET", "/agents");
|
|
83
|
+
return data.agents;
|
|
84
|
+
}
|
|
85
|
+
async getWorkspaces() {
|
|
86
|
+
const data = await this.apiCall("GET", "/workspaces");
|
|
87
|
+
return data.workspaces;
|
|
88
|
+
}
|
|
89
|
+
async getThreads(workspaceSlug) {
|
|
90
|
+
const data = await this.apiCall("GET", `/workspaces/${encodeURIComponent(workspaceSlug)}/threads`);
|
|
91
|
+
return data.threads;
|
|
92
|
+
}
|
|
93
|
+
async getTask(taskUuid) {
|
|
94
|
+
const data = await this.apiCall("GET", `/task/${encodeURIComponent(taskUuid)}`);
|
|
95
|
+
return { ...data.task, runs: data.runs };
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
1
99
|
// src/modules/activities.ts
|
|
2
100
|
var ActivitiesModule = class {
|
|
3
|
-
constructor(realtimexUrl, appId) {
|
|
101
|
+
constructor(realtimexUrl, appId, appName) {
|
|
4
102
|
this.baseUrl = realtimexUrl.replace(/\/$/, "");
|
|
5
103
|
this.appId = appId;
|
|
104
|
+
this.appName = appName || process.env.RTX_APP_NAME || "Local App";
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Request a single permission from Electron via internal API
|
|
108
|
+
*/
|
|
109
|
+
async requestPermission(permission) {
|
|
110
|
+
try {
|
|
111
|
+
const response = await fetch(`${this.baseUrl}/api/local-apps/request-permission`, {
|
|
112
|
+
method: "POST",
|
|
113
|
+
headers: { "Content-Type": "application/json" },
|
|
114
|
+
body: JSON.stringify({
|
|
115
|
+
app_id: this.appId,
|
|
116
|
+
app_name: this.appName,
|
|
117
|
+
permission
|
|
118
|
+
})
|
|
119
|
+
});
|
|
120
|
+
const data = await response.json();
|
|
121
|
+
return data.granted === true;
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.error("[SDK] Permission request failed:", error);
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
6
126
|
}
|
|
7
127
|
async request(path, options = {}) {
|
|
8
128
|
const url = `${this.baseUrl}${path}`;
|
|
@@ -20,6 +140,22 @@ var ActivitiesModule = class {
|
|
|
20
140
|
}
|
|
21
141
|
});
|
|
22
142
|
const data = await response.json();
|
|
143
|
+
if (response.status === 403) {
|
|
144
|
+
const errorCode = data.error;
|
|
145
|
+
const permission = data.permission;
|
|
146
|
+
const message = data.message;
|
|
147
|
+
if (errorCode === "PERMISSION_REQUIRED" && permission) {
|
|
148
|
+
const granted = await this.requestPermission(permission);
|
|
149
|
+
if (granted) {
|
|
150
|
+
return this.request(path, options);
|
|
151
|
+
} else {
|
|
152
|
+
throw new PermissionDeniedError(permission, message);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (errorCode === "PERMISSION_DENIED") {
|
|
156
|
+
throw new PermissionDeniedError(permission, message);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
23
159
|
if (!response.ok) {
|
|
24
160
|
throw new Error(data.error || `Request failed: ${response.status}`);
|
|
25
161
|
}
|
|
@@ -86,13 +222,64 @@ var WebhookModule = class {
|
|
|
86
222
|
this.appName = appName;
|
|
87
223
|
this.appId = appId;
|
|
88
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* Request a single permission from Electron via internal API
|
|
227
|
+
*/
|
|
228
|
+
async requestPermission(permission) {
|
|
229
|
+
try {
|
|
230
|
+
const response = await fetch(`${this.realtimexUrl}/api/local-apps/request-permission`, {
|
|
231
|
+
method: "POST",
|
|
232
|
+
headers: { "Content-Type": "application/json" },
|
|
233
|
+
body: JSON.stringify({
|
|
234
|
+
app_id: this.appId,
|
|
235
|
+
app_name: this.appName,
|
|
236
|
+
permission
|
|
237
|
+
})
|
|
238
|
+
});
|
|
239
|
+
const data = await response.json();
|
|
240
|
+
return data.granted === true;
|
|
241
|
+
} catch (error) {
|
|
242
|
+
console.error("[SDK] Permission request failed:", error);
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
async request(path, options = {}) {
|
|
247
|
+
const url = `${this.realtimexUrl}${path}`;
|
|
248
|
+
const response = await fetch(url, {
|
|
249
|
+
...options,
|
|
250
|
+
headers: {
|
|
251
|
+
"Content-Type": "application/json",
|
|
252
|
+
...options.headers
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
const data = await response.json();
|
|
256
|
+
if (response.status === 403) {
|
|
257
|
+
const errorCode = data.error;
|
|
258
|
+
const permission = data.permission;
|
|
259
|
+
const message = data.message;
|
|
260
|
+
if (errorCode === "PERMISSION_REQUIRED" && permission) {
|
|
261
|
+
const granted = await this.requestPermission(permission);
|
|
262
|
+
if (granted) {
|
|
263
|
+
return this.request(path, options);
|
|
264
|
+
} else {
|
|
265
|
+
throw new PermissionDeniedError(permission, message);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (errorCode === "PERMISSION_DENIED") {
|
|
269
|
+
throw new PermissionDeniedError(permission, message);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (!response.ok) {
|
|
273
|
+
throw new Error(data.error || `Request failed: ${response.status}`);
|
|
274
|
+
}
|
|
275
|
+
return data;
|
|
276
|
+
}
|
|
89
277
|
async triggerAgent(payload) {
|
|
90
278
|
if (payload.auto_run && (!payload.agent_name || !payload.workspace_slug)) {
|
|
91
279
|
throw new Error("auto_run requires agent_name and workspace_slug");
|
|
92
280
|
}
|
|
93
|
-
|
|
281
|
+
return this.request("/webhooks/realtimex", {
|
|
94
282
|
method: "POST",
|
|
95
|
-
headers: { "Content-Type": "application/json" },
|
|
96
283
|
body: JSON.stringify({
|
|
97
284
|
app_name: this.appName,
|
|
98
285
|
app_id: this.appId,
|
|
@@ -107,69 +294,16 @@ var WebhookModule = class {
|
|
|
107
294
|
}
|
|
108
295
|
})
|
|
109
296
|
});
|
|
110
|
-
const data = await response.json();
|
|
111
|
-
if (!response.ok) throw new Error(data.error || "Failed to trigger agent");
|
|
112
|
-
return data;
|
|
113
297
|
}
|
|
114
298
|
async ping() {
|
|
115
|
-
|
|
299
|
+
return this.request("/webhooks/realtimex", {
|
|
116
300
|
method: "POST",
|
|
117
|
-
headers: { "Content-Type": "application/json" },
|
|
118
301
|
body: JSON.stringify({
|
|
119
302
|
app_name: this.appName,
|
|
120
303
|
app_id: this.appId,
|
|
121
304
|
event: "ping"
|
|
122
305
|
})
|
|
123
306
|
});
|
|
124
|
-
const data = await response.json();
|
|
125
|
-
if (!response.ok) throw new Error(data.error || "Ping failed");
|
|
126
|
-
return data;
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
// src/modules/api.ts
|
|
131
|
-
var ApiModule = class {
|
|
132
|
-
constructor(realtimexUrl, appId) {
|
|
133
|
-
this.realtimexUrl = realtimexUrl.replace(/\/$/, "");
|
|
134
|
-
this.appId = appId;
|
|
135
|
-
}
|
|
136
|
-
getHeaders() {
|
|
137
|
-
return {
|
|
138
|
-
"Content-Type": "application/json",
|
|
139
|
-
"x-app-id": this.appId
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
async getAgents() {
|
|
143
|
-
const response = await fetch(`${this.realtimexUrl}/agents`, {
|
|
144
|
-
headers: this.getHeaders()
|
|
145
|
-
});
|
|
146
|
-
const data = await response.json();
|
|
147
|
-
if (!response.ok) throw new Error(data.error || "Failed to get agents");
|
|
148
|
-
return data.agents;
|
|
149
|
-
}
|
|
150
|
-
async getWorkspaces() {
|
|
151
|
-
const response = await fetch(`${this.realtimexUrl}/workspaces`, {
|
|
152
|
-
headers: this.getHeaders()
|
|
153
|
-
});
|
|
154
|
-
const data = await response.json();
|
|
155
|
-
if (!response.ok) throw new Error(data.error || "Failed to get workspaces");
|
|
156
|
-
return data.workspaces;
|
|
157
|
-
}
|
|
158
|
-
async getThreads(workspaceSlug) {
|
|
159
|
-
const response = await fetch(`${this.realtimexUrl}/workspaces/${encodeURIComponent(workspaceSlug)}/threads`, {
|
|
160
|
-
headers: this.getHeaders()
|
|
161
|
-
});
|
|
162
|
-
const data = await response.json();
|
|
163
|
-
if (!response.ok) throw new Error(data.error || "Failed to get threads");
|
|
164
|
-
return data.threads;
|
|
165
|
-
}
|
|
166
|
-
async getTask(taskUuid) {
|
|
167
|
-
const response = await fetch(`${this.realtimexUrl}/task/${encodeURIComponent(taskUuid)}`, {
|
|
168
|
-
headers: this.getHeaders()
|
|
169
|
-
});
|
|
170
|
-
const data = await response.json();
|
|
171
|
-
if (!response.ok) throw new Error(data.error || "Failed to get task");
|
|
172
|
-
return { ...data.task, runs: data.runs };
|
|
173
307
|
}
|
|
174
308
|
};
|
|
175
309
|
|
|
@@ -295,6 +429,384 @@ var PortModule = class {
|
|
|
295
429
|
}
|
|
296
430
|
};
|
|
297
431
|
|
|
432
|
+
// src/modules/llm.ts
|
|
433
|
+
var LLMPermissionError = class extends Error {
|
|
434
|
+
constructor(permission, code = "PERMISSION_REQUIRED") {
|
|
435
|
+
super(`Permission required: ${permission}`);
|
|
436
|
+
this.permission = permission;
|
|
437
|
+
this.code = code;
|
|
438
|
+
this.name = "LLMPermissionError";
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
var LLMProviderError = class extends Error {
|
|
442
|
+
constructor(message, code = "LLM_ERROR") {
|
|
443
|
+
super(message);
|
|
444
|
+
this.code = code;
|
|
445
|
+
this.name = "LLMProviderError";
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
var VectorStore = class {
|
|
449
|
+
constructor(baseUrl, appId) {
|
|
450
|
+
this.baseUrl = baseUrl;
|
|
451
|
+
this.appId = appId;
|
|
452
|
+
}
|
|
453
|
+
get headers() {
|
|
454
|
+
return {
|
|
455
|
+
"Content-Type": "application/json",
|
|
456
|
+
"x-app-id": this.appId
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Upsert (insert or update) vectors into storage
|
|
461
|
+
*
|
|
462
|
+
* @example
|
|
463
|
+
* ```ts
|
|
464
|
+
* await sdk.llm.vectors.upsert([
|
|
465
|
+
* { id: 'chunk-1', vector: embeddings[0], metadata: { text: 'Hello', documentId: 'doc-1' } }
|
|
466
|
+
* ], { workspaceId: 'ws-123' });
|
|
467
|
+
* ```
|
|
468
|
+
*/
|
|
469
|
+
async upsert(vectors, options = {}) {
|
|
470
|
+
const response = await fetch(`${this.baseUrl}/sdk/llm/vectors/upsert`, {
|
|
471
|
+
method: "POST",
|
|
472
|
+
headers: this.headers,
|
|
473
|
+
body: JSON.stringify({
|
|
474
|
+
vectors,
|
|
475
|
+
workspaceId: options.workspaceId
|
|
476
|
+
})
|
|
477
|
+
});
|
|
478
|
+
const data = await response.json();
|
|
479
|
+
if (data.code === "PERMISSION_REQUIRED") {
|
|
480
|
+
throw new LLMPermissionError(data.permission || "vectors.write");
|
|
481
|
+
}
|
|
482
|
+
return data;
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Query similar vectors by embedding
|
|
486
|
+
*
|
|
487
|
+
* @example
|
|
488
|
+
* ```ts
|
|
489
|
+
* const results = await sdk.llm.vectors.query(queryVector, {
|
|
490
|
+
* topK: 5,
|
|
491
|
+
* filter: { documentId: 'doc-1' },
|
|
492
|
+
* workspaceId: 'ws-123'
|
|
493
|
+
* });
|
|
494
|
+
* ```
|
|
495
|
+
*/
|
|
496
|
+
async query(vector, options = {}) {
|
|
497
|
+
const response = await fetch(`${this.baseUrl}/sdk/llm/vectors/query`, {
|
|
498
|
+
method: "POST",
|
|
499
|
+
headers: this.headers,
|
|
500
|
+
body: JSON.stringify({
|
|
501
|
+
vector,
|
|
502
|
+
topK: options.topK ?? 5,
|
|
503
|
+
filter: options.filter,
|
|
504
|
+
workspaceId: options.workspaceId
|
|
505
|
+
})
|
|
506
|
+
});
|
|
507
|
+
const data = await response.json();
|
|
508
|
+
if (data.code === "PERMISSION_REQUIRED") {
|
|
509
|
+
throw new LLMPermissionError(data.permission || "vectors.read");
|
|
510
|
+
}
|
|
511
|
+
return data;
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Delete vectors from storage
|
|
515
|
+
*
|
|
516
|
+
* Note: Currently only supports deleteAll: true
|
|
517
|
+
* Use workspaceId to scope deletion to a specific workspace
|
|
518
|
+
*
|
|
519
|
+
* @example
|
|
520
|
+
* ```ts
|
|
521
|
+
* await sdk.llm.vectors.delete({ deleteAll: true, workspaceId: 'ws-123' });
|
|
522
|
+
* ```
|
|
523
|
+
*/
|
|
524
|
+
async delete(options) {
|
|
525
|
+
const response = await fetch(`${this.baseUrl}/sdk/llm/vectors/delete`, {
|
|
526
|
+
method: "POST",
|
|
527
|
+
headers: this.headers,
|
|
528
|
+
body: JSON.stringify(options)
|
|
529
|
+
});
|
|
530
|
+
const data = await response.json();
|
|
531
|
+
if (data.code === "PERMISSION_REQUIRED") {
|
|
532
|
+
throw new LLMPermissionError(data.permission || "vectors.write");
|
|
533
|
+
}
|
|
534
|
+
return data;
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* List all available workspaces (namespaces) for this app
|
|
538
|
+
*
|
|
539
|
+
* @example
|
|
540
|
+
* ```ts
|
|
541
|
+
* const { workspaces } = await sdk.llm.vectors.listWorkspaces();
|
|
542
|
+
* console.log('Workspaces:', workspaces);
|
|
543
|
+
* ```
|
|
544
|
+
*/
|
|
545
|
+
async listWorkspaces() {
|
|
546
|
+
const response = await fetch(`${this.baseUrl}/sdk/llm/vectors/workspaces`, {
|
|
547
|
+
method: "GET",
|
|
548
|
+
headers: this.headers
|
|
549
|
+
});
|
|
550
|
+
const data = await response.json();
|
|
551
|
+
if (data.code === "PERMISSION_REQUIRED") {
|
|
552
|
+
throw new LLMPermissionError(data.permission || "vectors.read");
|
|
553
|
+
}
|
|
554
|
+
return data;
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
var LLMModule = class {
|
|
558
|
+
constructor(baseUrl, appId) {
|
|
559
|
+
this.baseUrl = baseUrl;
|
|
560
|
+
this.appId = appId;
|
|
561
|
+
this.vectors = new VectorStore(baseUrl, appId);
|
|
562
|
+
}
|
|
563
|
+
get headers() {
|
|
564
|
+
return {
|
|
565
|
+
"Content-Type": "application/json",
|
|
566
|
+
"x-app-id": this.appId
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Get available LLM and embedding providers/models
|
|
571
|
+
*
|
|
572
|
+
* @example
|
|
573
|
+
* ```ts
|
|
574
|
+
* const { llm, embedding } = await sdk.llm.getProviders();
|
|
575
|
+
* console.log('Available LLM models:', llm[0].models);
|
|
576
|
+
* ```
|
|
577
|
+
*/
|
|
578
|
+
async getProviders() {
|
|
579
|
+
const response = await fetch(`${this.baseUrl}/sdk/llm/providers`, {
|
|
580
|
+
method: "GET",
|
|
581
|
+
headers: this.headers
|
|
582
|
+
});
|
|
583
|
+
const data = await response.json();
|
|
584
|
+
if (data.code === "PERMISSION_REQUIRED") {
|
|
585
|
+
throw new LLMPermissionError(data.permission || "llm.providers");
|
|
586
|
+
}
|
|
587
|
+
return data;
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Send a chat completion request (synchronous)
|
|
591
|
+
*
|
|
592
|
+
* @example
|
|
593
|
+
* ```ts
|
|
594
|
+
* const response = await sdk.llm.chat([
|
|
595
|
+
* { role: 'system', content: 'You are a helpful assistant.' },
|
|
596
|
+
* { role: 'user', content: 'Hello!' }
|
|
597
|
+
* ], { model: 'gpt-4o', temperature: 0.7 });
|
|
598
|
+
*
|
|
599
|
+
* console.log(response.response?.content);
|
|
600
|
+
* ```
|
|
601
|
+
*/
|
|
602
|
+
async chat(messages, options = {}) {
|
|
603
|
+
const response = await fetch(`${this.baseUrl}/sdk/llm/chat`, {
|
|
604
|
+
method: "POST",
|
|
605
|
+
headers: this.headers,
|
|
606
|
+
body: JSON.stringify({
|
|
607
|
+
messages,
|
|
608
|
+
model: options.model,
|
|
609
|
+
provider: options.provider,
|
|
610
|
+
temperature: options.temperature ?? 0.7,
|
|
611
|
+
max_tokens: options.max_tokens ?? 1e3
|
|
612
|
+
})
|
|
613
|
+
});
|
|
614
|
+
const data = await response.json();
|
|
615
|
+
if (data.code === "PERMISSION_REQUIRED") {
|
|
616
|
+
throw new LLMPermissionError(data.permission || "llm.chat");
|
|
617
|
+
}
|
|
618
|
+
if (data.code === "LLM_ERROR") {
|
|
619
|
+
throw new LLMProviderError(data.error || "LLM request failed");
|
|
620
|
+
}
|
|
621
|
+
return data;
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Send a streaming chat completion request (SSE)
|
|
625
|
+
*
|
|
626
|
+
* @example
|
|
627
|
+
* ```ts
|
|
628
|
+
* for await (const chunk of sdk.llm.chatStream([
|
|
629
|
+
* { role: 'user', content: 'Tell me a story' }
|
|
630
|
+
* ])) {
|
|
631
|
+
* process.stdout.write(chunk.textResponse || '');
|
|
632
|
+
* }
|
|
633
|
+
* ```
|
|
634
|
+
*/
|
|
635
|
+
async *chatStream(messages, options = {}) {
|
|
636
|
+
const response = await fetch(`${this.baseUrl}/sdk/llm/chat/stream`, {
|
|
637
|
+
method: "POST",
|
|
638
|
+
headers: {
|
|
639
|
+
...this.headers,
|
|
640
|
+
"Accept": "text/event-stream"
|
|
641
|
+
},
|
|
642
|
+
body: JSON.stringify({
|
|
643
|
+
messages,
|
|
644
|
+
model: options.model,
|
|
645
|
+
provider: options.provider,
|
|
646
|
+
temperature: options.temperature ?? 0.7,
|
|
647
|
+
max_tokens: options.max_tokens ?? 1e3
|
|
648
|
+
})
|
|
649
|
+
});
|
|
650
|
+
if (!response.ok) {
|
|
651
|
+
const errorData = await response.json();
|
|
652
|
+
if (errorData.code === "PERMISSION_REQUIRED") {
|
|
653
|
+
throw new LLMPermissionError(errorData.permission || "llm.chat");
|
|
654
|
+
}
|
|
655
|
+
throw new LLMProviderError(errorData.error || "Stream request failed");
|
|
656
|
+
}
|
|
657
|
+
const reader = response.body?.getReader();
|
|
658
|
+
if (!reader) {
|
|
659
|
+
throw new LLMProviderError("Response body is not readable");
|
|
660
|
+
}
|
|
661
|
+
const decoder = new TextDecoder();
|
|
662
|
+
let buffer = "";
|
|
663
|
+
let isErrorEvent = false;
|
|
664
|
+
try {
|
|
665
|
+
while (true) {
|
|
666
|
+
const { done, value } = await reader.read();
|
|
667
|
+
if (done) break;
|
|
668
|
+
buffer += decoder.decode(value, { stream: true });
|
|
669
|
+
const lines = buffer.split("\n");
|
|
670
|
+
buffer = lines.pop() || "";
|
|
671
|
+
for (const line of lines) {
|
|
672
|
+
const trimmedLine = line.trim();
|
|
673
|
+
if (!trimmedLine || trimmedLine.startsWith(":")) continue;
|
|
674
|
+
if (trimmedLine.startsWith("event: error")) {
|
|
675
|
+
isErrorEvent = true;
|
|
676
|
+
continue;
|
|
677
|
+
}
|
|
678
|
+
if (trimmedLine.startsWith("data: ")) {
|
|
679
|
+
const jsonStr = trimmedLine.slice(6);
|
|
680
|
+
if (jsonStr === "[DONE]") {
|
|
681
|
+
isErrorEvent = false;
|
|
682
|
+
continue;
|
|
683
|
+
}
|
|
684
|
+
try {
|
|
685
|
+
const data = JSON.parse(jsonStr);
|
|
686
|
+
if (isErrorEvent) {
|
|
687
|
+
isErrorEvent = false;
|
|
688
|
+
throw new LLMProviderError(
|
|
689
|
+
data.error || "Stream error",
|
|
690
|
+
data.code || "LLM_STREAM_ERROR"
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
const chunk = data;
|
|
694
|
+
if (chunk.error) {
|
|
695
|
+
throw new LLMProviderError(
|
|
696
|
+
chunk.message || "Stream error"
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
yield chunk;
|
|
700
|
+
} catch (parseError) {
|
|
701
|
+
isErrorEvent = false;
|
|
702
|
+
if (jsonStr !== "[DONE]") {
|
|
703
|
+
console.warn("[LLM Stream] Parse error:", jsonStr);
|
|
704
|
+
}
|
|
705
|
+
if (parseError instanceof LLMProviderError) throw parseError;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
} finally {
|
|
711
|
+
reader.releaseLock();
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Generate vector embeddings from text
|
|
716
|
+
*
|
|
717
|
+
* @example
|
|
718
|
+
* ```ts
|
|
719
|
+
* // Single text
|
|
720
|
+
* const { embeddings } = await sdk.llm.embed('Hello world');
|
|
721
|
+
*
|
|
722
|
+
* // Multiple texts
|
|
723
|
+
* const { embeddings } = await sdk.llm.embed(['Hello', 'World']);
|
|
724
|
+
* ```
|
|
725
|
+
*/
|
|
726
|
+
async embed(input, options = {}) {
|
|
727
|
+
const inputArray = Array.isArray(input) ? input : [input];
|
|
728
|
+
const response = await fetch(`${this.baseUrl}/sdk/llm/embed`, {
|
|
729
|
+
method: "POST",
|
|
730
|
+
headers: this.headers,
|
|
731
|
+
body: JSON.stringify({
|
|
732
|
+
input: inputArray,
|
|
733
|
+
provider: options.provider,
|
|
734
|
+
model: options.model
|
|
735
|
+
})
|
|
736
|
+
});
|
|
737
|
+
const data = await response.json();
|
|
738
|
+
if (data.code === "PERMISSION_REQUIRED") {
|
|
739
|
+
throw new LLMPermissionError(data.permission || "llm.embed");
|
|
740
|
+
}
|
|
741
|
+
if (data.code === "PROVIDER_UNAVAILABLE") {
|
|
742
|
+
throw new LLMProviderError(data.error || "Embedding provider not available");
|
|
743
|
+
}
|
|
744
|
+
return data;
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* Helper: Embed text and store as vectors in one call
|
|
748
|
+
*
|
|
749
|
+
* @example
|
|
750
|
+
* ```ts
|
|
751
|
+
* await sdk.llm.embedAndStore({
|
|
752
|
+
* texts: ['Hello world', 'Goodbye world'],
|
|
753
|
+
* documentId: 'doc-123',
|
|
754
|
+
* workspaceId: 'ws-456'
|
|
755
|
+
* });
|
|
756
|
+
* ```
|
|
757
|
+
*/
|
|
758
|
+
async embedAndStore(params) {
|
|
759
|
+
const { texts, documentId, workspaceId, idPrefix = "chunk", provider, model } = params;
|
|
760
|
+
const embedResult = await this.embed(texts, { provider, model });
|
|
761
|
+
if (!embedResult.success || !embedResult.embeddings) {
|
|
762
|
+
return {
|
|
763
|
+
success: false,
|
|
764
|
+
error: embedResult.error || "Embedding failed",
|
|
765
|
+
code: embedResult.code
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
const vectors = texts.map((text, i) => ({
|
|
769
|
+
id: `${idPrefix}_${i}`,
|
|
770
|
+
vector: embedResult.embeddings[i],
|
|
771
|
+
metadata: {
|
|
772
|
+
text,
|
|
773
|
+
documentId,
|
|
774
|
+
workspaceId
|
|
775
|
+
}
|
|
776
|
+
}));
|
|
777
|
+
return this.vectors.upsert(vectors, { workspaceId });
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* Helper: Search similar documents by text query
|
|
781
|
+
*
|
|
782
|
+
* @example
|
|
783
|
+
* ```ts
|
|
784
|
+
* const results = await sdk.llm.search('What is RealtimeX?', {
|
|
785
|
+
* topK: 5,
|
|
786
|
+
* workspaceId: 'ws-123'
|
|
787
|
+
* });
|
|
788
|
+
*
|
|
789
|
+
* for (const result of results) {
|
|
790
|
+
* console.log(result.metadata?.text, result.score);
|
|
791
|
+
* }
|
|
792
|
+
* ```
|
|
793
|
+
*/
|
|
794
|
+
async search(query, options = {}) {
|
|
795
|
+
const embedResult = await this.embed(query, {
|
|
796
|
+
provider: options.provider,
|
|
797
|
+
model: options.model
|
|
798
|
+
});
|
|
799
|
+
if (!embedResult.success || !embedResult.embeddings?.[0]) {
|
|
800
|
+
throw new LLMProviderError("Failed to embed query");
|
|
801
|
+
}
|
|
802
|
+
const queryResult = await this.vectors.query(embedResult.embeddings[0], options);
|
|
803
|
+
if (!queryResult.success) {
|
|
804
|
+
throw new LLMProviderError(queryResult.error || "Vector search failed");
|
|
805
|
+
}
|
|
806
|
+
return queryResult.results || [];
|
|
807
|
+
}
|
|
808
|
+
};
|
|
809
|
+
|
|
298
810
|
// src/index.ts
|
|
299
811
|
var _RealtimeXSDK = class _RealtimeXSDK {
|
|
300
812
|
constructor(config = {}) {
|
|
@@ -302,12 +814,45 @@ var _RealtimeXSDK = class _RealtimeXSDK {
|
|
|
302
814
|
const envAppName = this.getEnvVar("RTX_APP_NAME");
|
|
303
815
|
this.appId = config.realtimex?.appId || envAppId || "";
|
|
304
816
|
this.appName = config.realtimex?.appName || envAppName;
|
|
305
|
-
|
|
306
|
-
this.
|
|
307
|
-
this.
|
|
308
|
-
this.
|
|
309
|
-
this.
|
|
817
|
+
this.permissions = config.permissions || [];
|
|
818
|
+
this.realtimexUrl = config.realtimex?.url || _RealtimeXSDK.DEFAULT_REALTIMEX_URL;
|
|
819
|
+
this.activities = new ActivitiesModule(this.realtimexUrl, this.appId, this.appName);
|
|
820
|
+
this.webhook = new WebhookModule(this.realtimexUrl, this.appName, this.appId);
|
|
821
|
+
this.api = new ApiModule(this.realtimexUrl, this.appId, this.appName);
|
|
822
|
+
this.task = new TaskModule(this.realtimexUrl, this.appName, this.appId);
|
|
310
823
|
this.port = new PortModule(config.defaultPort);
|
|
824
|
+
this.llm = new LLMModule(this.realtimexUrl, this.appId);
|
|
825
|
+
if (this.permissions.length > 0) {
|
|
826
|
+
this.register().catch((err) => {
|
|
827
|
+
console.error("[RealtimeX SDK] Auto-registration failed:", err.message);
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* Register app with RealtimeX hub and request declared permissions upfront.
|
|
833
|
+
* This is called automatically if permissions are provided in constructor.
|
|
834
|
+
*/
|
|
835
|
+
async register(permissions) {
|
|
836
|
+
const perms = permissions || this.permissions;
|
|
837
|
+
if (perms.length === 0) return;
|
|
838
|
+
try {
|
|
839
|
+
const response = await fetch(`${this.realtimexUrl.replace(/\/$/, "")}/sdk/register`, {
|
|
840
|
+
method: "POST",
|
|
841
|
+
headers: { "Content-Type": "application/json" },
|
|
842
|
+
body: JSON.stringify({
|
|
843
|
+
app_id: this.appId,
|
|
844
|
+
app_name: this.appName,
|
|
845
|
+
permissions: perms
|
|
846
|
+
})
|
|
847
|
+
});
|
|
848
|
+
const data = await response.json();
|
|
849
|
+
if (!response.ok) {
|
|
850
|
+
throw new Error(data.error || "Registration failed");
|
|
851
|
+
}
|
|
852
|
+
console.log(`[RealtimeX SDK] App registered successfully (${data.message})`);
|
|
853
|
+
} catch (error) {
|
|
854
|
+
throw new Error(`Failed to register app: ${error.message}`);
|
|
855
|
+
}
|
|
311
856
|
}
|
|
312
857
|
/**
|
|
313
858
|
* Get environment variable (works in Node.js and browser)
|
|
@@ -327,8 +872,14 @@ var RealtimeXSDK = _RealtimeXSDK;
|
|
|
327
872
|
export {
|
|
328
873
|
ActivitiesModule,
|
|
329
874
|
ApiModule,
|
|
875
|
+
LLMModule,
|
|
876
|
+
LLMPermissionError,
|
|
877
|
+
LLMProviderError,
|
|
878
|
+
PermissionDeniedError,
|
|
879
|
+
PermissionRequiredError,
|
|
330
880
|
PortModule,
|
|
331
881
|
RealtimeXSDK,
|
|
332
882
|
TaskModule,
|
|
883
|
+
VectorStore,
|
|
333
884
|
WebhookModule
|
|
334
885
|
};
|