cozo-memory 1.0.3 → 1.0.4

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.
@@ -0,0 +1,472 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ExportImportService = void 0;
7
+ const archiver_1 = __importDefault(require("archiver"));
8
+ class ExportImportService {
9
+ dbService;
10
+ constructor(dbService) {
11
+ this.dbService = dbService;
12
+ }
13
+ /**
14
+ * Export memory to various formats
15
+ */
16
+ async exportMemory(options) {
17
+ console.error('[Export] Starting export with format:', options.format);
18
+ // Fetch all data
19
+ const entities = await this.fetchEntities(options);
20
+ const observations = options.includeObservations !== false
21
+ ? await this.fetchObservations(options)
22
+ : [];
23
+ const relationships = options.includeRelationships !== false
24
+ ? await this.fetchRelationships(options)
25
+ : [];
26
+ const stats = {
27
+ entities: entities.length,
28
+ observations: observations.length,
29
+ relationships: relationships.length,
30
+ };
31
+ console.error('[Export] Fetched data:', stats);
32
+ switch (options.format) {
33
+ case 'json':
34
+ return {
35
+ format: 'json',
36
+ data: this.exportToJSON(entities, observations, relationships, options),
37
+ stats,
38
+ };
39
+ case 'markdown':
40
+ return {
41
+ format: 'markdown',
42
+ data: this.exportToMarkdown(entities, observations, relationships, options),
43
+ stats,
44
+ };
45
+ case 'obsidian':
46
+ const zipBuffer = await this.exportToObsidianZip(entities, observations, relationships, options);
47
+ return {
48
+ format: 'obsidian',
49
+ zipBuffer,
50
+ stats,
51
+ };
52
+ default:
53
+ throw new Error(`Unsupported export format: ${options.format}`);
54
+ }
55
+ }
56
+ /**
57
+ * Import memory from various formats
58
+ */
59
+ async importMemory(data, options) {
60
+ console.error('[Import] Starting import from format:', options.sourceFormat);
61
+ const result = {
62
+ imported: { entities: 0, observations: 0, relationships: 0 },
63
+ skipped: 0,
64
+ errors: [],
65
+ };
66
+ try {
67
+ switch (options.sourceFormat) {
68
+ case 'mem0':
69
+ return await this.importFromMem0(data, options, result);
70
+ case 'memgpt':
71
+ return await this.importFromMemGPT(data, options, result);
72
+ case 'markdown':
73
+ return await this.importFromMarkdown(data, options, result);
74
+ case 'cozo':
75
+ return await this.importFromCozoJSON(data, options, result);
76
+ default:
77
+ throw new Error(`Unsupported import format: ${options.sourceFormat}`);
78
+ }
79
+ }
80
+ catch (error) {
81
+ result.errors.push(`Import failed: ${error instanceof Error ? error.message : String(error)}`);
82
+ return result;
83
+ }
84
+ }
85
+ // ============================================================================
86
+ // FETCH DATA
87
+ // ============================================================================
88
+ async fetchEntities(options) {
89
+ let query = `?[id, name, type, metadata, created_at] := *entity{id, name, type, metadata, created_at @ "NOW"}`;
90
+ if (options.entityTypes && options.entityTypes.length > 0) {
91
+ const typeFilter = options.entityTypes.map(t => `"${t}"`).join(', ');
92
+ query += `, type in [${typeFilter}]`;
93
+ }
94
+ if (options.since) {
95
+ query += `, created_at >= ${options.since}`;
96
+ }
97
+ const result = await this.dbService.run(query);
98
+ return result.rows || [];
99
+ }
100
+ async fetchObservations(options) {
101
+ let query = `?[id, entity_id, text, metadata, created_at] := *observation{id, entity_id, text, metadata, created_at @ "NOW"}`;
102
+ if (options.since) {
103
+ query += `, created_at >= ${options.since}`;
104
+ }
105
+ const result = await this.dbService.run(query);
106
+ return result.rows || [];
107
+ }
108
+ async fetchRelationships(options) {
109
+ let query = `?[from_id, to_id, relation_type, strength, metadata, created_at] := *relationship{from_id, to_id, relation_type, strength, metadata, created_at @ "NOW"}`;
110
+ if (options.since) {
111
+ query += `, created_at >= ${options.since}`;
112
+ }
113
+ const result = await this.dbService.run(query);
114
+ return result.rows || [];
115
+ }
116
+ // ============================================================================
117
+ // EXPORT FORMATS
118
+ // ============================================================================
119
+ exportToJSON(entities, observations, relationships, options) {
120
+ return {
121
+ version: '1.0',
122
+ exported_at: new Date().toISOString(),
123
+ format: 'cozo-memory',
124
+ data: {
125
+ entities: entities.map(([id, name, type, metadata, created_at]) => ({
126
+ id,
127
+ name,
128
+ type,
129
+ metadata: options.includeMetadata !== false ? metadata : undefined,
130
+ created_at,
131
+ })),
132
+ observations: observations.map(([id, entity_id, text, metadata, created_at]) => ({
133
+ id,
134
+ entity_id,
135
+ text,
136
+ metadata: options.includeMetadata !== false ? metadata : undefined,
137
+ created_at,
138
+ })),
139
+ relationships: relationships.map(([from_id, to_id, relation_type, strength, metadata, created_at]) => ({
140
+ from_id,
141
+ to_id,
142
+ relation_type,
143
+ strength,
144
+ metadata: options.includeMetadata !== false ? metadata : undefined,
145
+ created_at,
146
+ })),
147
+ },
148
+ };
149
+ }
150
+ exportToMarkdown(entities, observations, relationships, options) {
151
+ let md = `# Memory Export\n\n`;
152
+ md += `Exported: ${new Date().toISOString()}\n\n`;
153
+ md += `## Statistics\n\n`;
154
+ md += `- Entities: ${entities.length}\n`;
155
+ md += `- Observations: ${observations.length}\n`;
156
+ md += `- Relationships: ${relationships.length}\n\n`;
157
+ md += `---\n\n`;
158
+ for (const [id, name, type, metadata, created_at] of entities) {
159
+ md += `## ${name}\n\n`;
160
+ md += `**Type:** ${type}\n\n`;
161
+ md += `**ID:** \`${id}\`\n\n`;
162
+ if (options.includeMetadata !== false && metadata && Object.keys(metadata).length > 0) {
163
+ md += `**Metadata:**\n\`\`\`json\n${JSON.stringify(metadata, null, 2)}\n\`\`\`\n\n`;
164
+ }
165
+ // Find observations for this entity
166
+ const entityObs = observations.filter(([, entity_id]) => entity_id === id);
167
+ if (entityObs.length > 0) {
168
+ md += `### Observations\n\n`;
169
+ for (const [obs_id, , text] of entityObs) {
170
+ md += `- ${text}\n`;
171
+ }
172
+ md += `\n`;
173
+ }
174
+ // Find relationships
175
+ const entityRels = relationships.filter(([from_id]) => from_id === id);
176
+ if (entityRels.length > 0) {
177
+ md += `### Relationships\n\n`;
178
+ for (const [, to_id, relation_type] of entityRels) {
179
+ const targetEntity = entities.find(([eid]) => eid === to_id);
180
+ const targetName = targetEntity ? targetEntity[1] : to_id;
181
+ md += `- **${relation_type}** → ${targetName}\n`;
182
+ }
183
+ md += `\n`;
184
+ }
185
+ md += `---\n\n`;
186
+ }
187
+ return md;
188
+ }
189
+ async exportToObsidianZip(entities, observations, relationships, options) {
190
+ return new Promise((resolve, reject) => {
191
+ const archive = (0, archiver_1.default)('zip', { zlib: { level: 9 } });
192
+ const chunks = [];
193
+ archive.on('data', (chunk) => chunks.push(chunk));
194
+ archive.on('end', () => resolve(Buffer.concat(chunks)));
195
+ archive.on('error', reject);
196
+ // Create README
197
+ archive.append(`# Memory Vault\n\nExported from CozoDB Memory on ${new Date().toISOString()}\n\n` +
198
+ `## Statistics\n\n- Entities: ${entities.length}\n- Observations: ${observations.length}\n- Relationships: ${relationships.length}\n`, { name: 'README.md' });
199
+ // Create entity notes
200
+ for (const [id, name, type, metadata, created_at] of entities) {
201
+ const safeName = this.sanitizeFilename(name);
202
+ let content = `---\n`;
203
+ content += `id: ${id}\n`;
204
+ content += `type: ${type}\n`;
205
+ // CozoDB timestamps are in microseconds, convert to milliseconds for Date
206
+ const timestamp = typeof created_at === 'number' ? created_at / 1000 : Date.now();
207
+ content += `created: ${new Date(timestamp).toISOString()}\n`;
208
+ if (options.includeMetadata !== false && metadata && Object.keys(metadata).length > 0) {
209
+ for (const [key, value] of Object.entries(metadata)) {
210
+ content += `${key}: ${JSON.stringify(value)}\n`;
211
+ }
212
+ }
213
+ content += `---\n\n`;
214
+ content += `# ${name}\n\n`;
215
+ // Add observations
216
+ const entityObs = observations.filter(([, entity_id]) => entity_id === id);
217
+ if (entityObs.length > 0) {
218
+ content += `## Notes\n\n`;
219
+ for (const [, , text] of entityObs) {
220
+ content += `- ${text}\n`;
221
+ }
222
+ content += `\n`;
223
+ }
224
+ // Add relationships with wiki-links
225
+ const outgoing = relationships.filter(([from_id]) => from_id === id);
226
+ const incoming = relationships.filter(([, to_id]) => to_id === id);
227
+ if (outgoing.length > 0) {
228
+ content += `## Connections\n\n`;
229
+ for (const [, to_id, relation_type] of outgoing) {
230
+ const targetEntity = entities.find(([eid]) => eid === to_id);
231
+ if (targetEntity) {
232
+ const targetName = this.sanitizeFilename(targetEntity[1]);
233
+ content += `- **${relation_type}**: [[${targetName}]]\n`;
234
+ }
235
+ }
236
+ content += `\n`;
237
+ }
238
+ if (incoming.length > 0) {
239
+ content += `## Referenced By\n\n`;
240
+ for (const [from_id, , relation_type] of incoming) {
241
+ const sourceEntity = entities.find(([eid]) => eid === from_id);
242
+ if (sourceEntity) {
243
+ const sourceName = this.sanitizeFilename(sourceEntity[1]);
244
+ content += `- [[${sourceName}]] (**${relation_type}**)\n`;
245
+ }
246
+ }
247
+ content += `\n`;
248
+ }
249
+ archive.append(content, { name: `${safeName}.md` });
250
+ }
251
+ archive.finalize();
252
+ });
253
+ }
254
+ // ============================================================================
255
+ // IMPORT FORMATS
256
+ // ============================================================================
257
+ async importFromMem0(data, options, result) {
258
+ const memories = typeof data === 'string' ? JSON.parse(data) : data;
259
+ const memoryArray = Array.isArray(memories) ? memories : [memories];
260
+ for (const mem of memoryArray) {
261
+ try {
262
+ // Mem0 format: { id, memory, user_id, metadata, created_at }
263
+ const entityName = mem.user_id || 'Imported User';
264
+ const entityType = options.defaultEntityType || 'Person';
265
+ // Check if entity exists
266
+ const existingEntity = await this.findEntityByName(entityName);
267
+ let entityId;
268
+ if (existingEntity) {
269
+ entityId = existingEntity;
270
+ if (options.mergeStrategy === 'skip') {
271
+ result.skipped++;
272
+ continue;
273
+ }
274
+ }
275
+ else {
276
+ // Create entity
277
+ entityId = await this.createEntity(entityName, entityType, mem.metadata || {});
278
+ result.imported.entities++;
279
+ }
280
+ // Add observation
281
+ await this.createObservation(entityId, mem.memory, mem.metadata || {});
282
+ result.imported.observations++;
283
+ }
284
+ catch (error) {
285
+ result.errors.push(`Failed to import Mem0 entry: ${error instanceof Error ? error.message : String(error)}`);
286
+ }
287
+ }
288
+ return result;
289
+ }
290
+ async importFromMemGPT(data, options, result) {
291
+ const memgptData = typeof data === 'string' ? JSON.parse(data) : data;
292
+ // MemGPT has archival_memory and recall_memory
293
+ const archival = memgptData.archival_memory || [];
294
+ const recall = memgptData.recall_memory || [];
295
+ // Create MemGPT agent entity
296
+ const agentName = memgptData.agent_name || 'MemGPT Agent';
297
+ const entityId = await this.createEntity(agentName, 'Agent', { source: 'memgpt' });
298
+ result.imported.entities++;
299
+ // Import archival memory
300
+ for (const item of archival) {
301
+ try {
302
+ await this.createObservation(entityId, item.content || item.text || item, {
303
+ type: 'archival',
304
+ timestamp: item.timestamp
305
+ });
306
+ result.imported.observations++;
307
+ }
308
+ catch (error) {
309
+ result.errors.push(`Failed to import MemGPT archival: ${error instanceof Error ? error.message : String(error)}`);
310
+ }
311
+ }
312
+ // Import recall memory
313
+ for (const item of recall) {
314
+ try {
315
+ await this.createObservation(entityId, item.content || item.text || item, {
316
+ type: 'recall',
317
+ timestamp: item.timestamp
318
+ });
319
+ result.imported.observations++;
320
+ }
321
+ catch (error) {
322
+ result.errors.push(`Failed to import MemGPT recall: ${error instanceof Error ? error.message : String(error)}`);
323
+ }
324
+ }
325
+ return result;
326
+ }
327
+ async importFromMarkdown(data, options, result) {
328
+ // Simple markdown parser: split by ## headers
329
+ const sections = data.split(/^## /m).filter(s => s.trim());
330
+ for (const section of sections) {
331
+ try {
332
+ const lines = section.split('\n');
333
+ const name = lines[0].trim();
334
+ if (!name || name === 'Statistics')
335
+ continue;
336
+ const entityType = options.defaultEntityType || 'Note';
337
+ const entityId = await this.createEntity(name, entityType, { source: 'markdown' });
338
+ result.imported.entities++;
339
+ // Extract observations (lines starting with -)
340
+ const observations = lines.filter(l => l.trim().startsWith('-')).map(l => l.trim().substring(1).trim());
341
+ for (const obs of observations) {
342
+ if (obs) {
343
+ await this.createObservation(entityId, obs, {});
344
+ result.imported.observations++;
345
+ }
346
+ }
347
+ }
348
+ catch (error) {
349
+ result.errors.push(`Failed to import markdown section: ${error instanceof Error ? error.message : String(error)}`);
350
+ }
351
+ }
352
+ return result;
353
+ }
354
+ async importFromCozoJSON(data, options, result) {
355
+ const cozoData = typeof data === 'string' ? JSON.parse(data) : data;
356
+ if (!cozoData.data) {
357
+ throw new Error('Invalid Cozo export format');
358
+ }
359
+ const { entities, observations, relationships } = cozoData.data;
360
+ // Import entities
361
+ for (const entity of entities || []) {
362
+ try {
363
+ if (options.mergeStrategy === 'skip') {
364
+ const existing = await this.findEntityById(entity.id);
365
+ if (existing) {
366
+ result.skipped++;
367
+ continue;
368
+ }
369
+ }
370
+ await this.createEntityWithId(entity.id, entity.name, entity.type, entity.metadata || {});
371
+ result.imported.entities++;
372
+ }
373
+ catch (error) {
374
+ result.errors.push(`Failed to import entity ${entity.name}: ${error instanceof Error ? error.message : String(error)}`);
375
+ }
376
+ }
377
+ // Import observations
378
+ for (const obs of observations || []) {
379
+ try {
380
+ await this.createObservationWithId(obs.id, obs.entity_id, obs.text, obs.metadata || {});
381
+ result.imported.observations++;
382
+ }
383
+ catch (error) {
384
+ result.errors.push(`Failed to import observation: ${error instanceof Error ? error.message : String(error)}`);
385
+ }
386
+ }
387
+ // Import relationships
388
+ for (const rel of relationships || []) {
389
+ try {
390
+ await this.createRelationship(rel.from_id, rel.to_id, rel.relation_type, rel.strength || 1.0, rel.metadata || {});
391
+ result.imported.relationships++;
392
+ }
393
+ catch (error) {
394
+ result.errors.push(`Failed to import relationship: ${error instanceof Error ? error.message : String(error)}`);
395
+ }
396
+ }
397
+ return result;
398
+ }
399
+ // ============================================================================
400
+ // HELPER METHODS
401
+ // ============================================================================
402
+ sanitizeFilename(name) {
403
+ return name.replace(/[<>:"/\\|?*]/g, '-').replace(/\s+/g, ' ').trim();
404
+ }
405
+ async findEntityByName(name) {
406
+ const result = await this.dbService.run(`?[id] := *entity{id, name @ "NOW"}, name == "${name}"`);
407
+ return result.rows && result.rows.length > 0 ? result.rows[0][0] : null;
408
+ }
409
+ async findEntityById(id) {
410
+ const result = await this.dbService.run(`?[id] := *entity{id @ "NOW"}, id == "${id}"`);
411
+ return result.rows && result.rows.length > 0;
412
+ }
413
+ async createEntity(name, type, metadata) {
414
+ const { v4: uuidv4 } = await import('uuid');
415
+ const id = uuidv4();
416
+ await this.createEntityWithId(id, name, type, metadata);
417
+ return id;
418
+ }
419
+ async createEntityWithId(id, name, type, metadata) {
420
+ const now = Date.now() * 1000;
421
+ const zeroVec = new Array(1024).fill(0);
422
+ // Escape strings properly for CozoDB
423
+ const escapedName = name.replace(/"/g, '\\"');
424
+ const escapedType = type.replace(/"/g, '\\"');
425
+ await this.dbService.run(`
426
+ ?[id, name, type, embedding, name_embedding, metadata, created_at] <- [[$id, $name, $type, $embedding, $name_embedding, $metadata, [${now}, true]]]
427
+ :insert entity {id, name, type, embedding, name_embedding, metadata, created_at}
428
+ `, {
429
+ id,
430
+ name: escapedName,
431
+ type: escapedType,
432
+ embedding: zeroVec,
433
+ name_embedding: zeroVec,
434
+ metadata: metadata || {}
435
+ });
436
+ }
437
+ async createObservation(entityId, text, metadata) {
438
+ const { v4: uuidv4 } = await import('uuid');
439
+ const id = uuidv4();
440
+ await this.createObservationWithId(id, entityId, text, metadata);
441
+ return id;
442
+ }
443
+ async createObservationWithId(id, entityId, text, metadata) {
444
+ const now = Date.now() * 1000;
445
+ const zeroVec = new Array(1024).fill(0);
446
+ const escapedText = text.replace(/"/g, '\\"').replace(/\n/g, '\\n');
447
+ await this.dbService.run(`
448
+ ?[id, entity_id, text, embedding, metadata, created_at] <- [[$id, $entity_id, $text, $embedding, $metadata, [${now}, true]]]
449
+ :insert observation {id, entity_id, text, embedding, metadata, created_at}
450
+ `, {
451
+ id,
452
+ entity_id: entityId,
453
+ text: escapedText,
454
+ embedding: zeroVec,
455
+ metadata: metadata || {}
456
+ });
457
+ }
458
+ async createRelationship(fromId, toId, relationType, strength, metadata) {
459
+ const now = Date.now() * 1000;
460
+ await this.dbService.run(`
461
+ ?[from_id, to_id, relation_type, strength, metadata, created_at] <- [[$from_id, $to_id, $relation_type, $strength, $metadata, [${now}, true]]]
462
+ :insert relationship {from_id, to_id, relation_type, strength, metadata, created_at}
463
+ `, {
464
+ from_id: fromId,
465
+ to_id: toId,
466
+ relation_type: relationType,
467
+ strength,
468
+ metadata: metadata || {}
469
+ });
470
+ }
471
+ }
472
+ exports.ExportImportService = ExportImportService;