openrag-sdk 0.1.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.mjs ADDED
@@ -0,0 +1,696 @@
1
+ // src/chat.ts
2
+ var ChatStream = class {
3
+ constructor(client, options) {
4
+ this.client = client;
5
+ this.options = options;
6
+ }
7
+ _text = "";
8
+ _chatId = null;
9
+ _sources = [];
10
+ _consumed = false;
11
+ _reader = null;
12
+ _response = null;
13
+ /** The accumulated text from content events. */
14
+ get text() {
15
+ return this._text;
16
+ }
17
+ /** The chat ID for continuing the conversation. */
18
+ get chatId() {
19
+ return this._chatId;
20
+ }
21
+ /** The sources retrieved during the conversation. */
22
+ get sources() {
23
+ return this._sources;
24
+ }
25
+ /** @internal Initialize the stream. */
26
+ async _init() {
27
+ const body = {
28
+ message: this.options.message,
29
+ stream: true,
30
+ limit: this.options.limit ?? 10,
31
+ score_threshold: this.options.scoreThreshold ?? 0
32
+ };
33
+ if (this.options.chatId) {
34
+ body["chat_id"] = this.options.chatId;
35
+ }
36
+ if (this.options.filters) {
37
+ body["filters"] = this.options.filters;
38
+ }
39
+ if (this.options.filterId) {
40
+ body["filter_id"] = this.options.filterId;
41
+ }
42
+ this._response = await this.client._request("POST", "/api/v1/chat", {
43
+ body: JSON.stringify(body),
44
+ stream: true
45
+ });
46
+ if (!this._response.body) {
47
+ throw new Error("Response body is null");
48
+ }
49
+ this._reader = this._response.body.getReader();
50
+ }
51
+ async *[Symbol.asyncIterator]() {
52
+ if (this._consumed) {
53
+ throw new Error("Stream has already been consumed");
54
+ }
55
+ this._consumed = true;
56
+ if (!this._reader) {
57
+ throw new Error("Stream not initialized");
58
+ }
59
+ const decoder = new TextDecoder();
60
+ let buffer = "";
61
+ while (true) {
62
+ const { done, value } = await this._reader.read();
63
+ if (done) break;
64
+ buffer += decoder.decode(value, { stream: true });
65
+ const lines = buffer.split("\n");
66
+ buffer = lines.pop() || "";
67
+ for (const line of lines) {
68
+ const trimmed = line.trim();
69
+ if (!trimmed || !trimmed.startsWith("data:")) continue;
70
+ const dataStr = trimmed.slice(5).trim();
71
+ if (!dataStr) continue;
72
+ try {
73
+ const data = JSON.parse(dataStr);
74
+ const eventType = data.type;
75
+ if (eventType === "content") {
76
+ const delta = data.delta || "";
77
+ this._text += delta;
78
+ yield { type: "content", delta };
79
+ } else if (eventType === "sources") {
80
+ this._sources = data.sources || [];
81
+ yield { type: "sources", sources: this._sources };
82
+ } else if (eventType === "done") {
83
+ this._chatId = data.chat_id || null;
84
+ yield { type: "done", chatId: this._chatId };
85
+ }
86
+ } catch {
87
+ }
88
+ }
89
+ }
90
+ }
91
+ /**
92
+ * Iterate over just the text deltas.
93
+ */
94
+ get textStream() {
95
+ const self = this;
96
+ return {
97
+ async *[Symbol.asyncIterator]() {
98
+ for await (const event of self) {
99
+ if (event.type === "content") {
100
+ yield event.delta;
101
+ }
102
+ }
103
+ }
104
+ };
105
+ }
106
+ /**
107
+ * Consume the stream and return the complete text.
108
+ */
109
+ async finalText() {
110
+ for await (const _ of this) {
111
+ }
112
+ return this._text;
113
+ }
114
+ /** Clean up resources. */
115
+ [Symbol.dispose]() {
116
+ this._reader?.cancel().catch(() => {
117
+ });
118
+ }
119
+ /** Close the stream. */
120
+ close() {
121
+ this[Symbol.dispose]();
122
+ }
123
+ };
124
+ var ChatClient = class {
125
+ constructor(client) {
126
+ this.client = client;
127
+ }
128
+ /**
129
+ * Send a chat message.
130
+ *
131
+ * @param options - Chat options including message, stream flag, etc.
132
+ * @returns ChatResponse if stream=false, AsyncIterable<StreamEvent> if stream=true.
133
+ */
134
+ async create(options) {
135
+ if (options.stream) {
136
+ return this._createStreamingIterator(options);
137
+ }
138
+ return this._createNonStreaming(options);
139
+ }
140
+ async _createNonStreaming(options) {
141
+ const body = {
142
+ message: options.message,
143
+ stream: false,
144
+ limit: options.limit ?? 10,
145
+ score_threshold: options.scoreThreshold ?? 0
146
+ };
147
+ if (options.chatId) {
148
+ body["chat_id"] = options.chatId;
149
+ }
150
+ if (options.filters) {
151
+ body["filters"] = options.filters;
152
+ }
153
+ if (options.filterId) {
154
+ body["filter_id"] = options.filterId;
155
+ }
156
+ const response = await this.client._request("POST", "/api/v1/chat", {
157
+ body: JSON.stringify(body)
158
+ });
159
+ const data = await response.json();
160
+ return {
161
+ response: data.response || "",
162
+ chatId: data.chat_id || null,
163
+ sources: data.sources || []
164
+ };
165
+ }
166
+ async _createStreamingIterator(options) {
167
+ const stream = new ChatStream(this.client, options);
168
+ await stream._init();
169
+ return stream;
170
+ }
171
+ /**
172
+ * Create a streaming chat context manager.
173
+ *
174
+ * @param options - Chat options.
175
+ * @returns ChatStream with helpers.
176
+ */
177
+ async stream(options) {
178
+ const stream = new ChatStream(this.client, { ...options, stream: true });
179
+ await stream._init();
180
+ return stream;
181
+ }
182
+ /**
183
+ * List all conversations.
184
+ */
185
+ async list() {
186
+ const response = await this.client._request("GET", "/api/v1/chat");
187
+ const data = await response.json();
188
+ const conversations = (data.conversations || []).map(
189
+ (c) => ({
190
+ chatId: c["chat_id"],
191
+ title: c["title"] || "",
192
+ createdAt: c["created_at"] || null,
193
+ lastActivity: c["last_activity"] || null,
194
+ messageCount: c["message_count"] || 0
195
+ })
196
+ );
197
+ return { conversations };
198
+ }
199
+ /**
200
+ * Get a specific conversation with full message history.
201
+ *
202
+ * @param chatId - The ID of the conversation to retrieve.
203
+ */
204
+ async get(chatId) {
205
+ const response = await this.client._request("GET", `/api/v1/chat/${chatId}`);
206
+ const data = await response.json();
207
+ const messages = (data.messages || []).map(
208
+ (m) => ({
209
+ role: m["role"],
210
+ content: m["content"],
211
+ timestamp: m["timestamp"] || null
212
+ })
213
+ );
214
+ return {
215
+ chatId: data.chat_id || chatId,
216
+ title: data.title || "",
217
+ createdAt: data.created_at || null,
218
+ lastActivity: data.last_activity || null,
219
+ messageCount: messages.length,
220
+ messages
221
+ };
222
+ }
223
+ /**
224
+ * Delete a conversation.
225
+ *
226
+ * @param chatId - The ID of the conversation to delete.
227
+ */
228
+ async delete(chatId) {
229
+ const response = await this.client._request("DELETE", `/api/v1/chat/${chatId}`);
230
+ const data = await response.json();
231
+ return data.success ?? false;
232
+ }
233
+ };
234
+
235
+ // src/documents.ts
236
+ var DocumentsClient = class {
237
+ constructor(client) {
238
+ this.client = client;
239
+ }
240
+ /**
241
+ * Ingest a document into the knowledge base.
242
+ *
243
+ * @param options - Ingest options (filePath or file+filename).
244
+ * @returns IngestTaskStatus with final status if wait=true, IngestResponse with task_id if wait=false.
245
+ */
246
+ async ingest(options) {
247
+ const formData = new FormData();
248
+ const wait = options.wait ?? true;
249
+ const pollInterval = options.pollInterval ?? 1;
250
+ const timeout = options.timeout ?? 300;
251
+ if (options.filePath) {
252
+ if (typeof globalThis.process !== "undefined") {
253
+ const fs = await import('fs');
254
+ const path = await import('path');
255
+ const fileBuffer = fs.readFileSync(options.filePath);
256
+ const filename = path.basename(options.filePath);
257
+ const blob = new Blob([fileBuffer]);
258
+ formData.append("file", blob, filename);
259
+ } else {
260
+ throw new Error("filePath is only supported in Node.js");
261
+ }
262
+ } else if (options.file) {
263
+ if (!options.filename) {
264
+ throw new Error("filename is required when providing file");
265
+ }
266
+ formData.append("file", options.file, options.filename);
267
+ } else {
268
+ throw new Error("Either filePath or file must be provided");
269
+ }
270
+ const response = await this.client._request(
271
+ "POST",
272
+ "/api/v1/documents/ingest",
273
+ {
274
+ body: formData,
275
+ isMultipart: true
276
+ }
277
+ );
278
+ const data = await response.json();
279
+ const ingestResponse = {
280
+ task_id: data.task_id,
281
+ status: data.status ?? null,
282
+ filename: data.filename ?? null
283
+ };
284
+ if (!wait) {
285
+ return ingestResponse;
286
+ }
287
+ return await this.waitForTask(ingestResponse.task_id, pollInterval, timeout);
288
+ }
289
+ /**
290
+ * Get the status of an ingestion task.
291
+ *
292
+ * @param taskId - The task ID returned from ingest().
293
+ * @returns IngestTaskStatus with current task status.
294
+ */
295
+ async getTaskStatus(taskId) {
296
+ const response = await this.client._request(
297
+ "GET",
298
+ `/api/v1/tasks/${taskId}`
299
+ );
300
+ const data = await response.json();
301
+ return {
302
+ task_id: data.task_id,
303
+ status: data.status,
304
+ total_files: data.total_files ?? 0,
305
+ processed_files: data.processed_files ?? 0,
306
+ successful_files: data.successful_files ?? 0,
307
+ failed_files: data.failed_files ?? 0,
308
+ files: data.files ?? {}
309
+ };
310
+ }
311
+ /**
312
+ * Wait for an ingestion task to complete.
313
+ *
314
+ * @param taskId - The task ID to wait for.
315
+ * @param pollInterval - Seconds between status checks.
316
+ * @param timeout - Maximum seconds to wait.
317
+ * @returns IngestTaskStatus with final status.
318
+ */
319
+ async waitForTask(taskId, pollInterval = 1, timeout = 300) {
320
+ const startTime = Date.now();
321
+ const timeoutMs = timeout * 1e3;
322
+ while (Date.now() - startTime < timeoutMs) {
323
+ const status = await this.getTaskStatus(taskId);
324
+ if (status.status === "completed" || status.status === "failed") {
325
+ return status;
326
+ }
327
+ await this.sleep(pollInterval * 1e3);
328
+ }
329
+ throw new Error(
330
+ `Ingestion task ${taskId} did not complete within ${timeout}s`
331
+ );
332
+ }
333
+ sleep(ms) {
334
+ return new Promise((resolve) => setTimeout(resolve, ms));
335
+ }
336
+ /**
337
+ * Delete a document from the knowledge base.
338
+ *
339
+ * @param filename - Name of the file to delete.
340
+ * @returns DeleteDocumentResponse with deleted chunk count.
341
+ */
342
+ async delete(filename) {
343
+ const response = await this.client._request("DELETE", "/api/v1/documents", {
344
+ body: JSON.stringify({ filename })
345
+ });
346
+ const data = await response.json();
347
+ return {
348
+ success: data.success ?? false,
349
+ deleted_chunks: data.deleted_chunks ?? 0
350
+ };
351
+ }
352
+ };
353
+
354
+ // src/search.ts
355
+ var SearchClient = class {
356
+ constructor(client) {
357
+ this.client = client;
358
+ }
359
+ /**
360
+ * Perform semantic search on documents.
361
+ *
362
+ * @param query - The search query text.
363
+ * @param options - Optional search options.
364
+ * @returns SearchResponse containing the search results.
365
+ */
366
+ async query(query, options) {
367
+ const body = {
368
+ query,
369
+ limit: options?.limit ?? 10,
370
+ score_threshold: options?.scoreThreshold ?? 0
371
+ };
372
+ if (options?.filters) {
373
+ body["filters"] = options.filters;
374
+ }
375
+ if (options?.filterId) {
376
+ body["filter_id"] = options.filterId;
377
+ }
378
+ const response = await this.client._request("POST", "/api/v1/search", {
379
+ body: JSON.stringify(body)
380
+ });
381
+ const data = await response.json();
382
+ return {
383
+ results: data.results || []
384
+ };
385
+ }
386
+ };
387
+
388
+ // src/knowledge-filters.ts
389
+ var KnowledgeFiltersClient = class {
390
+ constructor(client) {
391
+ this.client = client;
392
+ }
393
+ /**
394
+ * Create a new knowledge filter.
395
+ *
396
+ * @param options - The filter options including name and queryData.
397
+ * @returns The created filter response with ID.
398
+ */
399
+ async create(options) {
400
+ const body = {
401
+ name: options.name,
402
+ description: options.description ?? "",
403
+ queryData: JSON.stringify(options.queryData)
404
+ };
405
+ const response = await this.client._request("POST", "/knowledge-filter", {
406
+ body: JSON.stringify(body)
407
+ });
408
+ const data = await response.json();
409
+ return {
410
+ success: data.success ?? false,
411
+ id: data.id,
412
+ error: data.error
413
+ };
414
+ }
415
+ /**
416
+ * Search for knowledge filters by name, description, or query content.
417
+ *
418
+ * @param query - Optional search query text.
419
+ * @param limit - Maximum number of results (default 20).
420
+ * @returns List of matching knowledge filters.
421
+ */
422
+ async search(query, limit) {
423
+ const body = {
424
+ query: query ?? "",
425
+ limit: limit ?? 20
426
+ };
427
+ const response = await this.client._request(
428
+ "POST",
429
+ "/knowledge-filter/search",
430
+ {
431
+ body: JSON.stringify(body)
432
+ }
433
+ );
434
+ const data = await response.json();
435
+ if (!data.success || !data.filters) {
436
+ return [];
437
+ }
438
+ return data.filters.map((f) => this._parseFilter(f));
439
+ }
440
+ /**
441
+ * Get a specific knowledge filter by ID.
442
+ *
443
+ * @param filterId - The ID of the filter to retrieve.
444
+ * @returns The knowledge filter or null if not found.
445
+ */
446
+ async get(filterId) {
447
+ try {
448
+ const response = await this.client._request(
449
+ "GET",
450
+ `/knowledge-filter/${filterId}`
451
+ );
452
+ const data = await response.json();
453
+ if (!data.success || !data.filter) {
454
+ return null;
455
+ }
456
+ return this._parseFilter(data.filter);
457
+ } catch {
458
+ return null;
459
+ }
460
+ }
461
+ /**
462
+ * Update an existing knowledge filter.
463
+ *
464
+ * @param filterId - The ID of the filter to update.
465
+ * @param options - The fields to update.
466
+ * @returns Success status.
467
+ */
468
+ async update(filterId, options) {
469
+ const body = {};
470
+ if (options.name !== void 0) {
471
+ body["name"] = options.name;
472
+ }
473
+ if (options.description !== void 0) {
474
+ body["description"] = options.description;
475
+ }
476
+ if (options.queryData !== void 0) {
477
+ body["queryData"] = JSON.stringify(options.queryData);
478
+ }
479
+ const response = await this.client._request(
480
+ "PUT",
481
+ `/knowledge-filter/${filterId}`,
482
+ {
483
+ body: JSON.stringify(body)
484
+ }
485
+ );
486
+ const data = await response.json();
487
+ return data.success ?? false;
488
+ }
489
+ /**
490
+ * Delete a knowledge filter.
491
+ *
492
+ * @param filterId - The ID of the filter to delete.
493
+ * @returns Success status.
494
+ */
495
+ async delete(filterId) {
496
+ const response = await this.client._request(
497
+ "DELETE",
498
+ `/knowledge-filter/${filterId}`
499
+ );
500
+ const data = await response.json();
501
+ return data.success ?? false;
502
+ }
503
+ /**
504
+ * Parse a filter from API response, handling JSON-stringified queryData.
505
+ */
506
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
507
+ _parseFilter(filter) {
508
+ let queryData = filter["query_data"] ?? filter["queryData"];
509
+ if (typeof queryData === "string") {
510
+ try {
511
+ queryData = JSON.parse(queryData);
512
+ } catch {
513
+ queryData = {};
514
+ }
515
+ }
516
+ return {
517
+ id: filter["id"],
518
+ name: filter["name"],
519
+ description: filter["description"],
520
+ queryData,
521
+ owner: filter["owner"],
522
+ createdAt: filter["created_at"] ?? filter["createdAt"],
523
+ updatedAt: filter["updated_at"] ?? filter["updatedAt"]
524
+ };
525
+ }
526
+ };
527
+
528
+ // src/types.ts
529
+ var OpenRAGError = class extends Error {
530
+ constructor(message, statusCode) {
531
+ super(message);
532
+ this.statusCode = statusCode;
533
+ this.name = "OpenRAGError";
534
+ }
535
+ };
536
+ var AuthenticationError = class extends OpenRAGError {
537
+ constructor(message, statusCode) {
538
+ super(message, statusCode);
539
+ this.name = "AuthenticationError";
540
+ }
541
+ };
542
+ var NotFoundError = class extends OpenRAGError {
543
+ constructor(message, statusCode) {
544
+ super(message, statusCode);
545
+ this.name = "NotFoundError";
546
+ }
547
+ };
548
+ var ValidationError = class extends OpenRAGError {
549
+ constructor(message, statusCode) {
550
+ super(message, statusCode);
551
+ this.name = "ValidationError";
552
+ }
553
+ };
554
+ var RateLimitError = class extends OpenRAGError {
555
+ constructor(message, statusCode) {
556
+ super(message, statusCode);
557
+ this.name = "RateLimitError";
558
+ }
559
+ };
560
+ var ServerError = class extends OpenRAGError {
561
+ constructor(message, statusCode) {
562
+ super(message, statusCode);
563
+ this.name = "ServerError";
564
+ }
565
+ };
566
+
567
+ // src/client.ts
568
+ function getEnv(key) {
569
+ if (typeof globalThis.process !== "undefined" && globalThis.process.env) {
570
+ return globalThis.process.env[key];
571
+ }
572
+ return void 0;
573
+ }
574
+ var SettingsClient = class {
575
+ constructor(client) {
576
+ this.client = client;
577
+ }
578
+ /**
579
+ * Get current OpenRAG configuration.
580
+ */
581
+ async get() {
582
+ const response = await this.client._request("GET", "/api/v1/settings");
583
+ const data = await response.json();
584
+ return {
585
+ agent: data.agent || {},
586
+ knowledge: data.knowledge || {}
587
+ };
588
+ }
589
+ /**
590
+ * Update OpenRAG configuration.
591
+ *
592
+ * @param options - The settings to update.
593
+ * @returns Success response with message.
594
+ */
595
+ async update(options) {
596
+ const response = await this.client._request("POST", "/settings", {
597
+ body: JSON.stringify(options)
598
+ });
599
+ const data = await response.json();
600
+ return {
601
+ message: data.message || "Settings updated"
602
+ };
603
+ }
604
+ };
605
+ var OpenRAGClient = class _OpenRAGClient {
606
+ static DEFAULT_BASE_URL = "http://localhost:8080";
607
+ _apiKey;
608
+ _baseUrl;
609
+ _timeout;
610
+ /** Chat client for conversations. */
611
+ chat;
612
+ /** Search client for semantic search. */
613
+ search;
614
+ /** Documents client for ingestion and deletion. */
615
+ documents;
616
+ /** Settings client for configuration. */
617
+ settings;
618
+ /** Knowledge filters client for managing filters. */
619
+ knowledgeFilters;
620
+ constructor(options = {}) {
621
+ this._apiKey = options.apiKey || getEnv("OPENRAG_API_KEY") || "";
622
+ if (!this._apiKey) {
623
+ throw new AuthenticationError(
624
+ "API key is required. Set OPENRAG_API_KEY environment variable or pass apiKey option."
625
+ );
626
+ }
627
+ this._baseUrl = (options.baseUrl || getEnv("OPENRAG_URL") || _OpenRAGClient.DEFAULT_BASE_URL).replace(/\/$/, "");
628
+ this._timeout = options.timeout ?? 3e4;
629
+ this.chat = new ChatClient(this);
630
+ this.search = new SearchClient(this);
631
+ this.documents = new DocumentsClient(this);
632
+ this.settings = new SettingsClient(this);
633
+ this.knowledgeFilters = new KnowledgeFiltersClient(this);
634
+ }
635
+ /** @internal Get request headers with authentication. */
636
+ _getHeaders(isMultipart = false) {
637
+ const headers = {
638
+ "X-API-Key": this._apiKey
639
+ };
640
+ if (!isMultipart) {
641
+ headers["Content-Type"] = "application/json";
642
+ }
643
+ return headers;
644
+ }
645
+ /** @internal Make an authenticated request to the API. */
646
+ async _request(method, path, options = {}) {
647
+ const url = `${this._baseUrl}${path}`;
648
+ const headers = this._getHeaders(options.isMultipart);
649
+ const controller = new AbortController();
650
+ const timeoutId = setTimeout(() => controller.abort(), this._timeout);
651
+ try {
652
+ const response = await fetch(url, {
653
+ method,
654
+ headers,
655
+ body: options.body ?? null,
656
+ signal: controller.signal
657
+ });
658
+ if (!options.stream) {
659
+ this._handleError(response);
660
+ }
661
+ return response;
662
+ } finally {
663
+ clearTimeout(timeoutId);
664
+ }
665
+ }
666
+ /** @internal Handle error responses. */
667
+ _handleError(response) {
668
+ if (response.ok) return;
669
+ const statusCode = response.status;
670
+ const errorMessages = {
671
+ 401: "Invalid or missing API key",
672
+ 403: "Access denied",
673
+ 404: "Resource not found",
674
+ 400: "Invalid request",
675
+ 429: "Rate limit exceeded"
676
+ };
677
+ const message = errorMessages[statusCode] || `HTTP ${statusCode}`;
678
+ if (statusCode === 401 || statusCode === 403) {
679
+ throw new AuthenticationError(message, statusCode);
680
+ } else if (statusCode === 404) {
681
+ throw new NotFoundError(message, statusCode);
682
+ } else if (statusCode === 400) {
683
+ throw new ValidationError(message, statusCode);
684
+ } else if (statusCode === 429) {
685
+ throw new RateLimitError(message, statusCode);
686
+ } else if (statusCode >= 500) {
687
+ throw new ServerError(message, statusCode);
688
+ } else {
689
+ throw new OpenRAGError(message, statusCode);
690
+ }
691
+ }
692
+ };
693
+
694
+ export { AuthenticationError, ChatClient, ChatStream, DocumentsClient, KnowledgeFiltersClient, NotFoundError, OpenRAGClient, OpenRAGError, RateLimitError, SearchClient, ServerError, ValidationError };
695
+ //# sourceMappingURL=index.mjs.map
696
+ //# sourceMappingURL=index.mjs.map