@cmdoss/memwal-sdk 0.6.2 → 0.7.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/ARCHITECTURE.md +547 -547
- package/BENCHMARKS.md +238 -238
- package/README.md +181 -181
- package/dist/ai-sdk/tools.d.ts +2 -2
- package/dist/ai-sdk/tools.js +2 -2
- package/dist/client/PersonalDataWallet.d.ts.map +1 -1
- package/dist/client/SimplePDWClient.d.ts +1 -1
- package/dist/client/SimplePDWClient.d.ts.map +1 -1
- package/dist/client/SimplePDWClient.js +16 -7
- package/dist/client/SimplePDWClient.js.map +1 -1
- package/dist/client/namespaces/EmbeddingsNamespace.d.ts +1 -1
- package/dist/client/namespaces/EmbeddingsNamespace.js +1 -1
- package/dist/client/namespaces/MemoryNamespace.d.ts +27 -0
- package/dist/client/namespaces/MemoryNamespace.d.ts.map +1 -1
- package/dist/client/namespaces/MemoryNamespace.js +104 -0
- package/dist/client/namespaces/MemoryNamespace.js.map +1 -1
- package/dist/client/namespaces/consolidated/AINamespace.d.ts +2 -2
- package/dist/client/namespaces/consolidated/AINamespace.js +2 -2
- package/dist/client/namespaces/consolidated/BlockchainNamespace.d.ts.map +1 -1
- package/dist/client/namespaces/consolidated/BlockchainNamespace.js +22 -2
- package/dist/client/namespaces/consolidated/BlockchainNamespace.js.map +1 -1
- package/dist/graph/GraphService.js +1 -1
- package/dist/graph/GraphService.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/retrieval/MemoryRetrievalService.d.ts +31 -0
- package/dist/retrieval/MemoryRetrievalService.d.ts.map +1 -1
- package/dist/retrieval/MemoryRetrievalService.js +44 -4
- package/dist/retrieval/MemoryRetrievalService.js.map +1 -1
- package/dist/services/EmbeddingService.d.ts +28 -1
- package/dist/services/EmbeddingService.d.ts.map +1 -1
- package/dist/services/EmbeddingService.js +54 -0
- package/dist/services/EmbeddingService.js.map +1 -1
- package/dist/services/IndexManager.d.ts +5 -1
- package/dist/services/IndexManager.d.ts.map +1 -1
- package/dist/services/IndexManager.js +17 -40
- package/dist/services/IndexManager.js.map +1 -1
- package/dist/services/QueryService.js +1 -1
- package/dist/services/QueryService.js.map +1 -1
- package/dist/services/StorageService.d.ts +10 -0
- package/dist/services/StorageService.d.ts.map +1 -1
- package/dist/services/StorageService.js +13 -0
- package/dist/services/StorageService.js.map +1 -1
- package/dist/services/storage/QuiltBatchManager.d.ts +101 -1
- package/dist/services/storage/QuiltBatchManager.d.ts.map +1 -1
- package/dist/services/storage/QuiltBatchManager.js +410 -20
- package/dist/services/storage/QuiltBatchManager.js.map +1 -1
- package/dist/services/storage/index.d.ts +1 -1
- package/dist/services/storage/index.d.ts.map +1 -1
- package/dist/services/storage/index.js.map +1 -1
- package/dist/utils/LRUCache.d.ts +106 -0
- package/dist/utils/LRUCache.d.ts.map +1 -0
- package/dist/utils/LRUCache.js +281 -0
- package/dist/utils/LRUCache.js.map +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/memoryIndexOnChain.d.ts +212 -0
- package/dist/utils/memoryIndexOnChain.d.ts.map +1 -0
- package/dist/utils/memoryIndexOnChain.js +312 -0
- package/dist/utils/memoryIndexOnChain.js.map +1 -0
- package/dist/utils/rebuildIndexNode.d.ts +29 -0
- package/dist/utils/rebuildIndexNode.d.ts.map +1 -1
- package/dist/utils/rebuildIndexNode.js +366 -98
- package/dist/utils/rebuildIndexNode.js.map +1 -1
- package/dist/vector/HnswWasmService.d.ts +20 -5
- package/dist/vector/HnswWasmService.d.ts.map +1 -1
- package/dist/vector/HnswWasmService.js +73 -40
- package/dist/vector/HnswWasmService.js.map +1 -1
- package/dist/vector/IHnswService.d.ts +10 -1
- package/dist/vector/IHnswService.d.ts.map +1 -1
- package/dist/vector/IHnswService.js.map +1 -1
- package/dist/vector/NodeHnswService.d.ts +16 -0
- package/dist/vector/NodeHnswService.d.ts.map +1 -1
- package/dist/vector/NodeHnswService.js +84 -5
- package/dist/vector/NodeHnswService.js.map +1 -1
- package/dist/vector/createHnswService.d.ts +1 -1
- package/dist/vector/createHnswService.js +1 -1
- package/dist/vector/index.d.ts +1 -1
- package/dist/vector/index.js +1 -1
- package/package.json +157 -157
- package/src/access/index.ts +8 -8
- package/src/aggregation/index.ts +8 -8
- package/src/ai-sdk/tools.ts +2 -2
- package/src/client/SimplePDWClient.ts +23 -8
- package/src/client/namespaces/EmbeddingsNamespace.ts +1 -1
- package/src/client/namespaces/MemoryNamespace.ts +137 -0
- package/src/client/namespaces/consolidated/AINamespace.ts +2 -2
- package/src/client/namespaces/consolidated/BlockchainNamespace.ts +20 -2
- package/src/client/signers/DappKitSigner.ts +207 -207
- package/src/core/types/index.ts +1 -1
- package/src/generated/pdw/deps/sui/object.ts +12 -12
- package/src/generated/pdw/deps/sui/vec_map.ts +32 -32
- package/src/generated/pdw/memory.ts +1087 -1087
- package/src/generated/pdw/wallet.ts +123 -123
- package/src/generated/utils/index.ts +159 -159
- package/src/graph/GraphService.ts +1 -1
- package/src/index.ts +25 -1
- package/src/permissions/index.ts +9 -9
- package/src/retrieval/MemoryRetrievalService.ts +78 -4
- package/src/services/EmbeddingService.ts +66 -1
- package/src/services/IndexManager.ts +18 -45
- package/src/services/QueryService.ts +1 -1
- package/src/services/StorageService.ts +15 -0
- package/src/services/storage/QuiltBatchManager.ts +492 -22
- package/src/services/storage/index.ts +6 -1
- package/src/utils/LRUCache.ts +378 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/memoryIndexOnChain.ts +507 -0
- package/src/utils/rebuildIndexNode.ts +453 -106
- package/src/vector/HnswWasmService.ts +95 -43
- package/src/vector/IHnswService.ts +10 -1
- package/src/vector/NodeHnswService.ts +103 -5
- package/src/vector/createHnswService.ts +1 -1
- package/src/vector/index.ts +1 -1
- package/src/wallet/index.ts +17 -17
|
@@ -53,6 +53,20 @@ export interface RebuildIndexNodeOptions {
|
|
|
53
53
|
|
|
54
54
|
/** Whether to force re-index even if index exists */
|
|
55
55
|
force?: boolean;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Quilt IDs to include in the rebuild.
|
|
59
|
+
* Quilts contain batch-uploaded memories that may not have on-chain Memory objects.
|
|
60
|
+
* Pass Quilt IDs here to include them in the index rebuild.
|
|
61
|
+
*/
|
|
62
|
+
quiltIds?: string[];
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Number of concurrent blob fetches (default: 10)
|
|
66
|
+
* Higher values can speed up rebuilding but may overwhelm the server
|
|
67
|
+
* Benchmark results: 10 is ~1.64x faster than sequential
|
|
68
|
+
*/
|
|
69
|
+
fetchConcurrency?: number;
|
|
56
70
|
}
|
|
57
71
|
|
|
58
72
|
export interface RebuildIndexNodeResult {
|
|
@@ -62,6 +76,23 @@ export interface RebuildIndexNodeResult {
|
|
|
62
76
|
failedMemories: number;
|
|
63
77
|
errors: Array<{ blobId: string; error: string }>;
|
|
64
78
|
duration: number;
|
|
79
|
+
/** Detailed timing breakdown for performance analysis */
|
|
80
|
+
timing?: {
|
|
81
|
+
/** Time to initialize services (ms) */
|
|
82
|
+
initMs: number;
|
|
83
|
+
/** Time to fetch blockchain data (ms) */
|
|
84
|
+
blockchainFetchMs: number;
|
|
85
|
+
/** Time to fetch all blobs from Walrus (ms) */
|
|
86
|
+
walrusFetchMs: number;
|
|
87
|
+
/** Time to process memories and build index (ms) */
|
|
88
|
+
processingMs: number;
|
|
89
|
+
/** Time to save index to disk (ms) */
|
|
90
|
+
saveMs: number;
|
|
91
|
+
/** Total blobs fetched */
|
|
92
|
+
blobsFetched: number;
|
|
93
|
+
/** Total content bytes downloaded */
|
|
94
|
+
totalBytesDownloaded: number;
|
|
95
|
+
};
|
|
65
96
|
}
|
|
66
97
|
|
|
67
98
|
interface MemoryContent {
|
|
@@ -71,10 +102,79 @@ interface MemoryContent {
|
|
|
71
102
|
category: string;
|
|
72
103
|
importance: number;
|
|
73
104
|
topic: string;
|
|
105
|
+
memoryId?: string;
|
|
74
106
|
};
|
|
75
107
|
timestamp: number;
|
|
76
108
|
}
|
|
77
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Find a matching file in a Quilt using multiple strategies
|
|
112
|
+
* Mirrors the logic in SDK's QuiltBatchManager.findMemoryInQuilt()
|
|
113
|
+
*
|
|
114
|
+
* Strategies (in order):
|
|
115
|
+
* 1. Match by tags['memory_id'] === vectorId
|
|
116
|
+
* 2. Match by identifier === `memory-${vectorId}.json`
|
|
117
|
+
* 3. Match by JSON metadata.memoryId === vectorId
|
|
118
|
+
* 4. Fallback to index-based matching
|
|
119
|
+
*/
|
|
120
|
+
async function findMatchingFile(
|
|
121
|
+
files: WalrusFile[],
|
|
122
|
+
vectorId: number,
|
|
123
|
+
fallbackIndex: number
|
|
124
|
+
): Promise<{ file: WalrusFile | undefined; matchStrategy: string }> {
|
|
125
|
+
let matchedFile: WalrusFile | undefined;
|
|
126
|
+
let matchStrategy = '';
|
|
127
|
+
|
|
128
|
+
// Strategy 1: Match by tags['memory_id']
|
|
129
|
+
for (const f of files) {
|
|
130
|
+
const tags = await f.getTags();
|
|
131
|
+
if (tags?.['memory_id'] === String(vectorId)) {
|
|
132
|
+
matchedFile = f;
|
|
133
|
+
const identifier = await f.getIdentifier();
|
|
134
|
+
matchStrategy = `memory_id tag: ${tags['memory_id']} (${identifier})`;
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Strategy 2: Match by identifier pattern "memory-{vectorId}.json"
|
|
140
|
+
if (!matchedFile) {
|
|
141
|
+
for (const f of files) {
|
|
142
|
+
const identifier = await f.getIdentifier();
|
|
143
|
+
if (identifier === `memory-${vectorId}.json`) {
|
|
144
|
+
matchedFile = f;
|
|
145
|
+
matchStrategy = `identifier: ${identifier}`;
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Strategy 3: Parse JSON to find matching metadata.memoryId
|
|
152
|
+
if (!matchedFile) {
|
|
153
|
+
for (const f of files) {
|
|
154
|
+
try {
|
|
155
|
+
const json = await f.json() as MemoryContent;
|
|
156
|
+
if (json?.metadata?.memoryId === String(vectorId)) {
|
|
157
|
+
matchedFile = f;
|
|
158
|
+
const identifier = await f.getIdentifier();
|
|
159
|
+
matchStrategy = `JSON metadata.memoryId: ${json.metadata.memoryId} (${identifier})`;
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
} catch {
|
|
163
|
+
// Not valid JSON, continue
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Strategy 4: Fallback to index-based matching
|
|
169
|
+
if (!matchedFile && fallbackIndex < files.length) {
|
|
170
|
+
matchedFile = files[fallbackIndex];
|
|
171
|
+
const identifier = await matchedFile.getIdentifier();
|
|
172
|
+
matchStrategy = `index fallback (${fallbackIndex}): ${identifier || 'no identifier'}`;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return { file: matchedFile, matchStrategy };
|
|
176
|
+
}
|
|
177
|
+
|
|
78
178
|
/**
|
|
79
179
|
* Rebuild HNSW index from blockchain + Walrus (Node.js)
|
|
80
180
|
*/
|
|
@@ -87,12 +187,25 @@ export async function rebuildIndexNode(options: RebuildIndexNodeOptions): Promis
|
|
|
87
187
|
walrusAggregator,
|
|
88
188
|
indexDirectory = './.pdw-indexes',
|
|
89
189
|
onProgress,
|
|
90
|
-
force = false
|
|
190
|
+
force = false,
|
|
191
|
+
quiltIds = [],
|
|
192
|
+
fetchConcurrency = 10
|
|
91
193
|
} = options;
|
|
92
194
|
|
|
93
195
|
const startTime = Date.now();
|
|
94
196
|
const errors: Array<{ blobId: string; error: string }> = [];
|
|
95
197
|
|
|
198
|
+
// Detailed timing
|
|
199
|
+
const timing = {
|
|
200
|
+
initMs: 0,
|
|
201
|
+
blockchainFetchMs: 0,
|
|
202
|
+
walrusFetchMs: 0,
|
|
203
|
+
processingMs: 0,
|
|
204
|
+
saveMs: 0,
|
|
205
|
+
blobsFetched: 0,
|
|
206
|
+
totalBytesDownloaded: 0,
|
|
207
|
+
};
|
|
208
|
+
|
|
96
209
|
console.log('[rebuildIndexNode] Starting index rebuild...');
|
|
97
210
|
onProgress?.(0, 0, 'Initializing...');
|
|
98
211
|
|
|
@@ -123,6 +236,8 @@ export async function rebuildIndexNode(options: RebuildIndexNodeOptions): Promis
|
|
|
123
236
|
});
|
|
124
237
|
|
|
125
238
|
await hnswService.initialize();
|
|
239
|
+
timing.initMs = Date.now() - startTime;
|
|
240
|
+
console.log(`[rebuildIndexNode] ⏱️ Init: ${timing.initMs}ms`);
|
|
126
241
|
|
|
127
242
|
// Check if index exists
|
|
128
243
|
const indexPath = `${indexDirectory}/${userAddress.replace(/[^a-zA-Z0-9]/g, '_')}.hnsw`;
|
|
@@ -152,6 +267,7 @@ export async function rebuildIndexNode(options: RebuildIndexNodeOptions): Promis
|
|
|
152
267
|
}
|
|
153
268
|
|
|
154
269
|
// Fetch all memories from blockchain
|
|
270
|
+
const blockchainFetchStart = Date.now();
|
|
155
271
|
console.log('[rebuildIndexNode] Fetching memories from blockchain...');
|
|
156
272
|
onProgress?.(0, 0, 'Fetching memories from blockchain...');
|
|
157
273
|
|
|
@@ -222,153 +338,384 @@ export async function rebuildIndexNode(options: RebuildIndexNodeOptions): Promis
|
|
|
222
338
|
}
|
|
223
339
|
|
|
224
340
|
console.log(`[rebuildIndexNode] Unique blobIds: ${memoriesByBlobId.size} (${memoriesByBlobId.size < totalMemories ? 'Quilt detected' : 'individual blobs'})`);
|
|
341
|
+
timing.blockchainFetchMs = Date.now() - blockchainFetchStart;
|
|
342
|
+
console.log(`[rebuildIndexNode] ⏱️ Blockchain fetch: ${timing.blockchainFetchMs}ms`);
|
|
225
343
|
|
|
226
344
|
let indexedCount = 0;
|
|
227
345
|
let failedCount = 0;
|
|
228
346
|
let processedCount = 0;
|
|
229
347
|
|
|
230
|
-
//
|
|
348
|
+
// ==================== PARALLEL BLOB FETCHING + CONTENT ====================
|
|
349
|
+
// Step 1: Check blob types (Quilt vs regular) in parallel
|
|
350
|
+
// Step 2: Fetch content in parallel (patches for Quilt, bytes for regular)
|
|
351
|
+
const blobIds = Array.from(memoriesByBlobId.keys());
|
|
352
|
+
|
|
353
|
+
console.log(`[rebuildIndexNode] Fetching ${blobIds.length} blobs (concurrency: ${fetchConcurrency})...`);
|
|
354
|
+
const fetchStartTime = Date.now();
|
|
355
|
+
|
|
231
356
|
const quiltFileCache = new Map<string, WalrusFile[]>();
|
|
357
|
+
const contentCache = new Map<string, Uint8Array>(); // blobId or blobId:identifier -> content
|
|
358
|
+
const fetchErrors: Array<{ blobId: string; error: string }> = [];
|
|
359
|
+
|
|
360
|
+
// Process in batches to control concurrency
|
|
361
|
+
for (let i = 0; i < blobIds.length; i += fetchConcurrency) {
|
|
362
|
+
const batch = blobIds.slice(i, i + fetchConcurrency);
|
|
363
|
+
const batchNum = Math.floor(i / fetchConcurrency) + 1;
|
|
364
|
+
const totalBatches = Math.ceil(blobIds.length / fetchConcurrency);
|
|
365
|
+
|
|
366
|
+
console.log(`[rebuildIndexNode] 📥 Batch ${batchNum}/${totalBatches}: ${batch.length} blobs...`);
|
|
367
|
+
onProgress?.(i, blobIds.length, `Fetching batch ${batchNum}/${totalBatches}...`);
|
|
368
|
+
|
|
369
|
+
// Parallel fetch: check type + fetch content for each blob
|
|
370
|
+
const results = await Promise.all(
|
|
371
|
+
batch.map(async (blobId) => {
|
|
372
|
+
try {
|
|
373
|
+
// Try as Quilt first (getBlob + files)
|
|
374
|
+
try {
|
|
375
|
+
const blob = await walrusClient.walrus.getBlob({ blobId });
|
|
376
|
+
const quiltFiles = await blob.files();
|
|
377
|
+
|
|
378
|
+
if (quiltFiles.length > 1) {
|
|
379
|
+
// It's a Quilt with multiple patches - fetch all content in parallel
|
|
380
|
+
const patchResults = await Promise.all(
|
|
381
|
+
quiltFiles.map(async (file) => {
|
|
382
|
+
const identifier = await file.getIdentifier();
|
|
383
|
+
const tags = await file.getTags();
|
|
384
|
+
const bytes = await file.bytes();
|
|
385
|
+
return { file, identifier, tags, bytes };
|
|
386
|
+
})
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
return {
|
|
390
|
+
blobId,
|
|
391
|
+
success: true,
|
|
392
|
+
isQuilt: true,
|
|
393
|
+
files: quiltFiles,
|
|
394
|
+
patches: patchResults,
|
|
395
|
+
};
|
|
396
|
+
} else {
|
|
397
|
+
// Single file in blob - fetch content
|
|
398
|
+
const bytes = await quiltFiles[0].bytes();
|
|
399
|
+
return {
|
|
400
|
+
blobId,
|
|
401
|
+
success: true,
|
|
402
|
+
isQuilt: false,
|
|
403
|
+
files: quiltFiles,
|
|
404
|
+
bytes,
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
} catch {
|
|
408
|
+
// Not a Quilt - try as regular blob
|
|
409
|
+
const files = await walrusClient.walrus.getFiles({ ids: [blobId] });
|
|
410
|
+
if (files[0]) {
|
|
411
|
+
const bytes = await files[0].bytes();
|
|
412
|
+
return {
|
|
413
|
+
blobId,
|
|
414
|
+
success: true,
|
|
415
|
+
isQuilt: false,
|
|
416
|
+
files,
|
|
417
|
+
bytes,
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
return { blobId, success: false, error: 'No file found' };
|
|
421
|
+
}
|
|
422
|
+
} catch (error: any) {
|
|
423
|
+
return { blobId, success: false, error: error.message || String(error) };
|
|
424
|
+
}
|
|
425
|
+
})
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
// Process results into caches
|
|
429
|
+
for (const result of results) {
|
|
430
|
+
if (!result.success) {
|
|
431
|
+
fetchErrors.push({ blobId: result.blobId, error: result.error || 'Unknown error' });
|
|
432
|
+
console.error(`[rebuildIndexNode] ✗ ${result.blobId.substring(0, 16)}...: ${result.error}`);
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (result.isQuilt && result.patches) {
|
|
437
|
+
// Quilt: cache files and patch contents
|
|
438
|
+
quiltFileCache.set(result.blobId, result.files!);
|
|
439
|
+
for (const patch of result.patches) {
|
|
440
|
+
const cacheKey = patch.identifier
|
|
441
|
+
? `${result.blobId}:${patch.identifier}`
|
|
442
|
+
: result.blobId;
|
|
443
|
+
contentCache.set(cacheKey, patch.bytes);
|
|
444
|
+
}
|
|
445
|
+
console.log(`[rebuildIndexNode] ✓ ${result.blobId.substring(0, 16)}... (Quilt: ${result.patches.length} patches)`);
|
|
446
|
+
} else if (result.bytes) {
|
|
447
|
+
// Regular blob: cache file and content
|
|
448
|
+
quiltFileCache.set(result.blobId, result.files!);
|
|
449
|
+
contentCache.set(result.blobId, result.bytes);
|
|
450
|
+
console.log(`[rebuildIndexNode] ✓ ${result.blobId.substring(0, 16)}... (${result.bytes.length} bytes)`);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
timing.walrusFetchMs = Date.now() - fetchStartTime;
|
|
456
|
+
timing.blobsFetched = quiltFileCache.size;
|
|
457
|
+
// Calculate total bytes downloaded
|
|
458
|
+
for (const bytes of contentCache.values()) {
|
|
459
|
+
timing.totalBytesDownloaded += bytes.length;
|
|
460
|
+
}
|
|
461
|
+
console.log(`[rebuildIndexNode] ⏱️ Walrus fetch: ${timing.walrusFetchMs}ms (${quiltFileCache.size} blobs, ${contentCache.size} contents, ${(timing.totalBytesDownloaded / 1024).toFixed(1)}KB)`);
|
|
232
462
|
|
|
463
|
+
const processingStart = Date.now();
|
|
464
|
+
|
|
465
|
+
// ==================== PROCESS MEMORIES ====================
|
|
233
466
|
for (const [blobId, memoriesInBlob] of memoriesByBlobId) {
|
|
234
467
|
console.log(`[rebuildIndexNode] Processing blobId ${blobId.substring(0, 20)}... (${memoriesInBlob.length} memories)`);
|
|
235
468
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
// For regular blob: returns [singleFile]
|
|
239
|
-
// For Quilt: returns [file1, file2, ...] - all files in the quilt
|
|
240
|
-
let files: WalrusFile[];
|
|
241
|
-
|
|
242
|
-
if (quiltFileCache.has(blobId)) {
|
|
243
|
-
files = quiltFileCache.get(blobId)!;
|
|
244
|
-
console.log(`[rebuildIndexNode] ♻️ Using cached files (${files.length} files)`);
|
|
245
|
-
} else {
|
|
246
|
-
const blob = await walrusClient.walrus.getBlob({ blobId });
|
|
247
|
-
files = await blob.files();
|
|
248
|
-
quiltFileCache.set(blobId, files);
|
|
249
|
-
console.log(`[rebuildIndexNode] 📥 Fetched ${files.length} file(s) from Walrus`);
|
|
250
|
-
}
|
|
469
|
+
// Get pre-fetched files from cache
|
|
470
|
+
const files = quiltFileCache.get(blobId);
|
|
251
471
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
472
|
+
if (!files) {
|
|
473
|
+
// Blob fetch failed - mark all memories in this blob as failed
|
|
474
|
+
const fetchError = fetchErrors.find(e => e.blobId === blobId);
|
|
475
|
+
const errorMsg = fetchError?.error || 'Failed to fetch blob';
|
|
476
|
+
console.error(`[rebuildIndexNode] ✗ No files available: ${errorMsg}`);
|
|
257
477
|
|
|
258
|
-
|
|
259
|
-
|
|
478
|
+
for (const memory of memoriesInBlob) {
|
|
479
|
+
processedCount++;
|
|
480
|
+
failedCount++;
|
|
481
|
+
errors.push({ blobId: memory.blobId, error: `Blob fetch failed: ${errorMsg}` });
|
|
482
|
+
}
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
260
485
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
486
|
+
console.log(`[rebuildIndexNode] 📦 Using ${files.length} pre-fetched file(s)`);
|
|
487
|
+
|
|
488
|
+
// For each memory in this blobId
|
|
489
|
+
for (let i = 0; i < memoriesInBlob.length; i++) {
|
|
490
|
+
const memory = memoriesInBlob[i];
|
|
491
|
+
processedCount++;
|
|
492
|
+
const progress = `Memory ${processedCount}/${totalMemories}`;
|
|
493
|
+
|
|
494
|
+
console.log(`[rebuildIndexNode] Processing ${progress}: vectorId=${memory.vectorId}`);
|
|
495
|
+
onProgress?.(processedCount, totalMemories, `Processing ${progress}...`);
|
|
496
|
+
|
|
497
|
+
try {
|
|
498
|
+
// Find matching file using helper function (mirrors SDK's QuiltBatchManager.findMemoryInQuilt)
|
|
499
|
+
let file: WalrusFile | undefined;
|
|
500
|
+
|
|
501
|
+
if (files.length === 1) {
|
|
502
|
+
// Single file - use it directly
|
|
503
|
+
file = files[0];
|
|
504
|
+
} else if (files.length > 1) {
|
|
505
|
+
// Multiple files in Quilt - use matching strategies
|
|
506
|
+
const { file: matchedFile, matchStrategy } = await findMatchingFile(files, memory.vectorId, i);
|
|
507
|
+
file = matchedFile;
|
|
508
|
+
if (matchStrategy) {
|
|
509
|
+
console.log(`[rebuildIndexNode] 🎯 Matched by ${matchStrategy}`);
|
|
270
510
|
}
|
|
511
|
+
}
|
|
271
512
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
const trimmedText = rawText.trim();
|
|
513
|
+
if (!file) {
|
|
514
|
+
throw new Error(`No file found for memory vectorId=${memory.vectorId} (blob has ${files.length} files)`);
|
|
515
|
+
}
|
|
276
516
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
517
|
+
// Get file identifier and tags if available (for Quilts)
|
|
518
|
+
const identifier = await file.getIdentifier();
|
|
519
|
+
const tags = await file.getTags();
|
|
280
520
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
521
|
+
// Get content from cache (already pre-fetched) or fetch if not cached
|
|
522
|
+
const cacheKey = identifier ? `${blobId}:${identifier}` : blobId;
|
|
523
|
+
let rawBytes = contentCache.get(cacheKey);
|
|
524
|
+
if (!rawBytes) {
|
|
525
|
+
// Fallback: fetch content if not in cache
|
|
526
|
+
rawBytes = await file.bytes();
|
|
527
|
+
}
|
|
528
|
+
const rawText = new TextDecoder().decode(rawBytes);
|
|
529
|
+
const trimmedText = rawText.trim();
|
|
284
530
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
let timestamp = Date.now();
|
|
289
|
-
|
|
290
|
-
if (trimmedText.startsWith('{') && trimmedText.endsWith('}')) {
|
|
291
|
-
// JSON package format (correct format)
|
|
292
|
-
try {
|
|
293
|
-
const memoryData: MemoryContent = JSON.parse(trimmedText);
|
|
294
|
-
content = memoryData.content;
|
|
295
|
-
embedding = memoryData.embedding;
|
|
296
|
-
metadata = memoryData.metadata || {};
|
|
297
|
-
timestamp = memoryData.timestamp || Date.now();
|
|
298
|
-
|
|
299
|
-
if (!embedding || embedding.length !== 3072) {
|
|
300
|
-
throw new Error(`Invalid embedding in JSON: length=${embedding?.length || 0}`);
|
|
301
|
-
}
|
|
531
|
+
if (identifier) {
|
|
532
|
+
console.log(`[rebuildIndexNode] 📎 File identifier: ${identifier}`);
|
|
533
|
+
}
|
|
302
534
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
535
|
+
let content: string;
|
|
536
|
+
let embedding: number[];
|
|
537
|
+
let metadata: { category?: string; importance?: number; topic?: string } = {};
|
|
538
|
+
let timestamp = Date.now();
|
|
539
|
+
|
|
540
|
+
if (trimmedText.startsWith('{') && trimmedText.endsWith('}')) {
|
|
541
|
+
// JSON package format (correct format)
|
|
542
|
+
try {
|
|
543
|
+
const memoryData: MemoryContent = JSON.parse(trimmedText);
|
|
544
|
+
content = memoryData.content;
|
|
545
|
+
embedding = memoryData.embedding;
|
|
546
|
+
metadata = memoryData.metadata || {};
|
|
547
|
+
timestamp = memoryData.timestamp || Date.now();
|
|
548
|
+
|
|
549
|
+
if (!embedding || embedding.length !== 3072) {
|
|
550
|
+
throw new Error(`Invalid embedding in JSON: length=${embedding?.length || 0}`);
|
|
306
551
|
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
throw new Error('Binary, encrypted, or empty content - cannot index');
|
|
552
|
+
|
|
553
|
+
console.log(`[rebuildIndexNode] 📦 Format: JSON package`);
|
|
554
|
+
} catch (jsonError) {
|
|
555
|
+
throw new Error(`Invalid JSON structure: ${(jsonError as Error).message}`);
|
|
312
556
|
}
|
|
557
|
+
} else if (trimmedText.length > 0 && !trimmedText.includes('\x00') && trimmedText.length < 10000) {
|
|
558
|
+
// Plain text format - cannot index without embedding
|
|
559
|
+
throw new Error('Plain text format detected but no embedding available - skip');
|
|
560
|
+
} else {
|
|
561
|
+
throw new Error('Binary, encrypted, or empty content - cannot index');
|
|
562
|
+
}
|
|
313
563
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
564
|
+
// Add to HNSW index
|
|
565
|
+
await hnswService.addVector(
|
|
566
|
+
userAddress,
|
|
567
|
+
memory.vectorId,
|
|
568
|
+
embedding,
|
|
569
|
+
{
|
|
570
|
+
blobId: memory.blobId,
|
|
571
|
+
memoryObjectId: memory.id,
|
|
572
|
+
category: metadata.category || memory.category || tags?.['category'],
|
|
573
|
+
importance: metadata.importance || memory.importance || parseInt(tags?.['importance'] || '5'),
|
|
574
|
+
topic: metadata.topic || tags?.['topic'] || '',
|
|
575
|
+
timestamp,
|
|
576
|
+
content,
|
|
577
|
+
isEncrypted: false
|
|
578
|
+
}
|
|
579
|
+
);
|
|
330
580
|
|
|
331
|
-
|
|
332
|
-
|
|
581
|
+
indexedCount++;
|
|
582
|
+
console.log(`[rebuildIndexNode] ✓ Indexed: "${content.substring(0, 30)}..."`);
|
|
333
583
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
}
|
|
584
|
+
} catch (error: any) {
|
|
585
|
+
failedCount++;
|
|
586
|
+
const errorMsg = error.message || String(error);
|
|
587
|
+
errors.push({ blobId: memory.blobId, error: errorMsg });
|
|
588
|
+
console.error(`[rebuildIndexNode] ✗ Failed: ${errorMsg}`);
|
|
340
589
|
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
341
592
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
593
|
+
// ==================== QUILT MEMORIES ====================
|
|
594
|
+
// Process additional Quilts that may not have on-chain Memory objects
|
|
595
|
+
let quiltMemoriesTotal = 0;
|
|
596
|
+
let quiltMemoriesIndexed = 0;
|
|
597
|
+
|
|
598
|
+
if (quiltIds.length > 0) {
|
|
599
|
+
console.log(`\n[rebuildIndexNode] Processing ${quiltIds.length} additional Quilt(s)...`);
|
|
600
|
+
onProgress?.(processedCount, totalMemories + quiltIds.length, 'Processing Quilts...');
|
|
601
|
+
|
|
602
|
+
for (const quiltId of quiltIds) {
|
|
603
|
+
console.log(`[rebuildIndexNode] Processing Quilt: ${quiltId.substring(0, 30)}...`);
|
|
604
|
+
|
|
605
|
+
try {
|
|
606
|
+
// Fetch Quilt files
|
|
607
|
+
const blob = await walrusClient.walrus.getBlob({ blobId: quiltId });
|
|
608
|
+
const files = await blob.files();
|
|
609
|
+
console.log(`[rebuildIndexNode] 📥 Fetched Quilt: ${files.length} file(s)`);
|
|
610
|
+
|
|
611
|
+
// Process each file in the Quilt
|
|
612
|
+
for (let fileIdx = 0; fileIdx < files.length; fileIdx++) {
|
|
613
|
+
const file = files[fileIdx];
|
|
614
|
+
quiltMemoriesTotal++;
|
|
615
|
+
|
|
616
|
+
try {
|
|
617
|
+
const identifier = await file.getIdentifier() || `quilt-file-${fileIdx}`;
|
|
618
|
+
const tags = await file.getTags();
|
|
619
|
+
|
|
620
|
+
// Parse JSON content
|
|
621
|
+
const rawBytes = await file.bytes();
|
|
622
|
+
let rawText = new TextDecoder().decode(rawBytes);
|
|
623
|
+
|
|
624
|
+
// Trim trailing null bytes (Quilt corruption workaround)
|
|
625
|
+
let lastValidIndex = rawText.length - 1;
|
|
626
|
+
while (lastValidIndex >= 0 && rawText.charCodeAt(lastValidIndex) === 0) {
|
|
627
|
+
lastValidIndex--;
|
|
628
|
+
}
|
|
629
|
+
rawText = rawText.slice(0, lastValidIndex + 1);
|
|
346
630
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
631
|
+
if (!rawText.startsWith('{') || !rawText.endsWith('}')) {
|
|
632
|
+
throw new Error('Not a JSON file');
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
const memoryData: MemoryContent = JSON.parse(rawText);
|
|
636
|
+
|
|
637
|
+
if (!memoryData.embedding || memoryData.embedding.length === 0) {
|
|
638
|
+
throw new Error('No embedding in package');
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// Generate unique vector ID for Quilt memory
|
|
642
|
+
const vectorId = Date.now() % 4294967295 + fileIdx;
|
|
643
|
+
const memoryId = (memoryData as any).metadata?.memoryId || identifier.replace('.json', '');
|
|
644
|
+
|
|
645
|
+
// Add to HNSW index
|
|
646
|
+
await hnswService.addVector(
|
|
647
|
+
userAddress,
|
|
648
|
+
vectorId,
|
|
649
|
+
memoryData.embedding,
|
|
650
|
+
{
|
|
651
|
+
blobId: quiltId,
|
|
652
|
+
memoryObjectId: memoryId,
|
|
653
|
+
category: memoryData.metadata?.category || tags?.['category'] || 'general',
|
|
654
|
+
importance: memoryData.metadata?.importance || parseInt(tags?.['importance'] || '3'),
|
|
655
|
+
topic: memoryData.metadata?.topic || tags?.['topic'] || '',
|
|
656
|
+
timestamp: memoryData.timestamp || Date.now(),
|
|
657
|
+
content: memoryData.content || '[encrypted]',
|
|
658
|
+
isEncrypted: (memoryData as any).encrypted === true,
|
|
659
|
+
quiltId,
|
|
660
|
+
identifier
|
|
661
|
+
}
|
|
662
|
+
);
|
|
663
|
+
|
|
664
|
+
quiltMemoriesIndexed++;
|
|
665
|
+
console.log(`[rebuildIndexNode] ✓ Indexed Quilt file: ${identifier}`);
|
|
666
|
+
|
|
667
|
+
} catch (fileError: any) {
|
|
668
|
+
const errorMsg = fileError.message || String(fileError);
|
|
669
|
+
errors.push({ blobId: quiltId, error: `File ${fileIdx}: ${errorMsg}` });
|
|
670
|
+
console.error(`[rebuildIndexNode] ✗ Failed file ${fileIdx}: ${errorMsg}`);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
} catch (quiltError: any) {
|
|
675
|
+
const errorMsg = quiltError.message || String(quiltError);
|
|
676
|
+
errors.push({ blobId: quiltId, error: `Quilt fetch failed: ${errorMsg}` });
|
|
677
|
+
console.error(`[rebuildIndexNode] ✗ Failed to fetch Quilt: ${errorMsg}`);
|
|
351
678
|
}
|
|
352
679
|
}
|
|
680
|
+
|
|
681
|
+
console.log(`[rebuildIndexNode] Quilt indexing complete: ${quiltMemoriesIndexed}/${quiltMemoriesTotal}`);
|
|
353
682
|
}
|
|
354
683
|
|
|
684
|
+
// Update totals
|
|
685
|
+
const finalTotal = totalMemories + quiltMemoriesTotal;
|
|
686
|
+
const finalIndexed = indexedCount + quiltMemoriesIndexed;
|
|
687
|
+
const finalFailed = failedCount + (quiltMemoriesTotal - quiltMemoriesIndexed);
|
|
688
|
+
|
|
689
|
+
timing.processingMs = Date.now() - processingStart;
|
|
690
|
+
console.log(`[rebuildIndexNode] ⏱️ Processing: ${timing.processingMs}ms`);
|
|
691
|
+
|
|
355
692
|
// Force save index
|
|
693
|
+
const saveStart = Date.now();
|
|
356
694
|
console.log('[rebuildIndexNode] Saving index to disk...');
|
|
357
|
-
onProgress?.(
|
|
695
|
+
onProgress?.(finalTotal, finalTotal, 'Saving index...');
|
|
358
696
|
await hnswService.flushBatch(userAddress);
|
|
697
|
+
timing.saveMs = Date.now() - saveStart;
|
|
698
|
+
console.log(`[rebuildIndexNode] ⏱️ Save: ${timing.saveMs}ms`);
|
|
359
699
|
|
|
360
700
|
const duration = Date.now() - startTime;
|
|
361
701
|
console.log('[rebuildIndexNode] Index rebuild complete!');
|
|
362
|
-
console.log(`[rebuildIndexNode]
|
|
702
|
+
console.log(`[rebuildIndexNode] On-chain: ${totalMemories}, Quilts: ${quiltMemoriesTotal}, Total indexed: ${finalIndexed}, Failed: ${finalFailed}`);
|
|
363
703
|
console.log(`[rebuildIndexNode] Duration: ${(duration / 1000).toFixed(2)}s`);
|
|
704
|
+
console.log(`[rebuildIndexNode] ⏱️ TIMING BREAKDOWN:`);
|
|
705
|
+
console.log(` Init: ${timing.initMs}ms (${((timing.initMs / duration) * 100).toFixed(1)}%)`);
|
|
706
|
+
console.log(` Blockchain: ${timing.blockchainFetchMs}ms (${((timing.blockchainFetchMs / duration) * 100).toFixed(1)}%)`);
|
|
707
|
+
console.log(` Walrus: ${timing.walrusFetchMs}ms (${((timing.walrusFetchMs / duration) * 100).toFixed(1)}%)`);
|
|
708
|
+
console.log(` Processing: ${timing.processingMs}ms (${((timing.processingMs / duration) * 100).toFixed(1)}%)`);
|
|
709
|
+
console.log(` Save: ${timing.saveMs}ms (${((timing.saveMs / duration) * 100).toFixed(1)}%)`);
|
|
364
710
|
|
|
365
711
|
return {
|
|
366
712
|
success: true,
|
|
367
|
-
totalMemories,
|
|
368
|
-
indexedMemories:
|
|
369
|
-
failedMemories:
|
|
713
|
+
totalMemories: finalTotal,
|
|
714
|
+
indexedMemories: finalIndexed,
|
|
715
|
+
failedMemories: finalFailed,
|
|
370
716
|
errors,
|
|
371
|
-
duration
|
|
717
|
+
duration,
|
|
718
|
+
timing
|
|
372
719
|
};
|
|
373
720
|
|
|
374
721
|
} catch (error: any) {
|