@nebula-ai/sdk 1.2.2 → 1.4.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
@@ -1,1345 +1,1244 @@
1
- 'use strict';
2
-
3
- Object.defineProperty(exports, '__esModule', { value: true });
4
-
5
- // src/types.ts
6
- var GraphSearchResultType = /* @__PURE__ */ ((GraphSearchResultType2) => {
7
- GraphSearchResultType2["ENTITY"] = "entity";
8
- GraphSearchResultType2["RELATIONSHIP"] = "relationship";
9
- GraphSearchResultType2["COMMUNITY"] = "community";
10
- return GraphSearchResultType2;
11
- })(GraphSearchResultType || {});
12
- var NebulaException = class extends Error {
13
- constructor(message, statusCode, details) {
14
- super(message);
15
- this.statusCode = statusCode;
16
- this.details = details;
17
- this.name = "NebulaException";
18
- }
1
+ //#region src/runtime/errors.ts
2
+ var NebulaError = class extends Error {
3
+ name = "NebulaError";
4
+ constructor(message, options) {
5
+ super(message, options);
6
+ }
19
7
  };
20
- var NebulaClientException = class extends NebulaException {
21
- constructor(message, cause) {
22
- super(message);
23
- this.cause = cause;
24
- this.name = "NebulaClientException";
25
- }
8
+ var NebulaConnectionError = class extends NebulaError {
9
+ name = "NebulaConnectionError";
26
10
  };
27
- var NebulaAuthenticationException = class extends NebulaException {
28
- constructor(message = "Invalid API key") {
29
- super(message, 401);
30
- this.name = "NebulaAuthenticationException";
31
- }
11
+ var NebulaTimeoutError = class extends NebulaError {
12
+ name = "NebulaTimeoutError";
32
13
  };
33
- var NebulaRateLimitException = class extends NebulaException {
34
- constructor(message = "Rate limit exceeded") {
35
- super(message, 429);
36
- this.name = "NebulaRateLimitException";
37
- }
14
+ function isEnvelope(body) {
15
+ return typeof body === "object" && body !== null && typeof body.type === "string" && typeof body.message === "string";
16
+ }
17
+ var NebulaAPIError = class extends NebulaError {
18
+ name = "NebulaAPIError";
19
+ status;
20
+ requestId;
21
+ body;
22
+ type;
23
+ code;
24
+ details;
25
+ constructor(payload, message) {
26
+ const envelope = isEnvelope(payload.body) ? payload.body : void 0;
27
+ super(message ?? envelope?.message ?? `Nebula API error (status ${payload.status})`);
28
+ this.status = payload.status;
29
+ const envCode = typeof envelope?.code === "string" ? envelope.code : void 0;
30
+ const envRid = typeof envelope?.request_id === "string" ? envelope.request_id : void 0;
31
+ this.requestId = envRid ?? payload.requestId;
32
+ this.body = payload.body;
33
+ this.type = envelope?.type;
34
+ this.code = envCode;
35
+ this.details = envelope?.details ?? void 0;
36
+ }
38
37
  };
39
- var NebulaValidationException = class extends NebulaException {
40
- constructor(message = "Validation error", details) {
41
- super(message, 400);
42
- this.details = details;
43
- this.name = "NebulaValidationException";
44
- }
38
+ var NebulaBadRequestError = class extends NebulaAPIError {
39
+ name = "NebulaBadRequestError";
45
40
  };
46
- var NebulaCollectionNotFoundException = class extends NebulaException {
47
- constructor(message = "Collection not found") {
48
- super(message, 404);
49
- this.name = "NebulaCollectionNotFoundException";
50
- }
41
+ var NebulaUnauthorizedError = class extends NebulaAPIError {
42
+ name = "NebulaUnauthorizedError";
51
43
  };
52
- var NebulaNotFoundException = class extends NebulaException {
53
- constructor(resourceId, resourceType = "Resource") {
54
- super(`${resourceType} not found: ${resourceId}`, 404);
55
- this.name = "NebulaNotFoundException";
56
- }
44
+ var NebulaForbiddenError = class extends NebulaAPIError {
45
+ name = "NebulaForbiddenError";
57
46
  };
58
-
59
- // src/client.ts
60
- var _Nebula = class _Nebula {
61
- // 5MB
62
- constructor(config = {}) {
63
- this.apiKey = config.apiKey;
64
- if (!this.apiKey) {
65
- throw new NebulaClientException(
66
- "API key is required. Pass it to the constructor or set NEBULA_API_KEY environment variable."
67
- );
68
- }
69
- this.baseUrl = (config.baseUrl || "https://api.trynebula.ai").replace(/\/$/, "");
70
- this.timeout = config.timeout || 3e4;
71
- }
72
- // Public mutators used by tests
73
- setApiKey(next) {
74
- this.apiKey = next;
75
- }
76
- setBaseUrl(next) {
77
- this.baseUrl = (next || this.baseUrl).replace(/\/$/, "");
78
- }
79
- // Kept for backwards-compat tests; no-op in current implementation
80
- setCorsProxy(_next) {
81
- }
82
- /** Check if API key is set */
83
- isApiKeySet() {
84
- return !!(this.apiKey && this.apiKey.trim() !== "");
85
- }
86
- /** Detect if a token looks like a Nebula API key (public.raw) */
87
- _isNebulaApiKey(token) {
88
- const candidate = token || this.apiKey;
89
- if (!candidate) return false;
90
- const parts = candidate.split(".");
91
- if (parts.length !== 2) return false;
92
- const [publicPart, rawPart] = parts;
93
- return (publicPart.startsWith("key_") || publicPart.startsWith("neb_")) && !!rawPart && rawPart.length > 0;
94
- }
95
- /** Build authentication headers */
96
- _buildAuthHeaders(includeContentType = true) {
97
- const headers = {};
98
- if (this._isNebulaApiKey()) {
99
- headers["X-API-Key"] = this.apiKey;
100
- } else {
101
- headers["Authorization"] = `Bearer ${this.apiKey}`;
102
- }
103
- if (includeContentType) {
104
- headers["Content-Type"] = "application/json";
105
- }
106
- return headers;
107
- }
108
- _isRecord(value) {
109
- return typeof value === "object" && value !== null && !Array.isArray(value);
110
- }
111
- _unwrapResults(value) {
112
- if (this._isRecord(value) && "results" in value) {
113
- return value.results;
114
- }
115
- return value;
116
- }
117
- _unwrapResultsArray(value) {
118
- const unwrapped = this._unwrapResults(value);
119
- if (Array.isArray(unwrapped)) {
120
- return unwrapped;
121
- }
122
- if (unwrapped === void 0 || unwrapped === null) {
123
- return [];
124
- }
125
- return [unwrapped];
126
- }
127
- _looksLikeMultimodalContent(content) {
128
- if (!Array.isArray(content)) return false;
129
- return content.some((part) => {
130
- if (!this._isRecord(part)) return false;
131
- if (typeof part.type === "string") return true;
132
- if ("data" in part || "s3_key" in part || "url" in part) return true;
133
- return false;
134
- });
135
- }
136
- _normalizeContentParts(contentParts) {
137
- return contentParts.map((part) => {
138
- if (typeof part === "string") {
139
- return { type: "text", text: part };
140
- }
141
- if (!this._isRecord(part)) {
142
- return { type: "text", text: String(part) };
143
- }
144
- if (typeof part.type === "string") {
145
- return part;
146
- }
147
- if ("s3_key" in part && typeof part.s3_key === "string") {
148
- return {
149
- type: "s3_ref",
150
- s3_key: part.s3_key,
151
- bucket: typeof part.bucket === "string" ? part.bucket : void 0,
152
- media_type: typeof part.media_type === "string" ? part.media_type : "application/octet-stream",
153
- filename: typeof part.filename === "string" ? part.filename : void 0,
154
- size_bytes: typeof part.size_bytes === "number" ? part.size_bytes : void 0
155
- };
156
- }
157
- if ("data" in part && typeof part.data === "string") {
158
- return {
159
- type: "file",
160
- data: part.data,
161
- media_type: typeof part.media_type === "string" ? part.media_type : "application/octet-stream",
162
- filename: typeof part.filename === "string" ? part.filename : void 0,
163
- duration_seconds: typeof part.duration_seconds === "number" ? part.duration_seconds : void 0
164
- };
165
- }
166
- return { type: "text", text: String(part) };
167
- });
168
- }
169
- async _serializeContentAsText(content) {
170
- if (this._looksLikeMultimodalContent(content)) {
171
- const normalized = this._normalizeContentParts(content);
172
- const processed = await this._processContentParts(normalized);
173
- return JSON.stringify(processed);
174
- }
175
- if (typeof content === "object" && content !== null) {
176
- return JSON.stringify(content);
177
- }
178
- return String(content ?? "");
179
- }
180
- async _serializeContentAsParts(content) {
181
- if (!this._looksLikeMultimodalContent(content)) return null;
182
- const normalized = this._normalizeContentParts(content);
183
- return await this._processContentParts(normalized);
184
- }
185
- /** Make an HTTP request to the Nebula API */
186
- async _makeRequest(method, endpoint, jsonData, params, extraHeaders) {
187
- const url = new URL(endpoint, this.baseUrl);
188
- if (params) {
189
- Object.entries(params).forEach(([key, value]) => {
190
- if (value !== void 0 && value !== null) {
191
- if (Array.isArray(value)) {
192
- value.forEach((item) => {
193
- url.searchParams.append(key, String(item));
194
- });
195
- } else {
196
- url.searchParams.append(key, String(value));
197
- }
198
- }
199
- });
200
- }
201
- const headers = { ...this._buildAuthHeaders(true), ...extraHeaders };
202
- const controller = new AbortController();
203
- const timeoutId = setTimeout(() => controller.abort(), this.timeout);
204
- try {
205
- const response = await fetch(url.toString(), {
206
- method,
207
- headers,
208
- body: jsonData ? JSON.stringify(jsonData) : void 0,
209
- signal: controller.signal
210
- });
211
- clearTimeout(timeoutId);
212
- if (response.status === 200 || response.status === 202) {
213
- return await response.json();
214
- } else if (response.status === 401) {
215
- throw new NebulaAuthenticationException("Invalid API key");
216
- } else if (response.status === 429) {
217
- throw new NebulaRateLimitException("Rate limit exceeded");
218
- } else if (response.status === 400) {
219
- const errorData = await response.json().catch(() => ({}));
220
- throw new NebulaValidationException(errorData.message || "Validation error", errorData.details);
221
- } else if (response.status === 422) {
222
- const errorData = await response.json().catch(() => ({}));
223
- console.error("[SDK] 422 Validation error - Full details:");
224
- console.error(" Status:", response.status);
225
- console.error(" Error data:", JSON.stringify(errorData, null, 2));
226
- console.error(" Message:", errorData.message);
227
- console.error(" Detail:", errorData.detail);
228
- throw new NebulaValidationException(
229
- errorData.message || (typeof errorData.detail === "string" ? errorData.detail : JSON.stringify(errorData.detail)) || "Validation error",
230
- errorData
231
- );
232
- } else {
233
- const errorData = await response.json().catch(() => ({}));
234
- throw new NebulaException(errorData.message || `API error: ${response.status}`, response.status, errorData);
235
- }
236
- } catch (error) {
237
- clearTimeout(timeoutId);
238
- if (error instanceof NebulaException) {
239
- throw error;
240
- }
241
- if (error instanceof Error && error.name === "AbortError") {
242
- throw new NebulaClientException(`Request timed out after ${this.timeout} milliseconds`);
243
- }
244
- if (error instanceof Error) {
245
- throw new NebulaClientException(`Request failed: ${error.message}`, error);
246
- }
247
- throw new NebulaClientException(`Request failed: ${String(error)}`);
248
- }
249
- }
250
- // Collection Management Methods
251
- /** Create a new collection */
252
- async createCollection(options) {
253
- const data = { name: options.name };
254
- if (options.description) data.description = options.description;
255
- if (options.metadata) data.metadata = options.metadata;
256
- const response = await this._makeRequest("POST", "/v1/collections", data);
257
- const result = response.results || response;
258
- return this._collectionFromDict(result);
259
- }
260
- /** Get a specific collection by ID */
261
- async getCollection(collectionId) {
262
- const response = await this._makeRequest("GET", `/v1/collections/${collectionId}`);
263
- const result = response.results || response;
264
- return this._collectionFromDict(result);
265
- }
266
- /** Get a specific collection by name */
267
- async getCollectionByName(name) {
268
- const response = await this._makeRequest("GET", `/v1/collections/name/${name}`);
269
- const result = response.results || response;
270
- return this._collectionFromDict(result);
271
- }
272
- /** Get all collections */
273
- async listCollections(options) {
274
- const params = {
275
- limit: options?.limit ?? 100,
276
- offset: options?.offset ?? 0
277
- };
278
- if (options?.name !== void 0) {
279
- params.name = options.name;
280
- }
281
- const response = await this._makeRequest("GET", "/v1/collections", void 0, params);
282
- let collections;
283
- if (typeof response === "object" && response !== null && "results" in response) {
284
- collections = response.results;
285
- } else if (Array.isArray(response)) {
286
- collections = response;
287
- } else {
288
- collections = [response];
289
- }
290
- return collections.map((collection) => this._collectionFromDict(collection));
291
- }
292
- /** Update a collection */
293
- async updateCollection(options) {
294
- const data = {};
295
- if (options.name !== void 0) data.name = options.name;
296
- if (options.description !== void 0) data.description = options.description;
297
- if (options.metadata !== void 0) data.metadata = options.metadata;
298
- const response = await this._makeRequest("POST", `/v1/collections/${options.collectionId}`, data);
299
- const result = response.results || response;
300
- return this._collectionFromDict(result);
301
- }
302
- /** Delete a collection */
303
- async deleteCollection(collectionId) {
304
- await this._makeRequest("DELETE", `/v1/collections/${collectionId}`);
305
- return true;
306
- }
307
- // Memory Management Methods
308
- /**
309
- * Legacy convenience: store raw text content into a collection as a document
310
- */
311
- async store(content, collectionId, metadata = {}) {
312
- const docMetadata = {
313
- ...metadata,
314
- memory_type: "memory",
315
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
316
- };
317
- const payload = {
318
- collection_id: collectionId,
319
- raw_text: String(content || ""),
320
- metadata: docMetadata,
321
- ingestion_mode: "fast"
322
- };
323
- const response = await this._makeRequest("POST", "/v1/memories", payload);
324
- const id = response?.results?.engram_id || response?.results?.id || response?.id || "";
325
- const timestamp = docMetadata.timestamp;
326
- const result = {
327
- id: String(id),
328
- memory_id: String(id),
329
- content: String(content || ""),
330
- metadata: docMetadata,
331
- collection_ids: [collectionId],
332
- created_at: timestamp,
333
- updated_at: timestamp
334
- };
335
- return result;
336
- }
337
- /**
338
- * Store a single memory using the unified engrams API.
339
- *
340
- * Automatically infers memory type:
341
- * - If role is present, creates a conversation
342
- * - Otherwise, creates a document
343
- */
344
- async storeMemory(memory, name) {
345
- let mem;
346
- if ("collection_id" in memory || "snapshot" in memory) {
347
- mem = memory;
348
- } else {
349
- const memRecord2 = memory;
350
- mem = {
351
- collection_id: memRecord2.collection_id || memRecord2.collectionId || "",
352
- content: memRecord2.content || "",
353
- role: memRecord2.role,
354
- memory_id: memRecord2.memory_id || memRecord2.memoryId || void 0,
355
- metadata: memRecord2.metadata || {},
356
- snapshot: memRecord2.snapshot
357
- };
358
- }
359
- if (mem.snapshot) {
360
- const contentText = await this._serializeContentAsText(mem.content);
361
- const payload2 = {
362
- snapshot: mem.snapshot,
363
- raw_text: contentText
364
- };
365
- const response2 = await this._makeRequest("POST", "/v1/memories", payload2);
366
- const snapshot = response2?.results?.snapshot;
367
- if (snapshot) {
368
- return snapshot;
369
- }
370
- return response2?.results ?? {};
371
- }
372
- if (mem.memory_id) {
373
- return await this._appendToMemory(mem.memory_id, mem);
374
- }
375
- const memoryType = mem.role ? "conversation" : "document";
376
- if (memoryType === "conversation") {
377
- const messages = [];
378
- if (mem.content && mem.role) {
379
- const multimodalParts2 = await this._serializeContentAsParts(mem.content);
380
- const msgContent = multimodalParts2 ?? await this._serializeContentAsText(mem.content);
381
- const memRecord2 = mem;
382
- messages.push({
383
- content: msgContent,
384
- role: mem.role,
385
- metadata: mem.metadata || {},
386
- ...typeof memRecord2.authority === "number" ? { authority: Number(memRecord2.authority) } : {}
387
- });
388
- }
389
- if (messages.length === 0) {
390
- throw new NebulaClientException("Cannot create conversation without messages. Provide content and role.");
391
- }
392
- const data = {
393
- collection_id: mem.collection_id,
394
- name: name || "Conversation",
395
- messages,
396
- metadata: mem.metadata || {}
397
- };
398
- const response2 = await this._makeRequest("POST", "/v1/memories", data);
399
- if (response2.results) {
400
- const convId = response2.results.memory_id || response2.results.id;
401
- if (!convId) {
402
- throw new NebulaClientException("Failed to create conversation: no id returned");
403
- }
404
- return String(convId);
405
- }
406
- throw new NebulaClientException("Failed to create conversation: invalid response format");
407
- }
408
- const docMetadata = { ...mem.metadata };
409
- docMetadata.memory_type = "memory";
410
- const memRecord = mem;
411
- if (typeof memRecord.authority === "number") {
412
- const v = Number(memRecord.authority);
413
- if (!Number.isNaN(v) && v >= 0 && v <= 1) {
414
- docMetadata.authority = v;
415
- }
416
- }
417
- let payload;
418
- const multimodalParts = await this._serializeContentAsParts(mem.content);
419
- if (multimodalParts) {
420
- payload = {
421
- collection_id: mem.collection_id,
422
- content_parts: multimodalParts,
423
- metadata: docMetadata,
424
- ingestion_mode: "fast"
425
- };
426
- } else if (Array.isArray(mem.content) && mem.content.every((x) => typeof x === "string")) {
427
- payload = {
428
- collection_id: mem.collection_id,
429
- chunks: mem.content,
430
- metadata: docMetadata,
431
- ingestion_mode: "fast"
432
- };
433
- } else {
434
- const contentText = await this._serializeContentAsText(mem.content);
435
- if (!contentText || contentText === '""' || contentText === "[]" || contentText === "{}") {
436
- throw new NebulaClientException("Content is required for document memories");
437
- }
438
- payload = {
439
- collection_id: mem.collection_id,
440
- raw_text: contentText,
441
- metadata: docMetadata,
442
- ingestion_mode: "fast"
443
- };
444
- }
445
- const response = await this._makeRequest("POST", "/v1/memories", payload);
446
- const id = response?.results?.engram_id || response?.results?.id || response?.id || "";
447
- return String(id || "");
448
- }
449
- /**
450
- * Internal method to append content to an existing memory
451
- *
452
- * @throws NebulaNotFoundException if memory_id doesn't exist
453
- */
454
- async _appendToMemory(memoryId, memory) {
455
- const collectionId = memory.collection_id;
456
- const content = memory.content;
457
- const metadata = memory.metadata;
458
- if (!collectionId) {
459
- throw new NebulaClientException("collection_id is required");
460
- }
461
- const payload = {
462
- collection_id: collectionId
463
- };
464
- if (Array.isArray(content)) {
465
- if (content.length > 0 && typeof content[0] === "object" && "content" in content[0]) {
466
- payload.messages = content;
467
- } else {
468
- payload.chunks = content;
469
- }
470
- } else if (typeof content === "string") {
471
- payload.raw_text = content;
472
- } else {
473
- throw new NebulaClientException(
474
- "content must be a string, array of strings, or array of message objects"
475
- );
476
- }
477
- if (metadata) {
478
- payload.metadata = metadata;
479
- }
480
- try {
481
- await this._makeRequest("POST", `/v1/memories/${memoryId}/append`, payload);
482
- return memoryId;
483
- } catch (error) {
484
- if (error instanceof NebulaException && error.statusCode === 404) {
485
- throw new NebulaNotFoundException(memoryId, "Memory");
486
- }
487
- throw error;
488
- }
489
- }
490
- /** Store multiple memories using the unified engrams API.
491
- * @param memories - List of Memory objects to store.
492
- * @param metadata - Optional memory-level metadata for conversation groups.
493
- * Each Memory's own metadata is used as per-message metadata.
494
- */
495
- async storeMemories(memories, metadata) {
496
- const results = [];
497
- const convGroups = {};
498
- const others = [];
499
- for (const m of memories) {
500
- if (m.role) {
501
- const key = m.memory_id || `__new__::${m.collection_id}`;
502
- if (!convGroups[key]) convGroups[key] = [];
503
- convGroups[key].push(m);
504
- } else {
505
- others.push(m);
506
- }
507
- }
508
- for (const [key, group] of Object.entries(convGroups)) {
509
- const collectionId = group[0].collection_id;
510
- let convId;
511
- const messages = [];
512
- for (const m of group) {
513
- const multimodalParts = await this._serializeContentAsParts(m.content);
514
- const msgContent = multimodalParts ?? await this._serializeContentAsText(m.content);
515
- if (typeof msgContent === "string") {
516
- if (!msgContent.trim()) continue;
517
- } else if (msgContent.length === 0) {
518
- continue;
519
- }
520
- const mRecord = m;
521
- messages.push({
522
- content: msgContent,
523
- role: m.role,
524
- metadata: m.metadata || {},
525
- ...typeof mRecord.authority === "number" ? { authority: Number(mRecord.authority) } : {}
526
- });
527
- }
528
- if (!messages.length) {
529
- throw new NebulaClientException(
530
- "Cannot create/append conversation without messages. Provide non-empty content."
531
- );
532
- }
533
- if (key.startsWith("__new__::")) {
534
- const data = {
535
- collection_id: collectionId,
536
- name: "Conversation",
537
- messages,
538
- metadata: metadata ?? {}
539
- };
540
- const response = await this._makeRequest("POST", "/v1/memories", data);
541
- if (response.results) {
542
- convId = response.results.memory_id || response.results.id || "";
543
- if (!convId) {
544
- throw new NebulaClientException("Failed to create conversation: no id returned");
545
- }
546
- } else {
547
- throw new NebulaClientException("Failed to create conversation: invalid response format");
548
- }
549
- } else {
550
- convId = key;
551
- const appendMem = {
552
- collection_id: collectionId,
553
- content: messages,
554
- memory_id: convId,
555
- metadata: metadata ?? {}
556
- };
557
- await this._appendToMemory(convId, appendMem);
558
- }
559
- results.push(...Array(group.length).fill(String(convId)));
560
- }
561
- for (const m of others) {
562
- results.push(await this.storeMemory(m));
563
- }
564
- return results;
565
- }
566
- /** Delete one or more memories */
567
- async delete(memoryIds) {
568
- try {
569
- console.log("[SDK] delete() called with:", { memoryIds, type: typeof memoryIds, isArray: Array.isArray(memoryIds) });
570
- if (typeof memoryIds === "string") {
571
- console.log("[SDK] Single deletion path for ID:", memoryIds);
572
- try {
573
- await this._makeRequest("DELETE", `/v1/memories/${memoryIds}`);
574
- return true;
575
- } catch {
576
- console.log("[SDK] Falling back to POST /v1/memories/delete with single ID");
577
- const response = await this._makeRequest("POST", "/v1/memories/delete", memoryIds);
578
- return typeof response === "object" && response.success !== void 0 ? response.success : true;
579
- }
580
- } else {
581
- console.log("[SDK] Batch deletion path for IDs:", memoryIds);
582
- console.log("[SDK] Sending POST request with body:", memoryIds);
583
- const response = await this._makeRequest("POST", "/v1/memories/delete", memoryIds);
584
- console.log("[SDK] Batch deletion response:", response);
585
- return response;
586
- }
587
- } catch (error) {
588
- console.error("[SDK] Delete error:", error);
589
- if (error instanceof Error) {
590
- throw error;
591
- }
592
- throw new NebulaClientException(`Unknown error: ${String(error)}`);
593
- }
594
- }
595
- /** Delete a specific source within a memory */
596
- async deleteSource(sourceId) {
597
- try {
598
- await this._makeRequest("DELETE", `/v1/sources/${sourceId}`);
599
- return true;
600
- } catch (error) {
601
- if (error instanceof NebulaException && error.statusCode === 404) {
602
- throw new NebulaNotFoundException(sourceId, "Source");
603
- }
604
- throw error;
605
- }
606
- }
607
- /** Update a specific source within a memory */
608
- async updateSource(sourceId, content, metadata) {
609
- const payload = { content };
610
- if (metadata !== void 0) {
611
- payload.metadata = metadata;
612
- }
613
- try {
614
- await this._makeRequest("PATCH", `/v1/sources/${sourceId}`, payload);
615
- return true;
616
- } catch (error) {
617
- if (error instanceof NebulaException && error.statusCode === 404) {
618
- throw new NebulaNotFoundException(sourceId, "Source");
619
- }
620
- throw error;
621
- }
622
- }
623
- /**
624
- * Update memory-level properties including name, metadata, and collection associations.
625
- *
626
- * This method allows updating properties of an entire memory (document or conversation)
627
- * without modifying its content. For updating individual sources within a memory,
628
- * use updateSource(). For updating content, use storeMemory() to append.
629
- *
630
- * @param options - Update configuration
631
- * @param options.memoryId - The ID of the memory to update
632
- * @param options.name - New name for the memory (useful for conversations and documents)
633
- * @param options.metadata - Metadata to set. By default, replaces existing metadata.
634
- * Set mergeMetadata=true to merge with existing metadata instead.
635
- * @param options.collectionIds - New collection associations. Must specify at least one valid collection.
636
- * @param options.mergeMetadata - If true, merges provided metadata with existing metadata.
637
- * If false (default), replaces existing metadata entirely.
638
- *
639
- * @returns Promise resolving to true if successful
640
- *
641
- * @throws NebulaNotFoundException if memory_id doesn't exist
642
- * @throws NebulaValidationException if validation fails (e.g., no fields provided)
643
- * @throws NebulaAuthenticationException if user doesn't have permission to update this memory
644
- */
645
- async updateMemory(options) {
646
- const payload = {};
647
- if (options.name !== void 0) {
648
- payload.name = options.name;
649
- }
650
- if (options.metadata !== void 0) {
651
- payload.metadata = options.metadata;
652
- payload.merge_metadata = options.mergeMetadata ?? false;
653
- }
654
- if (options.collectionIds !== void 0) {
655
- payload.collection_ids = options.collectionIds;
656
- }
657
- if (Object.keys(payload).length === 0) {
658
- throw new NebulaValidationException(
659
- "At least one field (name, metadata, or collectionIds) must be provided to update"
660
- );
661
- }
662
- try {
663
- await this._makeRequest("PATCH", `/v1/memories/${options.memoryId}`, payload);
664
- return true;
665
- } catch (error) {
666
- if (error instanceof NebulaException && error.statusCode === 404) {
667
- throw new NebulaNotFoundException(options.memoryId, "Memory");
668
- }
669
- throw error;
670
- }
671
- }
672
- /**
673
- * Get all memories from specific collections with optional metadata filtering
674
- *
675
- * @param options - Configuration for listing memories
676
- * @param options.collection_ids - One or more collection IDs to retrieve memories from
677
- * @param options.limit - Maximum number of memories to return (default: 100)
678
- * @param options.offset - Number of memories to skip for pagination (default: 0)
679
- * @param options.metadata_filters - Optional metadata filters using MongoDB-like operators.
680
- * Supported operators: $eq, $ne, $in, $nin, $exists, $and, $or
681
- *
682
- * @returns Promise resolving to array of MemoryResponse objects
683
- *
684
- * @example
685
- * // Get all playground memories excluding conversations
686
- * const memories = await client.listMemories({
687
- * collection_ids: ['collection-id'],
688
- * metadata_filters: {
689
- * 'metadata.content_type': { $ne: 'conversation' }
690
- * }
691
- * });
692
- *
693
- * @example
694
- * // Complex filter with multiple conditions
695
- * const memories = await client.listMemories({
696
- * collection_ids: ['collection-id'],
697
- * metadata_filters: {
698
- * $and: [
699
- * { 'metadata.playground': { $eq: true } },
700
- * { 'metadata.session_id': { $exists: true } }
701
- * ]
702
- * }
703
- * });
704
- */
705
- async listMemories(options) {
706
- const ids = Array.isArray(options.collection_ids) ? options.collection_ids : [options.collection_ids];
707
- if (!ids.length) {
708
- throw new NebulaClientException("collection_ids must be provided to list_memories().");
709
- }
710
- const params = {
711
- limit: options.limit ?? 100,
712
- offset: options.offset ?? 0,
713
- collection_ids: ids
714
- };
715
- if (options.metadata_filters) {
716
- params.metadata_filters = JSON.stringify(options.metadata_filters);
717
- }
718
- const response = await this._makeRequest("GET", "/v1/memories", void 0, params);
719
- let documents;
720
- if (typeof response === "object" && response !== null && "results" in response) {
721
- documents = response.results;
722
- } else if (Array.isArray(response)) {
723
- documents = response;
724
- } else {
725
- documents = [response];
726
- }
727
- return documents.map((doc) => this._memoryResponseFromDict(doc, ids));
728
- }
729
- /** Get a specific memory by engram ID */
730
- async getMemory(memoryId) {
731
- const response = await this._makeRequest("GET", `/v1/memories/${memoryId}`);
732
- const content = response.text || response.content;
733
- const chunks = Array.isArray(response.chunks) ? response.chunks : void 0;
734
- const memoryData = {
735
- id: response.id,
736
- content,
737
- chunks,
738
- metadata: response.metadata || {},
739
- collection_ids: response.collection_ids || []
740
- };
741
- return this._memoryResponseFromDict(memoryData, []);
742
- }
743
- // Search Methods
744
- /**
745
- * Search within specific collections with optional metadata filtering.
746
- *
747
- * @param options - Search configuration
748
- * @param options.query - Search query string
749
- * @param options.collection_ids - One or more collection IDs to search within
750
- * @param options.effort - Compute effort budget (auto/low/medium/high). Controls traversal compute, not MemoryResponse size.
751
- * @param options.filters - Optional filters to apply to the search. Supports comprehensive metadata filtering
752
- * with MongoDB-like operators for both vector/chunk search and graph search.
753
- * @param options.searchSettings - Optional search configuration
754
- *
755
- * @returns Promise resolving to array of SearchResult objects containing both vector/chunk and graph search results
756
- *
757
- * @example
758
- * // Basic equality filter
759
- * await client.search({
760
- * query: "machine learning",
761
- * collection_ids: ["research-collection"],
762
- * filters: {
763
- * "metadata.category": { $eq: "research" },
764
- * "metadata.verified": true // Shorthand for $eq
765
- * }
766
- * });
767
- *
768
- * @example
769
- * // Numeric comparisons
770
- * await client.search({
771
- * query: "high priority",
772
- * collection_ids: ["tasks"],
773
- * filters: {
774
- * "metadata.priority": { $gte: 8 },
775
- * "metadata.score": { $lt: 100 }
776
- * }
777
- * });
778
- *
779
- * @example
780
- * // String matching
781
- * await client.search({
782
- * query: "employees",
783
- * collection_ids: ["team"],
784
- * filters: {
785
- * "metadata.email": { $ilike: "%@company.com" } // Case-insensitive
786
- * }
787
- * });
788
- *
789
- * @example
790
- * // Array operations
791
- * await client.search({
792
- * query: "developers",
793
- * collection_ids: ["team"],
794
- * filters: {
795
- * "metadata.skills": { $overlap: ["python", "typescript"] } // Has any
796
- * }
797
- * });
798
- *
799
- * @example
800
- * // Nested paths
801
- * await client.search({
802
- * query: "users",
803
- * collection_ids: ["profiles"],
804
- * filters: {
805
- * "metadata.user.preferences.theme": { $eq: "dark" }
806
- * }
807
- * });
808
- *
809
- * @example
810
- * // Complex logical combinations
811
- * await client.search({
812
- * query: "candidates",
813
- * collection_ids: ["hiring"],
814
- * filters: {
815
- * $and: [
816
- * { "metadata.verified": true },
817
- * { "metadata.level": { $gte: 5 } },
818
- * {
819
- * $or: [
820
- * { "metadata.skills": { $overlap: ["python", "go"] } },
821
- * { "metadata.years_experience": { $gte: 8 } }
822
- * ]
823
- * }
824
- * ]
825
- * }
826
- * });
827
- *
828
- * @remarks
829
- * Supported Operators:
830
- * - Comparison: $eq, $ne, $lt, $lte, $gt, $gte
831
- * - String: $like (case-sensitive), $ilike (case-insensitive)
832
- * - Array: $in, $nin, $overlap, $contains
833
- * - JSONB: $json_contains
834
- * - Logical: $and, $or
835
- *
836
- * For comprehensive filtering documentation, see the Metadata Filtering Guide:
837
- * https://docs.trynebula.ai/guides/metadata-filtering
838
- */
839
- async search(options) {
840
- if (options.snapshot) {
841
- const snapshotData = {
842
- snapshot: options.snapshot,
843
- query: options.query
844
- };
845
- if (options.effort) {
846
- snapshotData.effort = options.effort;
847
- }
848
- const response2 = await this._makeRequest("POST", "/v1/memories/search", snapshotData);
849
- const memoryResponseData2 = response2.results;
850
- return {
851
- query: memoryResponseData2.query || options.query,
852
- semantics: memoryResponseData2.semantics || memoryResponseData2.knowledge || [],
853
- procedures: memoryResponseData2.procedures || [],
854
- episodes: memoryResponseData2.episodes || [],
855
- sources: memoryResponseData2.sources || [],
856
- total_traversal_time_ms: memoryResponseData2.total_traversal_time_ms,
857
- token_count: memoryResponseData2.token_count,
858
- entities: memoryResponseData2.entities || []
859
- };
860
- }
861
- const data = {
862
- query: options.query
863
- };
864
- if (options.effort) {
865
- data.effort = options.effort;
866
- }
867
- if (options.collection_ids) {
868
- const collectionIds2 = Array.isArray(options.collection_ids) ? options.collection_ids : [options.collection_ids];
869
- const validCollectionIds = collectionIds2.filter((id) => id && id.trim() !== "");
870
- if (validCollectionIds.length) {
871
- data.collection_ids = validCollectionIds;
872
- }
873
- }
874
- if (options.filters) {
875
- data.filters = options.filters;
876
- }
877
- if (options.searchSettings) {
878
- data.search_settings = options.searchSettings;
879
- }
880
- const collectionIds = data.collection_ids;
881
- let extraHeaders;
882
- if (collectionIds && collectionIds.length === 1) {
883
- extraHeaders = { "X-Nebula-Collection-Id": collectionIds[0] };
884
- } else if (collectionIds && collectionIds.length > 1) {
885
- extraHeaders = { "X-Nebula-Collection-Id": [...collectionIds].sort().join(",") };
886
- }
887
- const response = await this._makeRequest("POST", "/v1/memories/search", data, void 0, extraHeaders);
888
- const memoryResponseData = response.results;
889
- const memoryResponse = {
890
- query: memoryResponseData.query || options.query,
891
- semantics: memoryResponseData.semantics || memoryResponseData.knowledge || [],
892
- procedures: memoryResponseData.procedures || [],
893
- episodes: memoryResponseData.episodes || [],
894
- sources: memoryResponseData.sources || [],
895
- total_traversal_time_ms: memoryResponseData.total_traversal_time_ms,
896
- token_count: memoryResponseData.token_count,
897
- entities: memoryResponseData.entities || []
898
- };
899
- return memoryResponse;
900
- }
901
- // Connector Methods
902
- /** List available connector providers */
903
- async listProviders() {
904
- const response = await this._makeRequest("GET", "/v1/connectors/providers");
905
- return this._unwrapResultsArray(response);
906
- }
907
- /** Start an OAuth connection flow */
908
- async connectProvider(provider, collectionId, config) {
909
- const body = { collection_id: collectionId };
910
- if (config !== void 0) body.config = config;
911
- const response = await this._makeRequest("POST", `/v1/connectors/${provider}/connect`, body);
912
- return this._unwrapResults(response);
913
- }
914
- /** List active connections for a collection */
915
- async listConnections(collectionId) {
916
- const response = await this._makeRequest("GET", "/v1/connectors", void 0, { collection_id: collectionId });
917
- return this._unwrapResultsArray(response);
918
- }
919
- /** Browse Google Drive folders for a connection */
920
- async listFolders(connectionId, parentId) {
921
- const params = {};
922
- if (parentId !== void 0) params.parent_id = parentId;
923
- const response = await this._makeRequest("GET", `/v1/connectors/${connectionId}/folders`, void 0, Object.keys(params).length ? params : void 0);
924
- return this._unwrapResultsArray(response);
925
- }
926
- /** List Slack channels for a connection */
927
- async listChannels(connectionId) {
928
- const response = await this._makeRequest("GET", `/v1/connectors/${connectionId}/channels`);
929
- return this._unwrapResultsArray(response);
930
- }
931
- /** Get a single connection by ID */
932
- async getConnection(connectionId) {
933
- const response = await this._makeRequest("GET", `/v1/connectors/${connectionId}`);
934
- return this._unwrapResults(response);
935
- }
936
- /** Manually trigger a sync for a connection */
937
- async triggerSync(connectionId) {
938
- const response = await this._makeRequest("POST", `/v1/connectors/${connectionId}/sync`);
939
- return this._unwrapResults(response);
940
- }
941
- /** Update connection config (e.g., folder/channel selection) */
942
- async updateConnectionConfig(connectionId, config, apply = "full_resync") {
943
- const response = await this._makeRequest("PATCH", `/v1/connectors/${connectionId}/config`, { config, apply });
944
- return this._unwrapResults(response);
945
- }
946
- /** Disconnect an external data source */
947
- async disconnect(connectionId, deleteMemories = false) {
948
- const params = {};
949
- if (deleteMemories) params.delete_memories = "true";
950
- const response = await this._makeRequest("DELETE", `/v1/connectors/${connectionId}`, void 0, Object.keys(params).length ? params : void 0);
951
- return this._unwrapResults(response);
952
- }
953
- // Health Check
954
- async healthCheck() {
955
- return this._makeRequest("GET", "/v1/health");
956
- }
957
- // Helpers
958
- _collectionFromDict(data) {
959
- let createdAt;
960
- if (data.created_at) {
961
- if (typeof data.created_at === "string") {
962
- createdAt = data.created_at;
963
- } else if (data.created_at instanceof Date) {
964
- createdAt = data.created_at.toISOString();
965
- }
966
- }
967
- let updatedAt;
968
- if (data.updated_at) {
969
- if (typeof data.updated_at === "string") {
970
- updatedAt = data.updated_at;
971
- } else if (data.updated_at instanceof Date) {
972
- updatedAt = data.updated_at.toISOString();
973
- }
974
- }
975
- const collectionId = String(data.id || "");
976
- const collectionName = String(data.name || "");
977
- const collectionDescription = typeof data.description === "string" ? data.description : void 0;
978
- const collectionOwnerId = data.owner_id ? String(data.owner_id) : void 0;
979
- const memoryCount = typeof data.memory_count === "number" ? data.memory_count : 0;
980
- const metadata = {
981
- graph_collection_status: String(data.graph_collection_status || ""),
982
- graph_sync_status: String(data.graph_sync_status || ""),
983
- user_count: typeof data.user_count === "number" ? data.user_count : 0
984
- };
985
- return {
986
- id: collectionId,
987
- name: collectionName,
988
- description: collectionDescription,
989
- metadata,
990
- created_at: createdAt,
991
- updated_at: updatedAt,
992
- memory_count: memoryCount,
993
- owner_id: collectionOwnerId
994
- };
995
- }
996
- _memoryResponseFromDict(data, collectionIds) {
997
- let createdAt;
998
- if (data.created_at) {
999
- if (typeof data.created_at === "string") {
1000
- createdAt = data.created_at;
1001
- } else if (data.created_at instanceof Date) {
1002
- createdAt = data.created_at.toISOString();
1003
- }
1004
- }
1005
- let updatedAt;
1006
- if (data.updated_at) {
1007
- if (typeof data.updated_at === "string") {
1008
- updatedAt = data.updated_at;
1009
- } else if (data.updated_at instanceof Date) {
1010
- updatedAt = data.updated_at.toISOString();
1011
- }
1012
- }
1013
- const engramId = String(data.id || "");
1014
- const content = typeof data.content === "string" ? data.content : typeof data.text === "string" ? data.text : void 0;
1015
- let chunks;
1016
- if (data.chunks && Array.isArray(data.chunks)) {
1017
- if (data.chunks.every((x) => typeof x === "string")) {
1018
- chunks = data.chunks.map((text) => ({
1019
- id: "",
1020
- content: text,
1021
- metadata: {}
1022
- }));
1023
- } else {
1024
- chunks = data.chunks.filter((item) => item && typeof item === "object" && ("text" in item || "content" in item)).map((item) => ({
1025
- id: String(item.id || ""),
1026
- content: String(item.text || item.content || ""),
1027
- metadata: typeof item.metadata === "object" && item.metadata !== null ? item.metadata : {},
1028
- role: typeof item.role === "string" ? item.role : void 0
1029
- }));
1030
- }
1031
- }
1032
- const metadata = { ...typeof data.metadata === "object" && data.metadata !== null ? data.metadata : {} };
1033
- if (data.engram_id) {
1034
- metadata.engram_id = data.engram_id;
1035
- }
1036
- let finalId = engramId;
1037
- if (data.engram_id && !engramId) {
1038
- finalId = String(data.engram_id);
1039
- }
1040
- if (data.document_metadata && typeof data.document_metadata === "object") {
1041
- Object.assign(metadata, data.document_metadata);
1042
- }
1043
- return {
1044
- id: finalId,
1045
- memory_id: finalId,
1046
- content,
1047
- chunks,
1048
- metadata,
1049
- collection_ids: Array.isArray(data.collection_ids) ? data.collection_ids : collectionIds,
1050
- created_at: createdAt,
1051
- updated_at: updatedAt
1052
- };
1053
- }
1054
- _searchResultFromDict(data) {
1055
- const content = typeof data.content === "string" ? data.content : typeof data.text === "string" ? data.text : "";
1056
- const resultId = String(data.id || data.chunk_id || "");
1057
- return {
1058
- id: String(resultId),
1059
- content: String(content),
1060
- score: typeof data.score === "number" ? data.score : Number(data.score || 0),
1061
- metadata: typeof data.metadata === "object" && data.metadata !== null ? data.metadata : {},
1062
- source: typeof data.source === "string" ? data.source : void 0
1063
- };
1064
- }
1065
- _searchResultFromGraphDict(data) {
1066
- const rid = data.id ? String(data.id) : "";
1067
- const resultTypeStr = typeof data.result_type === "string" ? data.result_type : "entity";
1068
- const rtype = GraphSearchResultType[resultTypeStr.toUpperCase()] || "entity" /* ENTITY */;
1069
- const content = typeof data.content === "object" && data.content !== null ? data.content : {};
1070
- const score = data.score !== void 0 ? Number(data.score) : 0;
1071
- const metadata = typeof data.metadata === "object" && data.metadata !== null ? data.metadata : {};
1072
- const chunkIds = Array.isArray(data.chunk_ids) ? data.chunk_ids : void 0;
1073
- let timestamp;
1074
- if (data.timestamp) {
1075
- if (typeof data.timestamp === "string") {
1076
- timestamp = data.timestamp;
1077
- } else if (data.timestamp instanceof Date) {
1078
- timestamp = data.timestamp.toISOString();
1079
- } else {
1080
- const parsed = new Date(String(data.timestamp));
1081
- if (!Number.isNaN(parsed.valueOf())) {
1082
- timestamp = parsed.toISOString();
1083
- }
1084
- }
1085
- }
1086
- const displayName = typeof data.display_name === "string" ? data.display_name : void 0;
1087
- const sourceRole = typeof data.source_role === "string" ? data.source_role : void 0;
1088
- const engramId = data.engram_id ? String(data.engram_id) : void 0;
1089
- const ownerId = data.owner_id ? String(data.owner_id) : void 0;
1090
- let entity;
1091
- let rel;
1092
- let comm;
1093
- if (rtype === "entity" /* ENTITY */) {
1094
- entity = {
1095
- id: content.id ? String(content.id) : void 0,
1096
- name: String(content.name || ""),
1097
- description: String(content.description || ""),
1098
- metadata: typeof content.metadata === "object" && content.metadata !== null ? content.metadata : {}
1099
- };
1100
- } else if (rtype === "relationship" /* RELATIONSHIP */) {
1101
- rel = {
1102
- id: content.id ? String(content.id) : void 0,
1103
- subject: String(content.subject || ""),
1104
- predicate: String(content.predicate || ""),
1105
- object: String(content.object || ""),
1106
- subject_id: content.subject_id ? String(content.subject_id) : void 0,
1107
- object_id: content.object_id ? String(content.object_id) : void 0,
1108
- description: typeof content.description === "string" ? content.description : void 0,
1109
- metadata: typeof content.metadata === "object" && content.metadata !== null ? content.metadata : {}
1110
- };
1111
- } else {
1112
- comm = {
1113
- id: content.id ? String(content.id) : void 0,
1114
- name: String(content.name || ""),
1115
- summary: String(content.summary || ""),
1116
- metadata: typeof content.metadata === "object" && content.metadata !== null ? content.metadata : {}
1117
- };
1118
- }
1119
- return {
1120
- id: rid,
1121
- score,
1122
- metadata,
1123
- source: "graph",
1124
- content: void 0,
1125
- graph_result_type: rtype,
1126
- graph_entity: entity,
1127
- graph_relationship: rel,
1128
- graph_community: comm,
1129
- chunk_ids: chunkIds,
1130
- timestamp,
1131
- display_name: displayName,
1132
- source_role: sourceRole,
1133
- engram_id: engramId,
1134
- owner_id: ownerId
1135
- };
1136
- }
1137
- async _sha256(message) {
1138
- const msgBuffer = new TextEncoder().encode(message);
1139
- let crypto;
1140
- if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.subtle) {
1141
- crypto = globalThis.crypto;
1142
- } else {
1143
- const nodeCrypto = await import('crypto');
1144
- crypto = nodeCrypto.webcrypto;
1145
- }
1146
- const hashBuffer = await crypto.subtle.digest("SHA-256", msgBuffer);
1147
- const hashArray = Array.from(new Uint8Array(hashBuffer));
1148
- const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
1149
- return hashHex;
1150
- }
1151
- _formDataFromObject(obj) {
1152
- const formData = new FormData();
1153
- Object.entries(obj).forEach(([key, value]) => {
1154
- formData.append(key, value);
1155
- });
1156
- return formData;
1157
- }
1158
- /**
1159
- * Convert and process multimodal content parts, auto-uploading large base64 files to S3.
1160
- *
1161
- * - Binary parts (`image`/`audio`/`document` with `data`) larger than 5MB are uploaded to S3 and converted to `s3_ref`.
1162
- */
1163
- async _processContentParts(contentParts) {
1164
- const processed = [];
1165
- for (const part of contentParts) {
1166
- if (part.type !== "text" && part.type !== "s3_ref" && "data" in part && part.data) {
1167
- const filePart = part;
1168
- const dataSize = Math.floor(String(filePart.data).length * 3 / 4);
1169
- if (dataSize > _Nebula.MAX_INLINE_SIZE) {
1170
- const filename = filePart.filename || `file.bin`;
1171
- const mediaType = filePart.media_type || "application/octet-stream";
1172
- const uploadInfo = await this.getUploadUrl({
1173
- filename,
1174
- content_type: mediaType,
1175
- file_size: dataSize
1176
- });
1177
- let bytes;
1178
- const atobFn = globalThis.atob;
1179
- if (typeof atobFn === "function") {
1180
- const binaryString = atobFn(String(filePart.data));
1181
- bytes = new Uint8Array(binaryString.length);
1182
- for (let i = 0; i < binaryString.length; i++) {
1183
- bytes[i] = binaryString.charCodeAt(i);
1184
- }
1185
- } else {
1186
- const { Buffer } = await import('buffer');
1187
- bytes = Uint8Array.from(Buffer.from(String(filePart.data), "base64"));
1188
- }
1189
- await fetch(uploadInfo.upload_url, {
1190
- method: "PUT",
1191
- body: bytes,
1192
- headers: { "Content-Type": mediaType }
1193
- });
1194
- processed.push({
1195
- type: "s3_ref",
1196
- s3_key: uploadInfo.s3_key,
1197
- media_type: mediaType,
1198
- filename
1199
- });
1200
- continue;
1201
- }
1202
- }
1203
- processed.push(part);
1204
- }
1205
- return processed;
1206
- }
1207
- /**
1208
- * Get a presigned URL for uploading large files to S3.
1209
- */
1210
- async getUploadUrl(options) {
1211
- const response = await this._makeRequest("POST", "/v1/memories/upload", void 0, {
1212
- filename: options.filename,
1213
- content_type: options.content_type,
1214
- file_size: options.file_size
1215
- });
1216
- if (response.results) {
1217
- return response.results;
1218
- }
1219
- return response;
1220
- }
1221
- // ------------------------------------------------------------------
1222
- // Device Memory
1223
- // ------------------------------------------------------------------
1224
- /**
1225
- * Export a collection's full graph state as a portable snapshot.
1226
- */
1227
- async exportSnapshot(collectionId) {
1228
- const response = await this._makeRequest("POST", "/v1/device-memory/snapshot/export", {
1229
- collection_id: collectionId
1230
- });
1231
- return response.results ?? response;
1232
- }
1233
- /**
1234
- * Import a snapshot into an ephemeral server-side collection.
1235
- * @returns The ephemeral collection ID.
1236
- */
1237
- async importSnapshot(snapshot) {
1238
- const response = await this._makeRequest("POST", "/v1/device-memory/snapshot/import", {
1239
- snapshot
1240
- });
1241
- const result = response.results ?? response;
1242
- return result.ephemeral_collection_id ?? "";
1243
- }
47
+ var NebulaNotFoundError = class extends NebulaAPIError {
48
+ name = "NebulaNotFoundError";
1244
49
  };
1245
- // Files larger than 5MB are automatically uploaded to S3
1246
- _Nebula.MAX_INLINE_SIZE = 5 * 1024 * 1024;
1247
- var Nebula = _Nebula;
1248
-
1249
- // src/content.ts
1250
- var MIME_TYPES = {
1251
- // Images
1252
- ".jpg": "image/jpeg",
1253
- ".jpeg": "image/jpeg",
1254
- ".png": "image/png",
1255
- ".gif": "image/gif",
1256
- ".webp": "image/webp",
1257
- ".heic": "image/heic",
1258
- ".bmp": "image/bmp",
1259
- ".tiff": "image/tiff",
1260
- // Audio
1261
- ".mp3": "audio/mpeg",
1262
- ".wav": "audio/wav",
1263
- ".m4a": "audio/m4a",
1264
- ".ogg": "audio/ogg",
1265
- ".flac": "audio/flac",
1266
- ".aac": "audio/aac",
1267
- ".webm": "audio/webm",
1268
- // Documents
1269
- ".pdf": "application/pdf",
1270
- ".doc": "application/msword",
1271
- ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
1272
- ".xls": "application/vnd.ms-excel",
1273
- ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
1274
- ".ppt": "application/vnd.ms-powerpoint",
1275
- ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
1276
- ".txt": "text/plain",
1277
- ".csv": "text/csv",
1278
- ".rtf": "application/rtf",
1279
- ".epub": "application/epub+zip"
50
+ var NebulaConflictError = class extends NebulaAPIError {
51
+ name = "NebulaConflictError";
1280
52
  };
1281
- var NebulaContent = class {
1282
- /**
1283
- * Create a FileContentPart from a file path.
1284
- * Automatically deduces media_type and the backend 'type' (image, audio, document).
1285
- */
1286
- static async fromFile(filePath, mediaType) {
1287
- let fs;
1288
- let path;
1289
- try {
1290
- fs = await import('fs/promises');
1291
- path = await import('path');
1292
- } catch (e) {
1293
- throw new Error("File system operations are only supported in Node.js environments.");
1294
- }
1295
- const absolutePath = path.resolve(filePath);
1296
- const fileName = path.basename(absolutePath);
1297
- const ext = path.extname(absolutePath).toLowerCase();
1298
- const detectedMime = mediaType || MIME_TYPES[ext] || "application/octet-stream";
1299
- const buffer = await fs.readFile(absolutePath);
1300
- const data = buffer.toString("base64");
1301
- return {
1302
- data,
1303
- media_type: detectedMime,
1304
- filename: fileName
1305
- };
1306
- }
53
+ var NebulaValidationError = class extends NebulaAPIError {
54
+ name = "NebulaValidationError";
1307
55
  };
56
+ var NebulaRateLimitError = class extends NebulaAPIError {
57
+ name = "NebulaRateLimitError";
58
+ retryAfter;
59
+ constructor(payload, retryAfter) {
60
+ super(payload);
61
+ this.retryAfter = retryAfter;
62
+ }
63
+ };
64
+ var NebulaServerError = class extends NebulaAPIError {
65
+ name = "NebulaServerError";
66
+ };
67
+ const STATUS_TO_CLASS = {
68
+ 400: NebulaBadRequestError,
69
+ 401: NebulaUnauthorizedError,
70
+ 403: NebulaForbiddenError,
71
+ 404: NebulaNotFoundError,
72
+ 409: NebulaConflictError,
73
+ 422: NebulaValidationError
74
+ };
75
+ function errorFromResponse(payload, retryAfter) {
76
+ if (payload.status === 429) return new NebulaRateLimitError(payload, retryAfter);
77
+ const cls = STATUS_TO_CLASS[payload.status];
78
+ if (cls) return new cls(payload);
79
+ if (payload.status >= 500) return new NebulaServerError(payload);
80
+ return new NebulaAPIError(payload);
81
+ }
82
+ //#endregion
83
+ //#region src/runtime/retry.ts
84
+ const DEFAULT_RETRY = {
85
+ maxRetries: 2,
86
+ baseMs: 250,
87
+ maxMs: 8e3
88
+ };
89
+ const RETRYABLE_STATUSES = new Set([
90
+ 408,
91
+ 429,
92
+ 502,
93
+ 503,
94
+ 504
95
+ ]);
96
+ function isRetryableStatus(status) {
97
+ return RETRYABLE_STATUSES.has(status);
98
+ }
99
+ function backoffMs(attempt, policy, retryAfterSec) {
100
+ if (retryAfterSec != null && Number.isFinite(retryAfterSec)) return Math.min(retryAfterSec * 1e3, policy.maxMs);
101
+ const exp = Math.min(policy.baseMs * 2 ** attempt, policy.maxMs);
102
+ return Math.floor(Math.random() * exp);
103
+ }
104
+ function sleep(ms, signal) {
105
+ if (ms <= 0) return Promise.resolve();
106
+ return new Promise((resolveSleep, reject) => {
107
+ const handle = setTimeout(resolveSleep, ms);
108
+ if (signal) {
109
+ const onAbort = () => {
110
+ clearTimeout(handle);
111
+ reject(signal.reason ?? /* @__PURE__ */ new Error("aborted"));
112
+ };
113
+ if (signal.aborted) onAbort();
114
+ else signal.addEventListener("abort", onAbort, { once: true });
115
+ }
116
+ });
117
+ }
118
+ //#endregion
119
+ //#region src/runtime/client.ts
120
+ const DEFAULT_BASE_URL = "https://api.zeroset.com";
121
+ const DEFAULT_TIMEOUT_MS = 6e4;
122
+ var NebulaCore = class {
123
+ baseUrl;
124
+ apiKey;
125
+ bearerToken;
126
+ defaultHeaders;
127
+ fetchImpl;
128
+ fetchOptions;
129
+ timeoutMs;
130
+ retry;
131
+ userAgent;
132
+ constructor(options = {}) {
133
+ this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
134
+ this.apiKey = options.apiKey;
135
+ this.bearerToken = options.bearerToken;
136
+ this.defaultHeaders = filterNullishHeaders(options.defaultHeaders);
137
+ this.fetchImpl = options.fetchImpl ?? globalThis.fetch.bind(globalThis);
138
+ this.fetchOptions = options.fetchOptions ?? {};
139
+ this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
140
+ this.retry = {
141
+ ...DEFAULT_RETRY,
142
+ ...options.retry ?? {}
143
+ };
144
+ this.userAgent = options.userAgent ?? "nebula-sdk-js/0.0.1";
145
+ }
146
+ buildUrl(path, pathParams, query) {
147
+ let resolved = path;
148
+ if (pathParams) for (const [k, v] of Object.entries(pathParams)) resolved = resolved.replace(`{${k}}`, encodeURIComponent(String(v)));
149
+ const url = new URL(this.baseUrl + resolved);
150
+ if (query) for (const [k, v] of Object.entries(query)) {
151
+ if (v === void 0 || v === null) continue;
152
+ if (Array.isArray(v)) for (const item of v) url.searchParams.append(k, String(item));
153
+ else url.searchParams.set(k, String(v));
154
+ }
155
+ return url.toString();
156
+ }
157
+ buildHeaders(perRequest, hasBody = false) {
158
+ const headers = new Headers(this.defaultHeaders);
159
+ headers.set("User-Agent", this.userAgent);
160
+ headers.set("Accept", "application/json");
161
+ if (hasBody) headers.set("Content-Type", "application/json");
162
+ if (this.apiKey) headers.set("X-API-Key", this.apiKey);
163
+ if (this.bearerToken) headers.set("Authorization", `Bearer ${this.bearerToken}`);
164
+ if (perRequest) for (const [k, v] of Object.entries(perRequest)) headers.set(k, v);
165
+ return headers;
166
+ }
167
+ async request(args) {
168
+ const url = this.buildUrl(args.path, args.pathParams, args.query);
169
+ const hasBody = args.body !== void 0 && args.body !== null;
170
+ const headers = this.buildHeaders(args.headers, hasBody);
171
+ const init = {
172
+ ...this.fetchOptions,
173
+ method: args.method,
174
+ headers,
175
+ body: hasBody ? JSON.stringify(args.body) : void 0
176
+ };
177
+ const maxAttempts = args.idempotent ?? false ? this.retry.maxRetries + 1 : 1;
178
+ let lastError;
179
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
180
+ const controller = new AbortController();
181
+ const timeoutHandle = setTimeout(() => controller.abort(/* @__PURE__ */ new Error("timeout")), this.timeoutMs);
182
+ const { signal: composedSignal, dispose: disposeAbort } = composeAbort(controller.signal, args.signal);
183
+ try {
184
+ const response = await this.fetchImpl(url, {
185
+ ...init,
186
+ signal: composedSignal
187
+ });
188
+ if (response.ok) {
189
+ if (response.status === 204) return void 0;
190
+ return await response.json();
191
+ }
192
+ const text = await safeReadText(response);
193
+ const parsed = safeParseJSON(text);
194
+ const retryAfter = parseRetryAfter(response.headers.get("Retry-After"));
195
+ const err = errorFromResponse({
196
+ status: response.status,
197
+ requestId: response.headers.get("X-Request-Id") ?? void 0,
198
+ body: parsed ?? text
199
+ }, retryAfter);
200
+ if (isRetryableStatus(response.status) && attempt + 1 < maxAttempts) {
201
+ await sleep(backoffMs(attempt, this.retry, retryAfter), args.signal);
202
+ lastError = err;
203
+ continue;
204
+ }
205
+ throw err;
206
+ } catch (rawError) {
207
+ if (rawError instanceof Error && rawError.name === "AbortError") {
208
+ if (args.signal?.aborted) throw rawError;
209
+ throw new NebulaTimeoutError(`Request timed out after ${this.timeoutMs}ms`, { cause: rawError });
210
+ }
211
+ if (rawError instanceof NebulaAPIError || rawError instanceof NebulaConnectionError) throw rawError;
212
+ if (attempt + 1 < maxAttempts) {
213
+ await sleep(backoffMs(attempt, this.retry), args.signal);
214
+ lastError = rawError;
215
+ continue;
216
+ }
217
+ if (rawError instanceof Error) throw new NebulaConnectionError(rawError.message, { cause: rawError });
218
+ throw rawError;
219
+ } finally {
220
+ clearTimeout(timeoutHandle);
221
+ disposeAbort();
222
+ }
223
+ }
224
+ throw lastError ?? new NebulaConnectionError("retry budget exhausted");
225
+ }
226
+ };
227
+ function composeAbort(timeoutSignal, userSignal) {
228
+ if (!userSignal) return {
229
+ signal: timeoutSignal,
230
+ dispose: () => {}
231
+ };
232
+ const controller = new AbortController();
233
+ const onTimeoutAbort = () => controller.abort(timeoutSignal.reason ?? /* @__PURE__ */ new Error("aborted"));
234
+ const onUserAbort = () => controller.abort(userSignal.reason ?? /* @__PURE__ */ new Error("aborted"));
235
+ if (timeoutSignal.aborted) controller.abort(timeoutSignal.reason);
236
+ if (userSignal.aborted) controller.abort(userSignal.reason);
237
+ timeoutSignal.addEventListener("abort", onTimeoutAbort, { once: true });
238
+ userSignal.addEventListener("abort", onUserAbort, { once: true });
239
+ const dispose = () => {
240
+ timeoutSignal.removeEventListener("abort", onTimeoutAbort);
241
+ userSignal.removeEventListener("abort", onUserAbort);
242
+ };
243
+ return {
244
+ signal: controller.signal,
245
+ dispose
246
+ };
247
+ }
248
+ async function safeReadText(response) {
249
+ try {
250
+ return await response.text();
251
+ } catch {
252
+ return "";
253
+ }
254
+ }
255
+ function safeParseJSON(text) {
256
+ if (!text) return void 0;
257
+ try {
258
+ return JSON.parse(text);
259
+ } catch {
260
+ return;
261
+ }
262
+ }
263
+ function filterNullishHeaders(headers) {
264
+ if (!headers) return {};
265
+ const out = {};
266
+ for (const [k, v] of Object.entries(headers)) if (v !== null && v !== void 0) out[k] = v;
267
+ return out;
268
+ }
269
+ function parseRetryAfter(header) {
270
+ if (!header) return void 0;
271
+ const asNumber = Number.parseFloat(header);
272
+ if (Number.isFinite(asNumber)) return asNumber;
273
+ const asDate = Date.parse(header);
274
+ if (Number.isFinite(asDate)) return Math.max(0, (asDate - Date.now()) / 1e3);
275
+ }
276
+ //#endregion
277
+ //#region src/resources/client.ts
278
+ var ClientResource = class {
279
+ core;
280
+ constructor(core) {
281
+ this.core = core;
282
+ }
283
+ /**
284
+ *
285
+ * Health probe
286
+ *
287
+ * Lightweight liveness probe. Returns a 200 with a fixed message when the API process is up. Does not verify downstream dependencies (database, storage, workers) — use the internal status endpoints for those.
288
+ * @operationId client.health
289
+ * @endpoint GET /v1/health
290
+ */
291
+ async health(options) {
292
+ return this.core.request({
293
+ method: "GET",
294
+ path: "/v1/health",
295
+ pathParams: {},
296
+ query: void 0,
297
+ idempotent: true,
298
+ signal: options?.signal
299
+ });
300
+ }
301
+ };
302
+ //#endregion
303
+ //#region src/resources/collections.ts
304
+ var CollectionsResource = class {
305
+ core;
306
+ constructor(core) {
307
+ this.core = core;
308
+ }
309
+ /**
310
+ *
311
+ * Create a new collection
312
+ *
313
+ * Create a new collection and automatically add the creating user
314
+ * to it.
315
+ *
316
+ * This endpoint allows authenticated users to create a new collection
317
+ * with a specified name and optional description. The user creating
318
+ * the collection is automatically added as a member.
319
+ * @operationId collections.create
320
+ * @endpoint POST /v1/collections
321
+ */
322
+ async create(params, options) {
323
+ return this.core.request({
324
+ method: "POST",
325
+ path: "/v1/collections",
326
+ pathParams: {},
327
+ query: void 0,
328
+ body: params.body,
329
+ idempotent: false,
330
+ signal: options?.signal
331
+ });
332
+ }
333
+ /**
334
+ *
335
+ * Delete collection
336
+ *
337
+ * Delete an existing collection.
338
+ *
339
+ * This endpoint allows deletion of a collection identified by its
340
+ * UUID. The user must have appropriate permissions to delete the
341
+ * collection. Deleting a collection removes all associations but does
342
+ * not delete the engrams within it.
343
+ * @operationId collections.delete
344
+ * @endpoint DELETE /v1/collections/{id}
345
+ */
346
+ async delete(id, options) {
347
+ return this.core.request({
348
+ method: "DELETE",
349
+ path: "/v1/collections/{id}",
350
+ pathParams: { id },
351
+ query: void 0,
352
+ idempotent: true,
353
+ signal: options?.signal
354
+ });
355
+ }
356
+ /**
357
+ *
358
+ * List collections
359
+ *
360
+ * Returns a cursor-paginated list of collections the authenticated
361
+ * user has access to.
362
+ *
363
+ * Results can be filtered by providing specific collection IDs.
364
+ * Regular users will only see collections they own or have access to.
365
+ * Superusers can see all collections.
366
+ *
367
+ * The collections are returned in order of last modification, with
368
+ * most recent first.
369
+ * @operationId collections.list
370
+ * @endpoint GET /v1/collections
371
+ */
372
+ async list(params = {}, options) {
373
+ return this.core.request({
374
+ method: "GET",
375
+ path: "/v1/collections",
376
+ pathParams: {},
377
+ query: {
378
+ ids: params.ids,
379
+ name: params.name,
380
+ cursor: params.cursor,
381
+ limit: params.limit,
382
+ owner_only: params.ownerOnly,
383
+ workspace_id: params.workspaceId
384
+ },
385
+ idempotent: true,
386
+ signal: options?.signal
387
+ });
388
+ }
389
+ /**
390
+ *
391
+ * Get collection details
392
+ *
393
+ * Get details of a specific collection.
394
+ *
395
+ * This endpoint retrieves detailed information about a single
396
+ * collection identified by its UUID. The user must have access to the
397
+ * collection to view its details.
398
+ * @operationId collections.retrieve
399
+ * @endpoint GET /v1/collections/{id}
400
+ */
401
+ async retrieve(id, options) {
402
+ return this.core.request({
403
+ method: "GET",
404
+ path: "/v1/collections/{id}",
405
+ pathParams: { id },
406
+ query: void 0,
407
+ idempotent: true,
408
+ signal: options?.signal
409
+ });
410
+ }
411
+ /**
412
+ *
413
+ * Get a collection by name
414
+ *
415
+ * Retrieve a collection by its (owner_id, name) combination.
416
+ *
417
+ * The authenticated user can only fetch collections they own, or, if
418
+ * superuser, from anyone.
419
+ * @operationId collections.retrieveByName
420
+ * @endpoint GET /v1/collections/name/{collection_name}
421
+ */
422
+ async retrieveByName(params, options) {
423
+ return this.core.request({
424
+ method: "GET",
425
+ path: "/v1/collections/name/{collection_name}",
426
+ pathParams: { collection_name: params.collectionName },
427
+ query: { owner_id: params.ownerId },
428
+ idempotent: true,
429
+ signal: options?.signal
430
+ });
431
+ }
432
+ /**
433
+ *
434
+ * Update collection
435
+ *
436
+ * Update an existing collection's configuration.
437
+ *
438
+ * This endpoint allows updating the name, description, and access settings of an
439
+ * existing collection. The user must have appropriate permissions to
440
+ * modify the collection.
441
+ * @operationId collections.update
442
+ * @endpoint POST /v1/collections/{id}
443
+ */
444
+ async update(params, options) {
445
+ return this.core.request({
446
+ method: "POST",
447
+ path: "/v1/collections/{id}",
448
+ pathParams: { id: params.id },
449
+ query: void 0,
450
+ body: params.body,
451
+ idempotent: false,
452
+ signal: options?.signal
453
+ });
454
+ }
455
+ };
456
+ //#endregion
457
+ //#region src/resources/connectors.ts
458
+ var ConnectorsResource = class {
459
+ core;
460
+ constructor(core) {
461
+ this.core = core;
462
+ }
463
+ /**
464
+ *
465
+ * Start OAuth connection flow
466
+ *
467
+ * Start the OAuth connection flow for the given external provider. Returns the authorization URL the user should visit to grant Nebula access. After consent the provider redirects back to Nebula and the connection becomes active.
468
+ * @operationId connectors.connect
469
+ * @endpoint POST /v1/connectors/{provider}/connect
470
+ */
471
+ async connect(params, options) {
472
+ return this.core.request({
473
+ method: "POST",
474
+ path: "/v1/connectors/{provider}/connect",
475
+ pathParams: { provider: params.provider },
476
+ query: void 0,
477
+ body: params.body,
478
+ idempotent: false,
479
+ signal: options?.signal
480
+ });
481
+ }
482
+ /**
483
+ *
484
+ * Disconnect an external data source
485
+ *
486
+ * Disconnect the named connection, revoking the stored OAuth credentials and stopping future syncs. Optionally pass `delete_memories=true` to also remove every memory this connection had ingested.
487
+ * @operationId connectors.disconnect
488
+ * @endpoint DELETE /v1/connectors/{connection_id}
489
+ */
490
+ async disconnect(params, options) {
491
+ return this.core.request({
492
+ method: "DELETE",
493
+ path: "/v1/connectors/{connection_id}",
494
+ pathParams: { connection_id: params.connectionId },
495
+ query: { delete_memories: params.deleteMemories },
496
+ idempotent: true,
497
+ signal: options?.signal
498
+ });
499
+ }
500
+ /**
501
+ *
502
+ * List active connections for a collection
503
+ *
504
+ * Return every connector connection associated with the given collection, with encrypted credentials redacted. Useful for showing the user which third-party data sources are wired up to a collection.
505
+ * @operationId connectors.list
506
+ * @endpoint GET /v1/connectors
507
+ */
508
+ async list(params, options) {
509
+ return this.core.request({
510
+ method: "GET",
511
+ path: "/v1/connectors",
512
+ pathParams: {},
513
+ query: { collection_id: params.collectionId },
514
+ idempotent: true,
515
+ signal: options?.signal
516
+ });
517
+ }
518
+ /**
519
+ *
520
+ * List available connector providers
521
+ *
522
+ * Return the set of connector provider identifiers (e.g. `google_drive`, `slack`) that this Nebula instance is configured to expose. Pass one of these to `POST /connectors/{provider}/connect` to start an OAuth flow.
523
+ * @operationId connectors.listProviders
524
+ * @endpoint GET /v1/connectors/providers
525
+ */
526
+ async listProviders(options) {
527
+ return this.core.request({
528
+ method: "GET",
529
+ path: "/v1/connectors/providers",
530
+ pathParams: {},
531
+ query: void 0,
532
+ idempotent: true,
533
+ signal: options?.signal
534
+ });
535
+ }
536
+ /**
537
+ *
538
+ * Get a single connection by ID
539
+ *
540
+ * Fetch a single connector connection by its UUID. Returns the connection metadata plus whether the underlying subscription is active. Encrypted credentials are never returned to clients.
541
+ * @operationId connectors.retrieve
542
+ * @endpoint GET /v1/connectors/{connection_id}
543
+ */
544
+ async retrieve(connectionId, options) {
545
+ return this.core.request({
546
+ method: "GET",
547
+ path: "/v1/connectors/{connection_id}",
548
+ pathParams: { connection_id: connectionId },
549
+ query: void 0,
550
+ idempotent: true,
551
+ signal: options?.signal
552
+ });
553
+ }
554
+ /**
555
+ *
556
+ * Manually trigger a sync
557
+ *
558
+ * Schedule an immediate sync for an active connection, bypassing the normal cadence. Returns 409 if a sync is already in progress and 400 if the connection isn't in the `active` state.
559
+ * @operationId connectors.sync
560
+ * @endpoint POST /v1/connectors/{connection_id}/sync
561
+ */
562
+ async sync(connectionId, options) {
563
+ return this.core.request({
564
+ method: "POST",
565
+ path: "/v1/connectors/{connection_id}/sync",
566
+ pathParams: { connection_id: connectionId },
567
+ query: void 0,
568
+ idempotent: true,
569
+ signal: options?.signal
570
+ });
571
+ }
572
+ };
573
+ //#endregion
574
+ //#region src/resources/memories.ts
575
+ var MemoriesResource = class {
576
+ core;
577
+ constructor(core) {
578
+ this.core = core;
579
+ }
580
+ /**
581
+ *
582
+ * Append content to an engram
583
+ *
584
+ * Append content to an existing engram.
585
+ *
586
+ * **For conversation engrams:**
587
+ * - Provide `messages` array with content, role, and optional metadata
588
+ * - Works like `/conversations/{id}/messages` endpoint
589
+ *
590
+ * **For document engrams:**
591
+ * - Provide either `raw_text` or `chunks` to append additional content
592
+ * - Content will be processed and added to the engram
593
+ * @operationId memories.append
594
+ * @endpoint POST /v1/memories/{id}/append
595
+ */
596
+ async append(params, options) {
597
+ return this.core.request({
598
+ method: "POST",
599
+ path: "/v1/memories/{id}/append",
600
+ pathParams: { id: params.id },
601
+ query: void 0,
602
+ body: params.body,
603
+ idempotent: false,
604
+ signal: options?.signal
605
+ });
606
+ }
607
+ /**
608
+ *
609
+ * Create a new memory (conversation or document)
610
+ *
611
+ * Create a new memory (conversation or document) using clean JSON body.
612
+ *
613
+ * - Use `collection_id` (UUID)
614
+ * - `kind` is optional and inferred from payload shape:
615
+ * - If `messages` present -> conversation
616
+ * - Otherwise -> document
617
+ * - For conversations: provide `messages` array
618
+ * - For documents: provide `raw_text` or `chunks`
619
+ * - Use `snapshot` for device-memory mode (mutually exclusive with collection_id)
620
+ * @operationId memories.create
621
+ * @endpoint POST /v1/memories
622
+ */
623
+ async create(params, options) {
624
+ return this.core.request({
625
+ method: "POST",
626
+ path: "/v1/memories",
627
+ pathParams: {},
628
+ query: void 0,
629
+ body: params.body,
630
+ idempotent: false,
631
+ signal: options?.signal
632
+ });
633
+ }
634
+ /**
635
+ *
636
+ * Get presigned URL for large file upload
637
+ *
638
+ * Get a presigned URL for uploading large files directly to S3.
639
+ *
640
+ * Use this for files larger than 5MB that cannot be sent inline as base64.
641
+ * After uploading, reference the file in memory creation using S3FileReference.
642
+ *
643
+ * Args:
644
+ * filename: Original filename (e.g., "image.jpg")
645
+ * content_type: MIME type (e.g., "image/jpeg", "application/pdf")
646
+ * file_size: Expected file size in bytes (max 100MB)
647
+ *
648
+ * Returns:
649
+ * dict with:
650
+ * - upload_url: Presigned URL for PUT request (expires in 1 hour)
651
+ * - upload_headers: Headers that must be sent with the presigned PUT request
652
+ * - s3_key: The S3 key to reference in memory creation
653
+ * - bucket: S3 bucket name
654
+ * - expires_in: Seconds until URL expires
655
+ * - max_size: Maximum allowed file size
656
+ * @operationId memories.createUpload
657
+ * @endpoint POST /v1/memories/upload
658
+ */
659
+ async createUpload(params, options) {
660
+ return this.core.request({
661
+ method: "POST",
662
+ path: "/v1/memories/upload",
663
+ pathParams: {},
664
+ query: {
665
+ filename: params.filename,
666
+ content_type: params.contentType,
667
+ file_size: params.fileSize
668
+ },
669
+ idempotent: false,
670
+ signal: options?.signal
671
+ });
672
+ }
673
+ /**
674
+ *
675
+ * Delete an engram
676
+ *
677
+ * Delete a specific engram with graph awareness. All chunks corresponding to the
678
+ * engram are deleted, and graph components (entities/relationships) are updated
679
+ * or deleted based on remaining chunk references from other engrams.
680
+ *
681
+ * This method now properly handles graph components and maintains graph integrity
682
+ * for search operations.
683
+ * @operationId memories.delete
684
+ * @endpoint DELETE /v1/memories/{id}
685
+ */
686
+ async delete(id, options) {
687
+ return this.core.request({
688
+ method: "DELETE",
689
+ path: "/v1/memories/{id}",
690
+ pathParams: { id },
691
+ query: void 0,
692
+ idempotent: false,
693
+ signal: options?.signal
694
+ });
695
+ }
696
+ /**
697
+ *
698
+ * Delete one or more engrams
699
+ *
700
+ * Delete one or more engrams.
701
+ *
702
+ * This endpoint efficiently handles both single and batch deletions.
703
+ * When multiple IDs are provided, it uses optimized batch operations.
704
+ *
705
+ * Args:
706
+ * ids: Either a single UUID or a list of UUIDs to delete
707
+ *
708
+ * Returns:
709
+ * For single deletion: boolean success response
710
+ * For batch deletion: detailed results with successful and failed deletions
711
+ * @operationId memories.deleteMany
712
+ * @endpoint POST /v1/memories/delete
713
+ */
714
+ async deleteMany(params, options) {
715
+ return this.core.request({
716
+ method: "POST",
717
+ path: "/v1/memories/delete",
718
+ pathParams: {},
719
+ query: void 0,
720
+ body: params.body,
721
+ idempotent: false,
722
+ signal: options?.signal
723
+ });
724
+ }
725
+ /**
726
+ *
727
+ * Delete a previously uploaded S3 file
728
+ *
729
+ * Delete a file from S3 that was uploaded via a presigned URL.
730
+ * Verifies the caller owns the file via S3 object metadata.
731
+ * @operationId memories.deleteUpload
732
+ * @endpoint DELETE /v1/memories/upload
733
+ */
734
+ async deleteUpload(params, options) {
735
+ return this.core.request({
736
+ method: "DELETE",
737
+ path: "/v1/memories/upload",
738
+ pathParams: {},
739
+ query: { s3_key: params.s3Key },
740
+ idempotent: true,
741
+ signal: options?.signal
742
+ });
743
+ }
744
+ /**
745
+ *
746
+ * List engrams
747
+ *
748
+ * Returns a cursor-paginated list of engrams the authenticated user
749
+ * has access to.
750
+ *
751
+ * Results can be filtered by providing specific engram IDs or collection IDs.
752
+ * Regular users will only see engrams they own or have access to through
753
+ * collections. Superusers can see all engrams.
754
+ *
755
+ * The engrams are returned in order of creation time, most recent
756
+ * first. The response includes the engram's text field if available.
757
+ * @operationId memories.list
758
+ * @endpoint GET /v1/memories
759
+ */
760
+ async list(params = {}, options) {
761
+ return this.core.request({
762
+ method: "GET",
763
+ path: "/v1/memories",
764
+ pathParams: {},
765
+ query: {
766
+ ids: params.ids,
767
+ cursor: params.cursor,
768
+ limit: params.limit,
769
+ chunks_limit: params.chunksLimit,
770
+ owner_only: params.ownerOnly,
771
+ collection_ids: params.collectionIds,
772
+ metadata_filters: params.metadataFilters,
773
+ min_applied_wal_seq: params.minAppliedWalSeq
774
+ },
775
+ idempotent: true,
776
+ signal: options?.signal
777
+ });
778
+ }
779
+ /**
780
+ *
781
+ * Recall workflow patterns by intent
782
+ *
783
+ * Workflow-pattern recall over 5 intents.
784
+ *
785
+ * * ``cursor`` -- match the caller's anchor against pattern
786
+ * canonical states, return ranked patterns + position.
787
+ * * ``predict`` -- like cursor but include the predicted next
788
+ * trace from each pattern's canonical instance.
789
+ * * ``resume`` -- like cursor, biased toward longer patterns.
790
+ * * ``evidence`` -- expand a specific pattern via
791
+ * ``hydrate_pattern``.
792
+ * * ``bootstrap`` -- top-K patterns by confidence with no anchor.
793
+ * @operationId memories.recallWorkflow
794
+ * @endpoint POST /v1/memories/workflow/recall
795
+ */
796
+ async recallWorkflow(params, options) {
797
+ return this.core.request({
798
+ method: "POST",
799
+ path: "/v1/memories/workflow/recall",
800
+ pathParams: {},
801
+ query: void 0,
802
+ body: params.body,
803
+ idempotent: false,
804
+ signal: options?.signal
805
+ });
806
+ }
807
+ /**
808
+ *
809
+ * Retrieve an engram
810
+ *
811
+ * Retrieves detailed information about a specific engram by its
812
+ * ID.
813
+ *
814
+ * This endpoint returns the engram's metadata, status, and system information. It does not
815
+ * return the engram's content - use the `/engrams/{id}/download` endpoint for that.
816
+ *
817
+ * Users can only retrieve engrams they own or have access to through collections.
818
+ * Superusers can retrieve any engram.
819
+ * @operationId memories.retrieve
820
+ * @endpoint GET /v1/memories/{id}
821
+ */
822
+ async retrieve(id, options) {
823
+ return this.core.request({
824
+ method: "GET",
825
+ path: "/v1/memories/{id}",
826
+ pathParams: { id },
827
+ query: void 0,
828
+ idempotent: true,
829
+ signal: options?.signal
830
+ });
831
+ }
832
+ /**
833
+ *
834
+ * Search memories
835
+ *
836
+ * Perform a search query across your memories.
837
+ *
838
+ * **Standard mode** (collection_ids or readable-scope search): returns hierarchical MemoryRecall
839
+ * with semantics, episodes, procedures, and sources.
840
+ *
841
+ * **Snapshot mode** (snapshot field): returns graph-search results with
842
+ * {entities, relationships} from stateless in-memory traversal.
843
+ * @operationId memories.search
844
+ * @endpoint POST /v1/memories/search
845
+ */
846
+ async search(params, options) {
847
+ return this.core.request({
848
+ method: "POST",
849
+ path: "/v1/memories/search",
850
+ pathParams: {},
851
+ query: void 0,
852
+ body: params.body,
853
+ idempotent: false,
854
+ signal: options?.signal
855
+ });
856
+ }
857
+ /**
858
+ *
859
+ * Update a memory
860
+ *
861
+ * Update memory-level properties including name, metadata, and collection associations.
862
+ *
863
+ * This endpoint allows updating properties of an entire memory (document or conversation)
864
+ * without modifying its content:
865
+ * - **name**: Updates the authoritative engram title
866
+ * - **metadata**: Can replace or merge with existing metadata
867
+ * - **collection_ids**: Updates authoritative engram collection associations
868
+ *
869
+ * Users can only update memories they own or have access to through collections.
870
+ * At least one collection association must be maintained.
871
+ *
872
+ * If collection_id is provided and the engram is shared across collections, a copy-on-write
873
+ * will be performed to create a collection-specific copy before modification.
874
+ * @operationId memories.update
875
+ * @endpoint PATCH /v1/memories/{id}
876
+ */
877
+ async update(params, options) {
878
+ return this.core.request({
879
+ method: "PATCH",
880
+ path: "/v1/memories/{id}",
881
+ pathParams: { id: params.id },
882
+ query: { collection_id: params.collectionId },
883
+ body: params.body,
884
+ idempotent: false,
885
+ signal: options?.signal
886
+ });
887
+ }
888
+ };
889
+ //#endregion
890
+ //#region src/resources/snapshots.ts
891
+ var SnapshotsResource = class {
892
+ core;
893
+ constructor(core) {
894
+ this.core = core;
895
+ }
896
+ /**
897
+ *
898
+ * Export a collection snapshot
899
+ *
900
+ * Export a collection's full graph state as a
901
+ * portable SnapshotEnvelope.
902
+ * @operationId snapshots.export
903
+ * @endpoint POST /v1/device-memory/snapshot/export
904
+ */
905
+ async export(params, options) {
906
+ return this.core.request({
907
+ method: "POST",
908
+ path: "/v1/device-memory/snapshot/export",
909
+ pathParams: {},
910
+ query: void 0,
911
+ body: params.body,
912
+ idempotent: true,
913
+ signal: options?.signal
914
+ });
915
+ }
916
+ /**
917
+ *
918
+ * Import a snapshot into an ephemeral collection
919
+ *
920
+ * Import a SnapshotEnvelope into an ephemeral
921
+ * collection. Returns the ephemeral collection UUID.
922
+ * @operationId snapshots.import
923
+ * @endpoint POST /v1/device-memory/snapshot/import
924
+ */
925
+ async import(params, options) {
926
+ return this.core.request({
927
+ method: "POST",
928
+ path: "/v1/device-memory/snapshot/import",
929
+ pathParams: {},
930
+ query: void 0,
931
+ body: params.body,
932
+ idempotent: false,
933
+ signal: options?.signal
934
+ });
935
+ }
936
+ };
937
+ //#endregion
938
+ //#region src/client.ts
939
+ var NebulaClient = class {
940
+ core;
941
+ client;
942
+ collections;
943
+ connectors;
944
+ memories;
945
+ snapshots;
946
+ constructor(options = {}) {
947
+ this.core = new NebulaCore(options);
948
+ this.client = new ClientResource(this.core);
949
+ this.collections = new CollectionsResource(this.core);
950
+ this.connectors = new ConnectorsResource(this.core);
951
+ this.memories = new MemoriesResource(this.core);
952
+ this.snapshots = new SnapshotsResource(this.core);
953
+ }
954
+ };
955
+ //#endregion
956
+ //#region src/lib/_dx_generated.ts
957
+ /** Strip a `results` envelope at runtime; matches `Unwrapped<>`. */
958
+ function unwrap$1(p) {
959
+ return p.then((r) => {
960
+ if (r !== null && typeof r === "object" && "results" in r) return r.results;
961
+ return r;
962
+ });
963
+ }
964
+ /**
965
+ * Generated DX layer: simple unwrap/passthrough convenience methods.
966
+ * Signatures are derived from the underlying resource methods so
967
+ * callers see the same arg names + types in IDE hover.
968
+ */
969
+ var NebulaDX = class extends NebulaClient {
970
+ /** Retrieve a single memory by id and return just the data. (generated from dx-extensions.yaml). */
971
+ async getMemory(...args) {
972
+ return unwrap$1(this.memories.retrieve(...args));
973
+ }
974
+ /** Update a memory by id; returns the updated record. (generated from dx-extensions.yaml). */
975
+ async updateMemory(...args) {
976
+ return unwrap$1(this.memories.update(...args));
977
+ }
978
+ /** Liveness probe with the wire envelope unwrapped. (generated from dx-extensions.yaml). */
979
+ async healthCheck(...args) {
980
+ return unwrap$1(this.client.health(...args));
981
+ }
982
+ /** unwrap → collections.create (generated from dx-extensions.yaml). */
983
+ async createCollection(...args) {
984
+ return unwrap$1(this.collections.create(...args));
985
+ }
986
+ /** unwrap → collections.retrieve (generated from dx-extensions.yaml). */
987
+ async getCollection(...args) {
988
+ return unwrap$1(this.collections.retrieve(...args));
989
+ }
990
+ /** unwrap → collections.retrieveByName (generated from dx-extensions.yaml). */
991
+ async getCollectionByName(...args) {
992
+ return unwrap$1(this.collections.retrieveByName(...args));
993
+ }
994
+ /** unwrap → collections.list (generated from dx-extensions.yaml). */
995
+ async listCollections(...args) {
996
+ return unwrap$1(this.collections.list(...args));
997
+ }
998
+ /** unwrap → collections.update (generated from dx-extensions.yaml). */
999
+ async updateCollection(...args) {
1000
+ return unwrap$1(this.collections.update(...args));
1001
+ }
1002
+ /** unwrap → connectors.listProviders (generated from dx-extensions.yaml). */
1003
+ async listProviders(...args) {
1004
+ return unwrap$1(this.connectors.listProviders(...args));
1005
+ }
1006
+ /** unwrap → connectors.retrieve (generated from dx-extensions.yaml). */
1007
+ async getConnection(...args) {
1008
+ return unwrap$1(this.connectors.retrieve(...args));
1009
+ }
1010
+ /** unwrap → connectors.sync (generated from dx-extensions.yaml). */
1011
+ async triggerSync(...args) {
1012
+ return unwrap$1(this.connectors.sync(...args));
1013
+ }
1014
+ /** unwrap → memories.createUpload (generated from dx-extensions.yaml). */
1015
+ async getUploadUrl(...args) {
1016
+ return unwrap$1(this.memories.createUpload(...args));
1017
+ }
1018
+ /** unwrap → snapshots.export (generated from dx-extensions.yaml). */
1019
+ async exportSnapshot(...args) {
1020
+ return unwrap$1(this.snapshots.export(...args));
1021
+ }
1022
+ /** unwrap → snapshots.import (generated from dx-extensions.yaml). */
1023
+ async importSnapshot(...args) {
1024
+ return unwrap$1(this.snapshots.import(...args));
1025
+ }
1026
+ };
1027
+ //#endregion
1028
+ //#region src/lib/dx.ts
1029
+ var Nebula = class extends NebulaDX {
1030
+ constructor(options = {}) {
1031
+ super(normalizeAuthOptions(normalizeClientOptions(options)));
1032
+ }
1033
+ /**
1034
+ * Polymorphic memory creator: dispatches to memories.create or memories.append
1035
+ * based on whether `memory_id` is set on the input. Returns the new memory's
1036
+ * id (string), or — when `snapshot` is set — the updated snapshot envelope.
1037
+ */
1038
+ async storeMemory(memory, options) {
1039
+ if ("memory_id" in memory && memory.memory_id != null) {
1040
+ const memoryID = memory.memory_id;
1041
+ await this.memories.append({
1042
+ id: memoryID,
1043
+ body: toMemoryAppendParams(memory)
1044
+ }, options);
1045
+ return memoryID;
1046
+ }
1047
+ const result = unwrapResults(await this.memories.create({ body: toMemoryCreateParams(memory) }, options));
1048
+ if (isSnapshotResult(result)) return result.snapshot ?? result;
1049
+ return extractID(result);
1050
+ }
1051
+ /**
1052
+ * Bulk parallel version of storeMemory with a concurrency cap.
1053
+ *
1054
+ * Default 8 concurrent in-flight requests matches the Python DX's
1055
+ * `asyncio.Semaphore(max_concurrency)` pattern. Use `maxConcurrency: 1`
1056
+ * for strictly serial submission; higher values risk overwhelming the
1057
+ * server when memories[] is large.
1058
+ */
1059
+ async storeMemories(memories, options) {
1060
+ const cap = Math.max(1, options?.maxConcurrency ?? 8);
1061
+ const signal = options?.signal;
1062
+ const results = new Array(memories.length);
1063
+ let nextIndex = 0;
1064
+ const worker = async () => {
1065
+ while (true) {
1066
+ const i = nextIndex++;
1067
+ if (i >= memories.length) return;
1068
+ results[i] = await this.storeMemory(memories[i], { signal });
1069
+ }
1070
+ };
1071
+ await Promise.all(Array.from({ length: Math.min(cap, memories.length) }, () => worker()));
1072
+ return results;
1073
+ }
1074
+ /**
1075
+ * List memories scoped to one or more collection ids (string | string[])
1076
+ * or a full MemoryListParams object.
1077
+ */
1078
+ async listMemories(query, options) {
1079
+ const normalized = typeof query === "string" || Array.isArray(query) ? { collectionIds: arrayify(query) } : query;
1080
+ return unwrap(this.memories.list(normalized, options));
1081
+ }
1082
+ /**
1083
+ * Memory search shortcut: unwraps `results`. (Affinity-header injection
1084
+ * is a planned enhancement; currently delegates straight to the resource.)
1085
+ */
1086
+ async search(body, options) {
1087
+ return unwrap(this.memories.search({ body }, options));
1088
+ }
1089
+ /**
1090
+ * deleteCollection coerces the wire {success: bool} envelope into a Python-
1091
+ * style boolean return.
1092
+ */
1093
+ async deleteCollection(id, options) {
1094
+ const result = unwrapResults(await this.collections.delete(id, options));
1095
+ return Boolean(result.success);
1096
+ }
1097
+ createCluster = this.createCollection;
1098
+ getCluster = this.getCollection;
1099
+ getClusterByName = this.getCollectionByName;
1100
+ listClusters = this.listCollections;
1101
+ updateCluster = this.updateCollection;
1102
+ deleteCluster = this.deleteCollection;
1103
+ /**
1104
+ * Positional `connectProvider(provider, collectionID, config?)` — wraps the
1105
+ * generated `connectors.connect({provider, body})` to build the body shape.
1106
+ */
1107
+ async connectProvider(provider, collectionID, config, options) {
1108
+ const body = {
1109
+ collection_id: collectionID,
1110
+ ...config !== void 0 ? { config } : {}
1111
+ };
1112
+ return unwrap(this.connectors.connect({
1113
+ provider,
1114
+ body
1115
+ }, options));
1116
+ }
1117
+ /** Positional listConnections(collectionID) — wraps the query wrapper. */
1118
+ async listConnections(collectionID, options) {
1119
+ return unwrap(this.connectors.list({ collectionId: collectionID }, options));
1120
+ }
1121
+ /** Positional disconnect(connectionID, deleteMemories?). */
1122
+ async disconnectConnection(connectionID, deleteMemories = false, options) {
1123
+ return unwrap(this.connectors.disconnect({
1124
+ connectionId: connectionID,
1125
+ deleteMemories
1126
+ }, options));
1127
+ }
1128
+ /** Alias for disconnectConnection (same arg shape). */
1129
+ async disconnect(connectionID, deleteMemories = false, options) {
1130
+ return unwrap(this.connectors.disconnect({
1131
+ connectionId: connectionID,
1132
+ deleteMemories
1133
+ }, options));
1134
+ }
1135
+ /** Single-id delete; coerces 204 to boolean true. */
1136
+ async deleteMemory(memoryID, options) {
1137
+ await this.memories.delete(memoryID, options);
1138
+ return true;
1139
+ }
1140
+ /** Bulk delete by ids. */
1141
+ async deleteMemories(memoryIDs, options) {
1142
+ return this.memories.deleteMany({ body: memoryIDs }, options);
1143
+ }
1144
+ /**
1145
+ * Polymorphic delete: dispatches based on argument type.
1146
+ * - `delete("/some/path")` -> raw HTTP DELETE (escape hatch; not implemented)
1147
+ * - `delete("memory-id")` -> deleteMemory
1148
+ * - `delete(["id1", "id2"])` -> deleteMemories (bulk)
1149
+ */
1150
+ async delete(pathOrMemoryIDs, options) {
1151
+ if (Array.isArray(pathOrMemoryIDs)) return this.deleteMemories(pathOrMemoryIDs, options);
1152
+ if (isRequestPath(pathOrMemoryIDs)) throw new Error(`delete("${pathOrMemoryIDs}") raw-path escape hatch is not implemented in this SDK yet`);
1153
+ return this.deleteMemory(pathOrMemoryIDs, options);
1154
+ }
1155
+ };
1156
+ function normalizeAuthOptions(options) {
1157
+ if (options.apiKey != null && options.bearerToken == null && !looksLikeNebulaAPIKey(options.apiKey)) return {
1158
+ ...options,
1159
+ apiKey: void 0,
1160
+ bearerToken: options.apiKey
1161
+ };
1162
+ return options;
1163
+ }
1164
+ function normalizeClientOptions(options) {
1165
+ const { api_key: apiKeyAlias, apiKey, baseUrl: baseUrlAlias, baseURL: baseURLCapAlias, base_url: baseUrlSnakeAlias, timeout: timeoutAlias, bearerToken, bearer_token: bearerTokenAlias, accessToken, access_token: accessTokenAlias, ...rest } = options;
1166
+ const restClientOptions = rest;
1167
+ return {
1168
+ ...restClientOptions,
1169
+ apiKey: firstDefined(apiKey, apiKeyAlias) ?? void 0,
1170
+ bearerToken: firstDefined(bearerToken, bearerTokenAlias, accessToken, accessTokenAlias) ?? void 0,
1171
+ baseUrl: firstDefined(restClientOptions.baseUrl, baseUrlAlias, baseURLCapAlias, baseUrlSnakeAlias) ?? void 0,
1172
+ timeoutMs: firstDefined(restClientOptions.timeoutMs, timeoutAlias) ?? void 0
1173
+ };
1174
+ }
1175
+ function firstDefined(...values) {
1176
+ return values.find((value) => value !== void 0);
1177
+ }
1178
+ function looksLikeNebulaAPIKey(token) {
1179
+ const parts = token.split(".");
1180
+ if (parts.length !== 2) return false;
1181
+ const [publicPart, rawPart] = parts;
1182
+ return Boolean(rawPart) && (publicPart.startsWith("key_") || publicPart.startsWith("neb_"));
1183
+ }
1184
+ function toMemoryCreateParams(memory) {
1185
+ const collectionID = memory.collection_id ?? memory.collectionId ?? void 0;
1186
+ const { collectionId: _ignore, content, memory_id: _ignoreMemoryID, ...rest } = memory;
1187
+ const params = { ...rest };
1188
+ if (collectionID !== void 0) params.collection_id = collectionID;
1189
+ if (content != null) if (typeof content === "string") params.raw_text = content;
1190
+ else params.content_parts = content;
1191
+ if (params.messages != null && !params.engram_type) params.engram_type = "conversation";
1192
+ return params;
1193
+ }
1194
+ function toMemoryAppendParams(memory) {
1195
+ const collectionID = memory.collection_id ?? memory.collectionId ?? void 0;
1196
+ if (!collectionID) throw new Error("collection_id is required when appending to an existing memory");
1197
+ const params = { collection_id: collectionID };
1198
+ for (const key of [
1199
+ "metadata",
1200
+ "ingestion_config",
1201
+ "ingestion_mode",
1202
+ "raw_text",
1203
+ "chunks",
1204
+ "messages"
1205
+ ]) {
1206
+ const value = memory[key];
1207
+ if (value != null) params[key] = value;
1208
+ }
1209
+ const content = memory.content;
1210
+ if (content != null) {
1211
+ if (typeof content === "string") params.raw_text = content;
1212
+ else if (Array.isArray(content) && content.every((item) => typeof item === "string")) params.chunks = content;
1213
+ else if (Array.isArray(content)) params.messages = content;
1214
+ }
1215
+ return params;
1216
+ }
1217
+ function unwrap(promise) {
1218
+ return Promise.resolve(promise).then((response) => unwrapResults(response));
1219
+ }
1220
+ function unwrapResults(response) {
1221
+ if (response !== null && typeof response === "object" && "results" in response) return response.results;
1222
+ return response;
1223
+ }
1224
+ function extractID(value) {
1225
+ if (typeof value === "object" && value !== null) {
1226
+ const record = value;
1227
+ const id = record["id"] ?? record["memory_id"] ?? record["engram_id"] ?? record["ephemeral_collection_id"];
1228
+ if (typeof id === "string") return id;
1229
+ }
1230
+ throw new Error("Nebula memory create response did not include an id");
1231
+ }
1232
+ function isSnapshotResult(value) {
1233
+ return typeof value === "object" && value !== null && "snapshot" in value;
1234
+ }
1235
+ function arrayify(value) {
1236
+ return Array.isArray(value) ? value : [value];
1237
+ }
1238
+ function isRequestPath(value) {
1239
+ return value.startsWith("/") || /^https?:\/\//i.test(value);
1240
+ }
1241
+ //#endregion
1242
+ export { DEFAULT_RETRY, Nebula, Nebula as default, NebulaAPIError, NebulaBadRequestError, NebulaClient, NebulaConflictError, NebulaConnectionError, NebulaCore, NebulaError, NebulaForbiddenError, NebulaNotFoundError, NebulaRateLimitError, NebulaServerError, NebulaTimeoutError, NebulaUnauthorizedError, NebulaValidationError, backoffMs, errorFromResponse, isRetryableStatus, sleep };
1308
1243
 
1309
- // src/index.ts
1310
- var MemoryBase = (data) => data;
1311
- var Memory2 = Object.assign(MemoryBase, {
1312
- /**
1313
- * Helper to create a file content part from a file path.
1314
- * Alias for NebulaContent.fromFile().
1315
- */
1316
- File: NebulaContent.fromFile,
1317
- /**
1318
- * Helper to create a complete Memory object from a single file.
1319
- */
1320
- async fromFile(filePath, collection_id, metadata, role) {
1321
- const path = await import('path');
1322
- const filename = path.basename(filePath);
1323
- return {
1324
- collection_id,
1325
- content: [await NebulaContent.fromFile(filePath)],
1326
- metadata: { filename, ...metadata },
1327
- role
1328
- };
1329
- }
1330
- });
1331
-
1332
- exports.GraphSearchResultType = GraphSearchResultType;
1333
- exports.Memory = Memory2;
1334
- exports.Nebula = Nebula;
1335
- exports.NebulaAuthenticationException = NebulaAuthenticationException;
1336
- exports.NebulaClientException = NebulaClientException;
1337
- exports.NebulaCollectionNotFoundException = NebulaCollectionNotFoundException;
1338
- exports.NebulaContent = NebulaContent;
1339
- exports.NebulaException = NebulaException;
1340
- exports.NebulaNotFoundException = NebulaNotFoundException;
1341
- exports.NebulaRateLimitException = NebulaRateLimitException;
1342
- exports.NebulaValidationException = NebulaValidationException;
1343
- exports.default = Nebula;
1344
- //# sourceMappingURL=index.js.map
1345
1244
  //# sourceMappingURL=index.js.map