@cmdoss/memwal-sdk 0.6.2 → 0.8.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 +310 -181
- package/dist/ai-sdk/tools.d.ts +2 -2
- package/dist/ai-sdk/tools.js +2 -2
- package/dist/client/ClientMemoryManager.js +2 -2
- package/dist/client/ClientMemoryManager.js.map +1 -1
- package/dist/client/PersonalDataWallet.d.ts.map +1 -1
- package/dist/client/SimplePDWClient.d.ts +29 -1
- package/dist/client/SimplePDWClient.d.ts.map +1 -1
- package/dist/client/SimplePDWClient.js +45 -13
- 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 +31 -0
- package/dist/client/namespaces/MemoryNamespace.d.ts.map +1 -1
- package/dist/client/namespaces/MemoryNamespace.js +272 -39
- 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 +12 -2
- package/dist/client/namespaces/consolidated/BlockchainNamespace.d.ts.map +1 -1
- package/dist/client/namespaces/consolidated/BlockchainNamespace.js +62 -4
- package/dist/client/namespaces/consolidated/BlockchainNamespace.js.map +1 -1
- package/dist/client/namespaces/consolidated/StorageNamespace.d.ts +67 -2
- package/dist/client/namespaces/consolidated/StorageNamespace.d.ts.map +1 -1
- package/dist/client/namespaces/consolidated/StorageNamespace.js +549 -16
- package/dist/client/namespaces/consolidated/StorageNamespace.js.map +1 -1
- package/dist/config/ConfigurationHelper.js +61 -61
- package/dist/config/defaults.js +2 -2
- package/dist/config/defaults.js.map +1 -1
- package/dist/graph/GraphService.js +21 -21
- 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/infrastructure/seal/EncryptionService.d.ts +9 -5
- package/dist/infrastructure/seal/EncryptionService.d.ts.map +1 -1
- package/dist/infrastructure/seal/EncryptionService.js +37 -15
- package/dist/infrastructure/seal/EncryptionService.js.map +1 -1
- package/dist/infrastructure/seal/SealService.d.ts +13 -5
- package/dist/infrastructure/seal/SealService.d.ts.map +1 -1
- package/dist/infrastructure/seal/SealService.js +36 -34
- package/dist/infrastructure/seal/SealService.js.map +1 -1
- package/dist/langchain/createPDWRAG.js +30 -30
- package/dist/retrieval/MemoryDecryptionPipeline.d.ts.map +1 -1
- package/dist/retrieval/MemoryDecryptionPipeline.js +2 -1
- package/dist/retrieval/MemoryDecryptionPipeline.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/CapabilityService.d.ts.map +1 -1
- package/dist/services/CapabilityService.js +30 -14
- package/dist/services/CapabilityService.js.map +1 -1
- package/dist/services/CrossContextPermissionService.d.ts.map +1 -1
- package/dist/services/CrossContextPermissionService.js +9 -7
- package/dist/services/CrossContextPermissionService.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/EncryptionService.d.ts.map +1 -1
- package/dist/services/EncryptionService.js +6 -5
- package/dist/services/EncryptionService.js.map +1 -1
- package/dist/services/GeminiAIService.js +309 -309
- 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 +11 -0
- package/dist/services/StorageService.d.ts.map +1 -1
- package/dist/services/StorageService.js +73 -10
- package/dist/services/StorageService.js.map +1 -1
- package/dist/services/TransactionService.d.ts +20 -0
- package/dist/services/TransactionService.d.ts.map +1 -1
- package/dist/services/TransactionService.js +43 -0
- package/dist/services/TransactionService.js.map +1 -1
- package/dist/services/ViewService.js +2 -2
- package/dist/services/ViewService.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/PermissionService.ts +635 -635
- package/src/aggregation/AggregationService.ts +389 -389
- package/src/ai-sdk/PDWVectorStore.ts +715 -715
- package/src/ai-sdk/index.ts +65 -65
- package/src/ai-sdk/tools.ts +460 -460
- package/src/ai-sdk/types.ts +404 -404
- package/src/batch/BatchManager.ts +597 -597
- package/src/batch/BatchingService.ts +429 -429
- package/src/batch/MemoryProcessingCache.ts +492 -492
- package/src/batch/index.ts +30 -30
- package/src/browser.ts +200 -200
- package/src/client/ClientMemoryManager.ts +987 -987
- package/src/client/PersonalDataWallet.ts +345 -345
- package/src/client/SimplePDWClient.ts +1289 -1222
- package/src/client/factory.ts +154 -154
- package/src/client/namespaces/AnalyticsNamespace.ts +377 -377
- package/src/client/namespaces/BatchNamespace.ts +356 -356
- package/src/client/namespaces/CacheNamespace.ts +123 -123
- package/src/client/namespaces/CapabilityNamespace.ts +217 -217
- package/src/client/namespaces/ClassifyNamespace.ts +169 -169
- package/src/client/namespaces/ContextNamespace.ts +297 -297
- package/src/client/namespaces/EmbeddingsNamespace.ts +99 -99
- package/src/client/namespaces/EncryptionNamespace.ts +221 -221
- package/src/client/namespaces/GraphNamespace.ts +468 -468
- package/src/client/namespaces/IndexNamespace.ts +361 -361
- package/src/client/namespaces/MemoryNamespace.ts +1422 -1135
- package/src/client/namespaces/PermissionsNamespace.ts +254 -254
- package/src/client/namespaces/PipelineNamespace.ts +220 -220
- package/src/client/namespaces/SearchNamespace.ts +1049 -1049
- package/src/client/namespaces/StorageNamespace.ts +458 -458
- package/src/client/namespaces/TxNamespace.ts +260 -260
- package/src/client/namespaces/WalletNamespace.ts +243 -243
- package/src/client/namespaces/consolidated/AINamespace.ts +449 -449
- package/src/client/namespaces/consolidated/BlockchainNamespace.ts +607 -546
- package/src/client/namespaces/consolidated/SecurityNamespace.ts +648 -648
- package/src/client/namespaces/consolidated/StorageNamespace.ts +1141 -497
- package/src/client/namespaces/consolidated/index.ts +39 -39
- package/src/client/signers/KeypairSigner.ts +108 -108
- package/src/client/signers/UnifiedSigner.ts +110 -110
- package/src/client/signers/WalletAdapterSigner.ts +159 -159
- package/src/client/signers/index.ts +26 -26
- package/src/config/ConfigurationHelper.ts +412 -412
- package/src/config/defaults.ts +51 -51
- package/src/config/index.ts +8 -8
- package/src/config/validation.ts +70 -70
- package/src/core/index.ts +14 -14
- package/src/core/interfaces/IService.ts +307 -307
- package/src/core/interfaces/index.ts +8 -8
- package/src/core/types/capability.ts +297 -297
- package/src/core/types/index.ts +870 -870
- package/src/core/types/wallet.ts +270 -270
- package/src/core/types.ts +9 -9
- package/src/core/wallet.ts +222 -222
- package/src/embedding/index.ts +19 -19
- package/src/embedding/types.ts +357 -357
- package/src/errors/index.ts +602 -602
- package/src/errors/recovery.ts +461 -461
- package/src/errors/validation.ts +567 -567
- package/src/generated/pdw/capability.ts +319 -319
- package/src/graph/GraphService.ts +887 -887
- package/src/graph/KnowledgeGraphManager.ts +728 -728
- package/src/graph/index.ts +25 -25
- package/src/index.ts +498 -474
- package/src/infrastructure/index.ts +22 -22
- package/src/infrastructure/seal/EncryptionService.ts +628 -603
- package/src/infrastructure/seal/SealService.ts +613 -615
- package/src/infrastructure/seal/index.ts +9 -9
- package/src/infrastructure/sui/BlockchainManager.ts +627 -627
- package/src/infrastructure/sui/SuiService.ts +888 -888
- package/src/infrastructure/sui/index.ts +9 -9
- package/src/infrastructure/walrus/StorageManager.ts +604 -604
- package/src/infrastructure/walrus/WalrusStorageService.ts +612 -612
- package/src/infrastructure/walrus/index.ts +9 -9
- package/src/langchain/PDWEmbeddings.ts +145 -145
- package/src/langchain/PDWVectorStore.ts +456 -456
- package/src/langchain/createPDWRAG.ts +303 -303
- package/src/langchain/index.ts +47 -47
- package/src/permissions/ConsentRepository.browser.ts +249 -249
- package/src/permissions/ConsentRepository.ts +364 -364
- package/src/pipeline/MemoryPipeline.ts +862 -862
- package/src/pipeline/PipelineManager.ts +683 -683
- package/src/pipeline/index.ts +26 -26
- package/src/retrieval/AdvancedSearchService.ts +629 -629
- package/src/retrieval/MemoryAnalyticsService.ts +711 -711
- package/src/retrieval/MemoryDecryptionPipeline.ts +825 -824
- package/src/retrieval/MemoryRetrievalService.ts +904 -830
- package/src/retrieval/index.ts +42 -42
- package/src/services/BatchService.ts +352 -352
- package/src/services/CapabilityService.ts +464 -448
- package/src/services/ClassifierService.ts +465 -465
- package/src/services/CrossContextPermissionService.ts +486 -484
- package/src/services/EmbeddingService.ts +771 -706
- package/src/services/EncryptionService.ts +712 -711
- package/src/services/GeminiAIService.ts +753 -753
- package/src/services/IndexManager.ts +977 -1004
- package/src/services/MemoryIndexService.ts +1003 -1003
- package/src/services/MemoryService.ts +369 -369
- package/src/services/QueryService.ts +890 -890
- package/src/services/StorageService.ts +1182 -1111
- package/src/services/TransactionService.ts +838 -790
- package/src/services/VectorService.ts +462 -462
- package/src/services/ViewService.ts +484 -484
- package/src/services/index.ts +25 -25
- package/src/services/storage/BlobAttributesManager.ts +333 -333
- package/src/services/storage/KnowledgeGraphManager.ts +425 -425
- package/src/services/storage/MemorySearchManager.ts +387 -387
- package/src/services/storage/QuiltBatchManager.ts +1130 -660
- package/src/services/storage/WalrusMetadataManager.ts +268 -268
- package/src/services/storage/WalrusStorageManager.ts +287 -287
- package/src/services/storage/index.ts +57 -52
- package/src/types/index.ts +13 -13
- package/src/utils/LRUCache.ts +378 -0
- package/src/utils/index.ts +76 -68
- package/src/utils/memoryIndexOnChain.ts +507 -0
- package/src/utils/rebuildIndex.ts +290 -290
- package/src/utils/rebuildIndexNode.ts +771 -424
- package/src/vector/BrowserHnswIndexService.ts +758 -758
- package/src/vector/HnswWasmService.ts +731 -679
- package/src/vector/IHnswService.ts +233 -224
- package/src/vector/NodeHnswService.ts +833 -735
- package/src/vector/VectorManager.ts +478 -478
- package/src/vector/createHnswService.ts +135 -135
- package/src/vector/index.ts +56 -56
- package/src/wallet/ContextWalletService.ts +656 -656
- package/src/wallet/MainWalletService.ts +317 -317
|
@@ -1,613 +1,613 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* WalrusStorageService - Production Decentralized Storage
|
|
3
|
-
*
|
|
4
|
-
* Walrus client integration with SEAL encryption,
|
|
5
|
-
* content verification, and standardized tagging per https://docs.wal.app/
|
|
6
|
-
*
|
|
7
|
-
* Uses @mysten/walrus SDK for writeBlob/readBlob operations when signer is available.
|
|
8
|
-
* Falls back to REST API for read-only operations without signer.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { getFullnodeUrl, SuiClient } from '@mysten/sui/client';
|
|
12
|
-
import { walrus } from '@mysten/walrus';
|
|
13
|
-
import type { Signer } from '@mysten/sui/cryptography';
|
|
14
|
-
import type { SealService } from '../seal/SealService';
|
|
15
|
-
|
|
16
|
-
export interface WalrusConfig {
|
|
17
|
-
network?: 'testnet' | 'mainnet';
|
|
18
|
-
adminAddress?: string;
|
|
19
|
-
storageEpochs?: number;
|
|
20
|
-
uploadRelayHost?: string;
|
|
21
|
-
aggregatorHost?: string;
|
|
22
|
-
retryAttempts?: number;
|
|
23
|
-
timeoutMs?: number;
|
|
24
|
-
sealService?: SealService;
|
|
25
|
-
signer?: Signer; // Required for writeBlob operations
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface MemoryMetadata {
|
|
29
|
-
contentType: string;
|
|
30
|
-
contentSize: number;
|
|
31
|
-
contentHash: string;
|
|
32
|
-
category: string;
|
|
33
|
-
topic: string;
|
|
34
|
-
importance: number; // 1-10 scale
|
|
35
|
-
embeddingBlobId?: string;
|
|
36
|
-
embeddingDimension: number;
|
|
37
|
-
createdTimestamp: number;
|
|
38
|
-
updatedTimestamp?: number;
|
|
39
|
-
customMetadata?: Record<string, string>;
|
|
40
|
-
isEncrypted?: boolean;
|
|
41
|
-
encryptionType?: string;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export interface WalrusUploadResult {
|
|
45
|
-
blobId: string;
|
|
46
|
-
metadata: MemoryMetadata;
|
|
47
|
-
embeddingBlobId?: string;
|
|
48
|
-
isEncrypted: boolean;
|
|
49
|
-
backupKey?: string;
|
|
50
|
-
storageEpochs: number;
|
|
51
|
-
uploadTimeMs: number;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export interface WalrusRetrievalResult {
|
|
55
|
-
content: string;
|
|
56
|
-
metadata: MemoryMetadata;
|
|
57
|
-
isDecrypted: boolean;
|
|
58
|
-
retrievalTimeMs: number;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export interface BlobInfo {
|
|
62
|
-
blobId: string;
|
|
63
|
-
contentType: string;
|
|
64
|
-
contentLength: number;
|
|
65
|
-
contentHash: string;
|
|
66
|
-
metadata: Record<string, string>;
|
|
67
|
-
tags: string[];
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
class WalrusError extends Error {
|
|
71
|
-
public readonly cause?: unknown;
|
|
72
|
-
|
|
73
|
-
constructor(message: string, cause?: unknown) {
|
|
74
|
-
super(message);
|
|
75
|
-
this.name = 'WalrusError';
|
|
76
|
-
this.cause = cause;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export interface WalrusStats {
|
|
81
|
-
totalUploads: number;
|
|
82
|
-
totalRetrievals: number;
|
|
83
|
-
successfulUploads: number;
|
|
84
|
-
failedUploads: number;
|
|
85
|
-
cacheHitRate: number;
|
|
86
|
-
averageUploadTime: number;
|
|
87
|
-
averageRetrievalTime: number;
|
|
88
|
-
localFallbackCount: number;
|
|
89
|
-
totalStorageUsed: number;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
interface CachedBlob {
|
|
93
|
-
content: Uint8Array;
|
|
94
|
-
contentType: string;
|
|
95
|
-
timestamp: Date;
|
|
96
|
-
metadata: Record<string, string>;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Production-ready Walrus storage service using @mysten/walrus SDK
|
|
101
|
-
*/
|
|
102
|
-
export class WalrusStorageService {
|
|
103
|
-
private readonly config: Omit<Required<WalrusConfig>, 'sealService' | 'signer'> & {
|
|
104
|
-
sealService?: SealService;
|
|
105
|
-
signer?: Signer;
|
|
106
|
-
};
|
|
107
|
-
private readonly cache = new Map<string, CachedBlob>();
|
|
108
|
-
private stats: WalrusStats = {
|
|
109
|
-
totalUploads: 0,
|
|
110
|
-
totalRetrievals: 0,
|
|
111
|
-
successfulUploads: 0,
|
|
112
|
-
failedUploads: 0,
|
|
113
|
-
cacheHitRate: 0,
|
|
114
|
-
averageUploadTime: 0,
|
|
115
|
-
averageRetrievalTime: 0,
|
|
116
|
-
localFallbackCount: 0,
|
|
117
|
-
totalStorageUsed: 0
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
private readonly CACHE_TTL_MS = 30 * 60 * 1000; // 30 minutes
|
|
121
|
-
private sealService?: SealService;
|
|
122
|
-
private walrusClient: ReturnType<typeof this.createWalrusClient> | null = null;
|
|
123
|
-
|
|
124
|
-
constructor(config: Partial<WalrusConfig> = {}) {
|
|
125
|
-
const network = config.network || 'testnet';
|
|
126
|
-
|
|
127
|
-
this.config = {
|
|
128
|
-
network,
|
|
129
|
-
adminAddress: config.adminAddress || '',
|
|
130
|
-
storageEpochs: config.storageEpochs || 12,
|
|
131
|
-
uploadRelayHost: config.uploadRelayHost || 'https://upload-relay.testnet.walrus.space',
|
|
132
|
-
aggregatorHost: config.aggregatorHost || 'https://aggregator.walrus-testnet.walrus.space',
|
|
133
|
-
retryAttempts: config.retryAttempts || 3,
|
|
134
|
-
timeoutMs: config.timeoutMs || 60000,
|
|
135
|
-
sealService: config.sealService,
|
|
136
|
-
signer: config.signer
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
this.sealService = config.sealService;
|
|
140
|
-
|
|
141
|
-
// Initialize Walrus SDK client
|
|
142
|
-
this.walrusClient = this.createWalrusClient(network);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Create Walrus client using @mysten/walrus SDK
|
|
147
|
-
* Uses $extend to add walrus capabilities to SuiClient
|
|
148
|
-
*/
|
|
149
|
-
private createWalrusClient(network: 'testnet' | 'mainnet') {
|
|
150
|
-
const suiClient = new SuiClient({
|
|
151
|
-
url: getFullnodeUrl(network)
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
// Extend SuiClient with Walrus capabilities using $extend
|
|
155
|
-
return suiClient.$extend(walrus({
|
|
156
|
-
network,
|
|
157
|
-
}));
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// ==================== PUBLIC API ====================
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Store memory with encryption and metadata
|
|
164
|
-
*/
|
|
165
|
-
async storeMemory(
|
|
166
|
-
content: string,
|
|
167
|
-
category: string,
|
|
168
|
-
options: {
|
|
169
|
-
topic?: string;
|
|
170
|
-
importance?: number;
|
|
171
|
-
customMetadata?: Record<string, string>;
|
|
172
|
-
contextId?: string;
|
|
173
|
-
appId?: string;
|
|
174
|
-
encrypt?: boolean;
|
|
175
|
-
userAddress?: string;
|
|
176
|
-
} = {}
|
|
177
|
-
): Promise<WalrusUploadResult> {
|
|
178
|
-
const startTime = Date.now();
|
|
179
|
-
this.stats.totalUploads++;
|
|
180
|
-
|
|
181
|
-
try {
|
|
182
|
-
const {
|
|
183
|
-
topic = `Memory about ${category}`,
|
|
184
|
-
importance = 5,
|
|
185
|
-
customMetadata = {},
|
|
186
|
-
contextId,
|
|
187
|
-
appId,
|
|
188
|
-
encrypt = false,
|
|
189
|
-
userAddress
|
|
190
|
-
} = options;
|
|
191
|
-
|
|
192
|
-
let processedContent = content;
|
|
193
|
-
let backupKey: string | undefined;
|
|
194
|
-
let isEncrypted = false;
|
|
195
|
-
|
|
196
|
-
// Use SEAL encryption if requested and available
|
|
197
|
-
if (encrypt && this.sealService && userAddress) {
|
|
198
|
-
const sessionConfig = {
|
|
199
|
-
address: userAddress,
|
|
200
|
-
packageId: this.config.adminAddress, // Use configured package ID
|
|
201
|
-
ttlMin: 60 // 1 hour session
|
|
202
|
-
};
|
|
203
|
-
const sessionKey = await this.sealService.createSession(sessionConfig);
|
|
204
|
-
const encryptResult = await this.sealService.encryptData({
|
|
205
|
-
data: new TextEncoder().encode(content),
|
|
206
|
-
id: userAddress,
|
|
207
|
-
threshold: 2 // Default threshold
|
|
208
|
-
});
|
|
209
|
-
processedContent = JSON.stringify(encryptResult);
|
|
210
|
-
backupKey = 'session-key-reference'; // Store session reference
|
|
211
|
-
isEncrypted = true;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
const metadata = this.createMetadataWithEmbedding(
|
|
215
|
-
processedContent,
|
|
216
|
-
category,
|
|
217
|
-
topic,
|
|
218
|
-
importance,
|
|
219
|
-
{
|
|
220
|
-
...customMetadata,
|
|
221
|
-
...(contextId && { 'context-id': contextId }),
|
|
222
|
-
...(appId && { 'app-id': appId }),
|
|
223
|
-
...(userAddress && { owner: userAddress }),
|
|
224
|
-
encrypted: isEncrypted.toString(),
|
|
225
|
-
...(isEncrypted && { 'encryption-type': 'seal' })
|
|
226
|
-
}
|
|
227
|
-
);
|
|
228
|
-
|
|
229
|
-
// Upload to Walrus with official client
|
|
230
|
-
const blobId = await this.uploadToWalrus(processedContent, metadata);
|
|
231
|
-
|
|
232
|
-
// Use Walrus blob_id as content hash (already content-addressed via blake2b256)
|
|
233
|
-
metadata.contentHash = blobId;
|
|
234
|
-
|
|
235
|
-
const uploadTimeMs = Date.now() - startTime;
|
|
236
|
-
this.stats.successfulUploads++;
|
|
237
|
-
this.updateAverageUploadTime(uploadTimeMs);
|
|
238
|
-
|
|
239
|
-
return {
|
|
240
|
-
blobId,
|
|
241
|
-
metadata,
|
|
242
|
-
isEncrypted,
|
|
243
|
-
backupKey,
|
|
244
|
-
storageEpochs: this.config.storageEpochs,
|
|
245
|
-
uploadTimeMs
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
} catch (error) {
|
|
249
|
-
this.stats.failedUploads++;
|
|
250
|
-
throw new WalrusError(
|
|
251
|
-
`Failed to store memory: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
252
|
-
error
|
|
253
|
-
);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Retrieve memory with decryption
|
|
259
|
-
*/
|
|
260
|
-
async retrieveMemory(
|
|
261
|
-
blobId: string,
|
|
262
|
-
options: {
|
|
263
|
-
userAddress?: string;
|
|
264
|
-
sessionKey?: any; // SEAL session key
|
|
265
|
-
txBytes?: Uint8Array;
|
|
266
|
-
} = {}
|
|
267
|
-
): Promise<WalrusRetrievalResult> {
|
|
268
|
-
const startTime = Date.now();
|
|
269
|
-
this.stats.totalRetrievals++;
|
|
270
|
-
|
|
271
|
-
try {
|
|
272
|
-
// Check cache first
|
|
273
|
-
const cached = this.cache.get(blobId);
|
|
274
|
-
if (cached && this.isCacheValid(cached.timestamp)) {
|
|
275
|
-
this.stats.cacheHitRate = (this.stats.cacheHitRate * (this.stats.totalRetrievals - 1) + 1) / this.stats.totalRetrievals;
|
|
276
|
-
|
|
277
|
-
const content = new TextDecoder().decode(cached.content);
|
|
278
|
-
return {
|
|
279
|
-
content,
|
|
280
|
-
metadata: cached.metadata as unknown as MemoryMetadata,
|
|
281
|
-
isDecrypted: false,
|
|
282
|
-
retrievalTimeMs: Date.now() - startTime
|
|
283
|
-
};
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// Retrieve from Walrus
|
|
287
|
-
const { content, metadata } = await this.retrieveFromWalrus(blobId);
|
|
288
|
-
|
|
289
|
-
let processedContent = content;
|
|
290
|
-
let isDecrypted = false;
|
|
291
|
-
|
|
292
|
-
// Decrypt if needed
|
|
293
|
-
if (metadata.isEncrypted && metadata.encryptionType === 'seal' && this.sealService) {
|
|
294
|
-
const { userAddress, sessionKey, txBytes } = options;
|
|
295
|
-
if (userAddress && sessionKey && txBytes) {
|
|
296
|
-
try {
|
|
297
|
-
const encryptedData = JSON.parse(content);
|
|
298
|
-
const decryptedBytes = await this.sealService.decryptData({
|
|
299
|
-
encryptedObject: new Uint8Array(encryptedData),
|
|
300
|
-
sessionKey,
|
|
301
|
-
txBytes
|
|
302
|
-
});
|
|
303
|
-
processedContent = new TextDecoder().decode(decryptedBytes);
|
|
304
|
-
isDecrypted = true;
|
|
305
|
-
} catch (decryptError) {
|
|
306
|
-
console.warn('SEAL decryption failed, returning encrypted content:', decryptError);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
const retrievalTimeMs = Date.now() - startTime;
|
|
312
|
-
this.updateAverageRetrievalTime(retrievalTimeMs);
|
|
313
|
-
|
|
314
|
-
return {
|
|
315
|
-
content: processedContent,
|
|
316
|
-
metadata,
|
|
317
|
-
isDecrypted,
|
|
318
|
-
retrievalTimeMs
|
|
319
|
-
};
|
|
320
|
-
|
|
321
|
-
} catch (error) {
|
|
322
|
-
throw new WalrusError(
|
|
323
|
-
`Failed to retrieve memory ${blobId}: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
324
|
-
error
|
|
325
|
-
);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
* Get service statistics
|
|
331
|
-
*/
|
|
332
|
-
getStats(): WalrusStats {
|
|
333
|
-
return { ...this.stats };
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
/**
|
|
337
|
-
* Clear cache
|
|
338
|
-
*/
|
|
339
|
-
clearCache(): void {
|
|
340
|
-
this.cache.clear();
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/**
|
|
344
|
-
* Retrieve content by blobId with optional decryption
|
|
345
|
-
*/
|
|
346
|
-
async retrieveContent(blobId: string, decryptionKey?: string | Uint8Array): Promise<{
|
|
347
|
-
content: string;
|
|
348
|
-
metadata: MemoryMetadata;
|
|
349
|
-
retrievalTimeMs: number;
|
|
350
|
-
isFromCache: boolean;
|
|
351
|
-
}> {
|
|
352
|
-
const startTime = Date.now();
|
|
353
|
-
|
|
354
|
-
try {
|
|
355
|
-
const result = await this.retrieveFromWalrus(blobId);
|
|
356
|
-
|
|
357
|
-
return {
|
|
358
|
-
content: result.content,
|
|
359
|
-
metadata: result.metadata,
|
|
360
|
-
retrievalTimeMs: Date.now() - startTime,
|
|
361
|
-
isFromCache: false // TODO: implement cache checking
|
|
362
|
-
};
|
|
363
|
-
} catch (error) {
|
|
364
|
-
throw new Error(`Failed to retrieve content: ${error}`);
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
/**
|
|
369
|
-
* List blobs for a specific user
|
|
370
|
-
*/
|
|
371
|
-
async listUserBlobs(userId: string, options: {
|
|
372
|
-
category?: string;
|
|
373
|
-
limit?: number;
|
|
374
|
-
offset?: number;
|
|
375
|
-
sortBy?: 'date' | 'size' | 'importance';
|
|
376
|
-
filters?: Record<string, any>;
|
|
377
|
-
} = {}): Promise<{ blobs: BlobInfo[]; totalCount: number }> {
|
|
378
|
-
// TODO: Implement actual Walrus listing when API is available
|
|
379
|
-
// For now, return empty result as this would require backend indexing
|
|
380
|
-
console.warn('listUserBlobs not yet implemented - requires Walrus indexing service');
|
|
381
|
-
return {
|
|
382
|
-
blobs: [],
|
|
383
|
-
totalCount: 0
|
|
384
|
-
};
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
/**
|
|
388
|
-
* Delete a blob by ID
|
|
389
|
-
*/
|
|
390
|
-
async deleteBlob(blobId: string): Promise<boolean> {
|
|
391
|
-
// TODO: Implement actual Walrus deletion when API is available
|
|
392
|
-
// Walrus typically doesn't support deletion, but this could mark as deleted
|
|
393
|
-
console.warn('deleteBlob not yet implemented - Walrus typically immutable');
|
|
394
|
-
return false;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
/**
|
|
398
|
-
* Check Walrus service availability
|
|
399
|
-
*/
|
|
400
|
-
async checkWalrusAvailability(): Promise<boolean> {
|
|
401
|
-
// TODO: Implement proper availability check with official client
|
|
402
|
-
console.warn('checkWalrusAvailability not yet implemented - assuming available for testing');
|
|
403
|
-
return true; // Return true to allow tests to run, even though implementation is placeholder
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
/**
|
|
407
|
-
* Get cache information
|
|
408
|
-
*/
|
|
409
|
-
getCacheInfo(): {
|
|
410
|
-
size: number;
|
|
411
|
-
maxSize: number;
|
|
412
|
-
hitRate: number;
|
|
413
|
-
entries: number;
|
|
414
|
-
} {
|
|
415
|
-
return {
|
|
416
|
-
size: this.cache.size, // Approximate
|
|
417
|
-
maxSize: 100, // Default cache size
|
|
418
|
-
hitRate: this.stats.cacheHitRate,
|
|
419
|
-
entries: this.cache.size
|
|
420
|
-
};
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
/**
|
|
424
|
-
* Upload content with metadata
|
|
425
|
-
*/
|
|
426
|
-
async uploadContentWithMetadata(
|
|
427
|
-
content: string,
|
|
428
|
-
userId: string,
|
|
429
|
-
options: {
|
|
430
|
-
category?: string;
|
|
431
|
-
topic?: string;
|
|
432
|
-
importance?: number;
|
|
433
|
-
additionalTags?: Record<string, string>;
|
|
434
|
-
enableEncryption?: boolean;
|
|
435
|
-
}
|
|
436
|
-
): Promise<{
|
|
437
|
-
blobId: string;
|
|
438
|
-
metadata: MemoryMetadata;
|
|
439
|
-
uploadTimeMs: number;
|
|
440
|
-
isEncrypted: boolean;
|
|
441
|
-
}> {
|
|
442
|
-
const startTime = Date.now();
|
|
443
|
-
|
|
444
|
-
try {
|
|
445
|
-
// Upload to Walrus first to get blob_id (which serves as content hash)
|
|
446
|
-
const tempMetadata: MemoryMetadata = {
|
|
447
|
-
contentType: 'application/json',
|
|
448
|
-
contentSize: content.length,
|
|
449
|
-
contentHash: '', // Will be set to blobId below
|
|
450
|
-
category: options.category || 'general',
|
|
451
|
-
topic: options.topic || 'misc',
|
|
452
|
-
importance: options.importance || 5,
|
|
453
|
-
embeddingDimension: 0,
|
|
454
|
-
createdTimestamp: Date.now(),
|
|
455
|
-
updatedTimestamp: Date.now(),
|
|
456
|
-
customMetadata: options.additionalTags || {},
|
|
457
|
-
isEncrypted: options.enableEncryption || false,
|
|
458
|
-
encryptionType: options.enableEncryption ? 'seal' : undefined
|
|
459
|
-
};
|
|
460
|
-
|
|
461
|
-
const blobId = await this.uploadToWalrus(content, tempMetadata);
|
|
462
|
-
|
|
463
|
-
// Use Walrus blob_id as content hash (already content-addressed via blake2b256)
|
|
464
|
-
const memoryMetadata: MemoryMetadata = {
|
|
465
|
-
...tempMetadata,
|
|
466
|
-
contentHash: blobId, // Walrus blob_id serves as content hash
|
|
467
|
-
};
|
|
468
|
-
|
|
469
|
-
return {
|
|
470
|
-
blobId,
|
|
471
|
-
metadata: memoryMetadata,
|
|
472
|
-
uploadTimeMs: Date.now() - startTime,
|
|
473
|
-
isEncrypted: memoryMetadata.isEncrypted || false
|
|
474
|
-
};
|
|
475
|
-
} catch (error) {
|
|
476
|
-
throw new Error(`Failed to upload content: ${error}`);
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
// ==================== PRIVATE METHODS ====================
|
|
481
|
-
|
|
482
|
-
private createMetadataWithEmbedding(
|
|
483
|
-
content: string,
|
|
484
|
-
category: string,
|
|
485
|
-
topic: string,
|
|
486
|
-
importance: number,
|
|
487
|
-
customMetadata: Record<string, string>
|
|
488
|
-
): MemoryMetadata {
|
|
489
|
-
const contentBuffer = Buffer.from(content, 'utf-8');
|
|
490
|
-
const timestamp = Date.now();
|
|
491
|
-
|
|
492
|
-
return {
|
|
493
|
-
contentType: 'text/plain',
|
|
494
|
-
contentSize: contentBuffer.length,
|
|
495
|
-
contentHash: '', // Will be set to blobId after upload
|
|
496
|
-
category,
|
|
497
|
-
topic: topic || `Memory about ${category}`,
|
|
498
|
-
importance: Math.max(1, Math.min(10, importance)),
|
|
499
|
-
embeddingDimension: 3072,
|
|
500
|
-
createdTimestamp: timestamp,
|
|
501
|
-
customMetadata
|
|
502
|
-
};
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
private async uploadToWalrus(content: string, _metadata: MemoryMetadata): Promise<string> {
|
|
506
|
-
// Use Walrus SDK writeBlob when signer is available
|
|
507
|
-
if (this.walrusClient && this.config.signer) {
|
|
508
|
-
try {
|
|
509
|
-
const blob = new TextEncoder().encode(content);
|
|
510
|
-
const { blobId } = await this.walrusClient.walrus.writeBlob({
|
|
511
|
-
blob,
|
|
512
|
-
deletable: false,
|
|
513
|
-
epochs: this.config.storageEpochs,
|
|
514
|
-
signer: this.config.signer
|
|
515
|
-
});
|
|
516
|
-
return blobId;
|
|
517
|
-
} catch (error) {
|
|
518
|
-
throw new WalrusError(`Walrus SDK upload failed: ${error instanceof Error ? error.message : 'Unknown error'}`, error);
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
// Fallback to REST API if no signer (read-only mode or relay upload)
|
|
523
|
-
try {
|
|
524
|
-
const response = await fetch(`${this.config.uploadRelayHost}/v1/store?epochs=${this.config.storageEpochs}`, {
|
|
525
|
-
method: 'PUT',
|
|
526
|
-
headers: {
|
|
527
|
-
'Content-Type': 'application/octet-stream'
|
|
528
|
-
},
|
|
529
|
-
body: new TextEncoder().encode(content)
|
|
530
|
-
});
|
|
531
|
-
|
|
532
|
-
if (!response.ok) {
|
|
533
|
-
throw new Error(`Upload failed: ${response.status} ${response.statusText}`);
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
const result = await response.json();
|
|
537
|
-
// Handle both newlyCreated and alreadyCertified responses
|
|
538
|
-
const newBlob = result.newlyCreated?.blobObject;
|
|
539
|
-
const certifiedBlob = result.alreadyCertified?.blobId;
|
|
540
|
-
return newBlob?.blobId || certifiedBlob || result.blobId;
|
|
541
|
-
} catch (error) {
|
|
542
|
-
throw new WalrusError(`Walrus REST upload failed: ${error instanceof Error ? error.message : 'Unknown error'}`, error);
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
private async retrieveFromWalrus(blobId: string): Promise<{ content: string; metadata: MemoryMetadata }> {
|
|
547
|
-
// Use Walrus SDK readBlob when client is available
|
|
548
|
-
if (this.walrusClient) {
|
|
549
|
-
try {
|
|
550
|
-
const blob = await this.walrusClient.walrus.readBlob({ blobId });
|
|
551
|
-
const content = new TextDecoder().decode(blob);
|
|
552
|
-
|
|
553
|
-
const metadata: MemoryMetadata = {
|
|
554
|
-
contentType: 'text/plain',
|
|
555
|
-
contentSize: content.length,
|
|
556
|
-
contentHash: blobId,
|
|
557
|
-
category: 'unknown',
|
|
558
|
-
topic: 'Retrieved memory',
|
|
559
|
-
importance: 5,
|
|
560
|
-
embeddingDimension: 3072,
|
|
561
|
-
createdTimestamp: Date.now()
|
|
562
|
-
};
|
|
563
|
-
|
|
564
|
-
return { content, metadata };
|
|
565
|
-
} catch (error) {
|
|
566
|
-
// Fall through to REST API on SDK error
|
|
567
|
-
console.warn('Walrus SDK readBlob failed, falling back to REST API:', error);
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
// Fallback to REST API
|
|
572
|
-
try {
|
|
573
|
-
const response = await fetch(`${this.config.aggregatorHost}/v1/${blobId}`);
|
|
574
|
-
|
|
575
|
-
if (!response.ok) {
|
|
576
|
-
throw new Error(`Retrieval failed: ${response.status} ${response.statusText}`);
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
const content = await response.text();
|
|
580
|
-
|
|
581
|
-
const metadata: MemoryMetadata = {
|
|
582
|
-
contentType: 'text/plain',
|
|
583
|
-
contentSize: content.length,
|
|
584
|
-
contentHash: blobId,
|
|
585
|
-
category: 'unknown',
|
|
586
|
-
topic: 'Retrieved memory',
|
|
587
|
-
importance: 5,
|
|
588
|
-
embeddingDimension: 3072,
|
|
589
|
-
createdTimestamp: Date.now()
|
|
590
|
-
};
|
|
591
|
-
|
|
592
|
-
return { content, metadata };
|
|
593
|
-
} catch (error) {
|
|
594
|
-
throw new WalrusError(`Walrus retrieval failed: ${error instanceof Error ? error.message : 'Unknown error'}`, error);
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
private isCacheValid(timestamp: Date): boolean {
|
|
599
|
-
return Date.now() - timestamp.getTime() < this.CACHE_TTL_MS;
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
private updateAverageUploadTime(newTime: number): void {
|
|
603
|
-
const totalUploads = this.stats.successfulUploads;
|
|
604
|
-
this.stats.averageUploadTime =
|
|
605
|
-
(this.stats.averageUploadTime * (totalUploads - 1) + newTime) / totalUploads;
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
private updateAverageRetrievalTime(newTime: number): void {
|
|
609
|
-
const totalRetrievals = this.stats.totalRetrievals;
|
|
610
|
-
this.stats.averageRetrievalTime =
|
|
611
|
-
(this.stats.averageRetrievalTime * (totalRetrievals - 1) + newTime) / totalRetrievals;
|
|
612
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* WalrusStorageService - Production Decentralized Storage
|
|
3
|
+
*
|
|
4
|
+
* Walrus client integration with SEAL encryption,
|
|
5
|
+
* content verification, and standardized tagging per https://docs.wal.app/
|
|
6
|
+
*
|
|
7
|
+
* Uses @mysten/walrus SDK for writeBlob/readBlob operations when signer is available.
|
|
8
|
+
* Falls back to REST API for read-only operations without signer.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { getFullnodeUrl, SuiClient } from '@mysten/sui/client';
|
|
12
|
+
import { walrus } from '@mysten/walrus';
|
|
13
|
+
import type { Signer } from '@mysten/sui/cryptography';
|
|
14
|
+
import type { SealService } from '../seal/SealService';
|
|
15
|
+
|
|
16
|
+
export interface WalrusConfig {
|
|
17
|
+
network?: 'testnet' | 'mainnet';
|
|
18
|
+
adminAddress?: string;
|
|
19
|
+
storageEpochs?: number;
|
|
20
|
+
uploadRelayHost?: string;
|
|
21
|
+
aggregatorHost?: string;
|
|
22
|
+
retryAttempts?: number;
|
|
23
|
+
timeoutMs?: number;
|
|
24
|
+
sealService?: SealService;
|
|
25
|
+
signer?: Signer; // Required for writeBlob operations
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface MemoryMetadata {
|
|
29
|
+
contentType: string;
|
|
30
|
+
contentSize: number;
|
|
31
|
+
contentHash: string;
|
|
32
|
+
category: string;
|
|
33
|
+
topic: string;
|
|
34
|
+
importance: number; // 1-10 scale
|
|
35
|
+
embeddingBlobId?: string;
|
|
36
|
+
embeddingDimension: number;
|
|
37
|
+
createdTimestamp: number;
|
|
38
|
+
updatedTimestamp?: number;
|
|
39
|
+
customMetadata?: Record<string, string>;
|
|
40
|
+
isEncrypted?: boolean;
|
|
41
|
+
encryptionType?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface WalrusUploadResult {
|
|
45
|
+
blobId: string;
|
|
46
|
+
metadata: MemoryMetadata;
|
|
47
|
+
embeddingBlobId?: string;
|
|
48
|
+
isEncrypted: boolean;
|
|
49
|
+
backupKey?: string;
|
|
50
|
+
storageEpochs: number;
|
|
51
|
+
uploadTimeMs: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface WalrusRetrievalResult {
|
|
55
|
+
content: string;
|
|
56
|
+
metadata: MemoryMetadata;
|
|
57
|
+
isDecrypted: boolean;
|
|
58
|
+
retrievalTimeMs: number;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface BlobInfo {
|
|
62
|
+
blobId: string;
|
|
63
|
+
contentType: string;
|
|
64
|
+
contentLength: number;
|
|
65
|
+
contentHash: string;
|
|
66
|
+
metadata: Record<string, string>;
|
|
67
|
+
tags: string[];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
class WalrusError extends Error {
|
|
71
|
+
public readonly cause?: unknown;
|
|
72
|
+
|
|
73
|
+
constructor(message: string, cause?: unknown) {
|
|
74
|
+
super(message);
|
|
75
|
+
this.name = 'WalrusError';
|
|
76
|
+
this.cause = cause;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface WalrusStats {
|
|
81
|
+
totalUploads: number;
|
|
82
|
+
totalRetrievals: number;
|
|
83
|
+
successfulUploads: number;
|
|
84
|
+
failedUploads: number;
|
|
85
|
+
cacheHitRate: number;
|
|
86
|
+
averageUploadTime: number;
|
|
87
|
+
averageRetrievalTime: number;
|
|
88
|
+
localFallbackCount: number;
|
|
89
|
+
totalStorageUsed: number;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
interface CachedBlob {
|
|
93
|
+
content: Uint8Array;
|
|
94
|
+
contentType: string;
|
|
95
|
+
timestamp: Date;
|
|
96
|
+
metadata: Record<string, string>;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Production-ready Walrus storage service using @mysten/walrus SDK
|
|
101
|
+
*/
|
|
102
|
+
export class WalrusStorageService {
|
|
103
|
+
private readonly config: Omit<Required<WalrusConfig>, 'sealService' | 'signer'> & {
|
|
104
|
+
sealService?: SealService;
|
|
105
|
+
signer?: Signer;
|
|
106
|
+
};
|
|
107
|
+
private readonly cache = new Map<string, CachedBlob>();
|
|
108
|
+
private stats: WalrusStats = {
|
|
109
|
+
totalUploads: 0,
|
|
110
|
+
totalRetrievals: 0,
|
|
111
|
+
successfulUploads: 0,
|
|
112
|
+
failedUploads: 0,
|
|
113
|
+
cacheHitRate: 0,
|
|
114
|
+
averageUploadTime: 0,
|
|
115
|
+
averageRetrievalTime: 0,
|
|
116
|
+
localFallbackCount: 0,
|
|
117
|
+
totalStorageUsed: 0
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
private readonly CACHE_TTL_MS = 30 * 60 * 1000; // 30 minutes
|
|
121
|
+
private sealService?: SealService;
|
|
122
|
+
private walrusClient: ReturnType<typeof this.createWalrusClient> | null = null;
|
|
123
|
+
|
|
124
|
+
constructor(config: Partial<WalrusConfig> = {}) {
|
|
125
|
+
const network = config.network || 'testnet';
|
|
126
|
+
|
|
127
|
+
this.config = {
|
|
128
|
+
network,
|
|
129
|
+
adminAddress: config.adminAddress || '',
|
|
130
|
+
storageEpochs: config.storageEpochs || 12,
|
|
131
|
+
uploadRelayHost: config.uploadRelayHost || 'https://upload-relay.testnet.walrus.space',
|
|
132
|
+
aggregatorHost: config.aggregatorHost || 'https://aggregator.walrus-testnet.walrus.space',
|
|
133
|
+
retryAttempts: config.retryAttempts || 3,
|
|
134
|
+
timeoutMs: config.timeoutMs || 60000,
|
|
135
|
+
sealService: config.sealService,
|
|
136
|
+
signer: config.signer
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
this.sealService = config.sealService;
|
|
140
|
+
|
|
141
|
+
// Initialize Walrus SDK client
|
|
142
|
+
this.walrusClient = this.createWalrusClient(network);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Create Walrus client using @mysten/walrus SDK
|
|
147
|
+
* Uses $extend to add walrus capabilities to SuiClient
|
|
148
|
+
*/
|
|
149
|
+
private createWalrusClient(network: 'testnet' | 'mainnet') {
|
|
150
|
+
const suiClient = new SuiClient({
|
|
151
|
+
url: getFullnodeUrl(network)
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// Extend SuiClient with Walrus capabilities using $extend
|
|
155
|
+
return suiClient.$extend(walrus({
|
|
156
|
+
network,
|
|
157
|
+
}));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ==================== PUBLIC API ====================
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Store memory with encryption and metadata
|
|
164
|
+
*/
|
|
165
|
+
async storeMemory(
|
|
166
|
+
content: string,
|
|
167
|
+
category: string,
|
|
168
|
+
options: {
|
|
169
|
+
topic?: string;
|
|
170
|
+
importance?: number;
|
|
171
|
+
customMetadata?: Record<string, string>;
|
|
172
|
+
contextId?: string;
|
|
173
|
+
appId?: string;
|
|
174
|
+
encrypt?: boolean;
|
|
175
|
+
userAddress?: string;
|
|
176
|
+
} = {}
|
|
177
|
+
): Promise<WalrusUploadResult> {
|
|
178
|
+
const startTime = Date.now();
|
|
179
|
+
this.stats.totalUploads++;
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
const {
|
|
183
|
+
topic = `Memory about ${category}`,
|
|
184
|
+
importance = 5,
|
|
185
|
+
customMetadata = {},
|
|
186
|
+
contextId,
|
|
187
|
+
appId,
|
|
188
|
+
encrypt = false,
|
|
189
|
+
userAddress
|
|
190
|
+
} = options;
|
|
191
|
+
|
|
192
|
+
let processedContent = content;
|
|
193
|
+
let backupKey: string | undefined;
|
|
194
|
+
let isEncrypted = false;
|
|
195
|
+
|
|
196
|
+
// Use SEAL encryption if requested and available
|
|
197
|
+
if (encrypt && this.sealService && userAddress) {
|
|
198
|
+
const sessionConfig = {
|
|
199
|
+
address: userAddress,
|
|
200
|
+
packageId: this.config.adminAddress, // Use configured package ID
|
|
201
|
+
ttlMin: 60 // 1 hour session
|
|
202
|
+
};
|
|
203
|
+
const sessionKey = await this.sealService.createSession(sessionConfig);
|
|
204
|
+
const encryptResult = await this.sealService.encryptData({
|
|
205
|
+
data: new TextEncoder().encode(content),
|
|
206
|
+
id: userAddress,
|
|
207
|
+
threshold: 2 // Default threshold
|
|
208
|
+
});
|
|
209
|
+
processedContent = JSON.stringify(encryptResult);
|
|
210
|
+
backupKey = 'session-key-reference'; // Store session reference
|
|
211
|
+
isEncrypted = true;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const metadata = this.createMetadataWithEmbedding(
|
|
215
|
+
processedContent,
|
|
216
|
+
category,
|
|
217
|
+
topic,
|
|
218
|
+
importance,
|
|
219
|
+
{
|
|
220
|
+
...customMetadata,
|
|
221
|
+
...(contextId && { 'context-id': contextId }),
|
|
222
|
+
...(appId && { 'app-id': appId }),
|
|
223
|
+
...(userAddress && { owner: userAddress }),
|
|
224
|
+
encrypted: isEncrypted.toString(),
|
|
225
|
+
...(isEncrypted && { 'encryption-type': 'seal' })
|
|
226
|
+
}
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
// Upload to Walrus with official client
|
|
230
|
+
const blobId = await this.uploadToWalrus(processedContent, metadata);
|
|
231
|
+
|
|
232
|
+
// Use Walrus blob_id as content hash (already content-addressed via blake2b256)
|
|
233
|
+
metadata.contentHash = blobId;
|
|
234
|
+
|
|
235
|
+
const uploadTimeMs = Date.now() - startTime;
|
|
236
|
+
this.stats.successfulUploads++;
|
|
237
|
+
this.updateAverageUploadTime(uploadTimeMs);
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
blobId,
|
|
241
|
+
metadata,
|
|
242
|
+
isEncrypted,
|
|
243
|
+
backupKey,
|
|
244
|
+
storageEpochs: this.config.storageEpochs,
|
|
245
|
+
uploadTimeMs
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
} catch (error) {
|
|
249
|
+
this.stats.failedUploads++;
|
|
250
|
+
throw new WalrusError(
|
|
251
|
+
`Failed to store memory: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
252
|
+
error
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Retrieve memory with decryption
|
|
259
|
+
*/
|
|
260
|
+
async retrieveMemory(
|
|
261
|
+
blobId: string,
|
|
262
|
+
options: {
|
|
263
|
+
userAddress?: string;
|
|
264
|
+
sessionKey?: any; // SEAL session key
|
|
265
|
+
txBytes?: Uint8Array;
|
|
266
|
+
} = {}
|
|
267
|
+
): Promise<WalrusRetrievalResult> {
|
|
268
|
+
const startTime = Date.now();
|
|
269
|
+
this.stats.totalRetrievals++;
|
|
270
|
+
|
|
271
|
+
try {
|
|
272
|
+
// Check cache first
|
|
273
|
+
const cached = this.cache.get(blobId);
|
|
274
|
+
if (cached && this.isCacheValid(cached.timestamp)) {
|
|
275
|
+
this.stats.cacheHitRate = (this.stats.cacheHitRate * (this.stats.totalRetrievals - 1) + 1) / this.stats.totalRetrievals;
|
|
276
|
+
|
|
277
|
+
const content = new TextDecoder().decode(cached.content);
|
|
278
|
+
return {
|
|
279
|
+
content,
|
|
280
|
+
metadata: cached.metadata as unknown as MemoryMetadata,
|
|
281
|
+
isDecrypted: false,
|
|
282
|
+
retrievalTimeMs: Date.now() - startTime
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Retrieve from Walrus
|
|
287
|
+
const { content, metadata } = await this.retrieveFromWalrus(blobId);
|
|
288
|
+
|
|
289
|
+
let processedContent = content;
|
|
290
|
+
let isDecrypted = false;
|
|
291
|
+
|
|
292
|
+
// Decrypt if needed
|
|
293
|
+
if (metadata.isEncrypted && metadata.encryptionType === 'seal' && this.sealService) {
|
|
294
|
+
const { userAddress, sessionKey, txBytes } = options;
|
|
295
|
+
if (userAddress && sessionKey && txBytes) {
|
|
296
|
+
try {
|
|
297
|
+
const encryptedData = JSON.parse(content);
|
|
298
|
+
const decryptedBytes = await this.sealService.decryptData({
|
|
299
|
+
encryptedObject: new Uint8Array(encryptedData),
|
|
300
|
+
sessionKey,
|
|
301
|
+
txBytes
|
|
302
|
+
});
|
|
303
|
+
processedContent = new TextDecoder().decode(decryptedBytes);
|
|
304
|
+
isDecrypted = true;
|
|
305
|
+
} catch (decryptError) {
|
|
306
|
+
console.warn('SEAL decryption failed, returning encrypted content:', decryptError);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const retrievalTimeMs = Date.now() - startTime;
|
|
312
|
+
this.updateAverageRetrievalTime(retrievalTimeMs);
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
content: processedContent,
|
|
316
|
+
metadata,
|
|
317
|
+
isDecrypted,
|
|
318
|
+
retrievalTimeMs
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
} catch (error) {
|
|
322
|
+
throw new WalrusError(
|
|
323
|
+
`Failed to retrieve memory ${blobId}: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
324
|
+
error
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Get service statistics
|
|
331
|
+
*/
|
|
332
|
+
getStats(): WalrusStats {
|
|
333
|
+
return { ...this.stats };
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Clear cache
|
|
338
|
+
*/
|
|
339
|
+
clearCache(): void {
|
|
340
|
+
this.cache.clear();
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Retrieve content by blobId with optional decryption
|
|
345
|
+
*/
|
|
346
|
+
async retrieveContent(blobId: string, decryptionKey?: string | Uint8Array): Promise<{
|
|
347
|
+
content: string;
|
|
348
|
+
metadata: MemoryMetadata;
|
|
349
|
+
retrievalTimeMs: number;
|
|
350
|
+
isFromCache: boolean;
|
|
351
|
+
}> {
|
|
352
|
+
const startTime = Date.now();
|
|
353
|
+
|
|
354
|
+
try {
|
|
355
|
+
const result = await this.retrieveFromWalrus(blobId);
|
|
356
|
+
|
|
357
|
+
return {
|
|
358
|
+
content: result.content,
|
|
359
|
+
metadata: result.metadata,
|
|
360
|
+
retrievalTimeMs: Date.now() - startTime,
|
|
361
|
+
isFromCache: false // TODO: implement cache checking
|
|
362
|
+
};
|
|
363
|
+
} catch (error) {
|
|
364
|
+
throw new Error(`Failed to retrieve content: ${error}`);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* List blobs for a specific user
|
|
370
|
+
*/
|
|
371
|
+
async listUserBlobs(userId: string, options: {
|
|
372
|
+
category?: string;
|
|
373
|
+
limit?: number;
|
|
374
|
+
offset?: number;
|
|
375
|
+
sortBy?: 'date' | 'size' | 'importance';
|
|
376
|
+
filters?: Record<string, any>;
|
|
377
|
+
} = {}): Promise<{ blobs: BlobInfo[]; totalCount: number }> {
|
|
378
|
+
// TODO: Implement actual Walrus listing when API is available
|
|
379
|
+
// For now, return empty result as this would require backend indexing
|
|
380
|
+
console.warn('listUserBlobs not yet implemented - requires Walrus indexing service');
|
|
381
|
+
return {
|
|
382
|
+
blobs: [],
|
|
383
|
+
totalCount: 0
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Delete a blob by ID
|
|
389
|
+
*/
|
|
390
|
+
async deleteBlob(blobId: string): Promise<boolean> {
|
|
391
|
+
// TODO: Implement actual Walrus deletion when API is available
|
|
392
|
+
// Walrus typically doesn't support deletion, but this could mark as deleted
|
|
393
|
+
console.warn('deleteBlob not yet implemented - Walrus typically immutable');
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Check Walrus service availability
|
|
399
|
+
*/
|
|
400
|
+
async checkWalrusAvailability(): Promise<boolean> {
|
|
401
|
+
// TODO: Implement proper availability check with official client
|
|
402
|
+
console.warn('checkWalrusAvailability not yet implemented - assuming available for testing');
|
|
403
|
+
return true; // Return true to allow tests to run, even though implementation is placeholder
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Get cache information
|
|
408
|
+
*/
|
|
409
|
+
getCacheInfo(): {
|
|
410
|
+
size: number;
|
|
411
|
+
maxSize: number;
|
|
412
|
+
hitRate: number;
|
|
413
|
+
entries: number;
|
|
414
|
+
} {
|
|
415
|
+
return {
|
|
416
|
+
size: this.cache.size, // Approximate
|
|
417
|
+
maxSize: 100, // Default cache size
|
|
418
|
+
hitRate: this.stats.cacheHitRate,
|
|
419
|
+
entries: this.cache.size
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Upload content with metadata
|
|
425
|
+
*/
|
|
426
|
+
async uploadContentWithMetadata(
|
|
427
|
+
content: string,
|
|
428
|
+
userId: string,
|
|
429
|
+
options: {
|
|
430
|
+
category?: string;
|
|
431
|
+
topic?: string;
|
|
432
|
+
importance?: number;
|
|
433
|
+
additionalTags?: Record<string, string>;
|
|
434
|
+
enableEncryption?: boolean;
|
|
435
|
+
}
|
|
436
|
+
): Promise<{
|
|
437
|
+
blobId: string;
|
|
438
|
+
metadata: MemoryMetadata;
|
|
439
|
+
uploadTimeMs: number;
|
|
440
|
+
isEncrypted: boolean;
|
|
441
|
+
}> {
|
|
442
|
+
const startTime = Date.now();
|
|
443
|
+
|
|
444
|
+
try {
|
|
445
|
+
// Upload to Walrus first to get blob_id (which serves as content hash)
|
|
446
|
+
const tempMetadata: MemoryMetadata = {
|
|
447
|
+
contentType: 'application/json',
|
|
448
|
+
contentSize: content.length,
|
|
449
|
+
contentHash: '', // Will be set to blobId below
|
|
450
|
+
category: options.category || 'general',
|
|
451
|
+
topic: options.topic || 'misc',
|
|
452
|
+
importance: options.importance || 5,
|
|
453
|
+
embeddingDimension: 0,
|
|
454
|
+
createdTimestamp: Date.now(),
|
|
455
|
+
updatedTimestamp: Date.now(),
|
|
456
|
+
customMetadata: options.additionalTags || {},
|
|
457
|
+
isEncrypted: options.enableEncryption || false,
|
|
458
|
+
encryptionType: options.enableEncryption ? 'seal' : undefined
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
const blobId = await this.uploadToWalrus(content, tempMetadata);
|
|
462
|
+
|
|
463
|
+
// Use Walrus blob_id as content hash (already content-addressed via blake2b256)
|
|
464
|
+
const memoryMetadata: MemoryMetadata = {
|
|
465
|
+
...tempMetadata,
|
|
466
|
+
contentHash: blobId, // Walrus blob_id serves as content hash
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
return {
|
|
470
|
+
blobId,
|
|
471
|
+
metadata: memoryMetadata,
|
|
472
|
+
uploadTimeMs: Date.now() - startTime,
|
|
473
|
+
isEncrypted: memoryMetadata.isEncrypted || false
|
|
474
|
+
};
|
|
475
|
+
} catch (error) {
|
|
476
|
+
throw new Error(`Failed to upload content: ${error}`);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// ==================== PRIVATE METHODS ====================
|
|
481
|
+
|
|
482
|
+
private createMetadataWithEmbedding(
|
|
483
|
+
content: string,
|
|
484
|
+
category: string,
|
|
485
|
+
topic: string,
|
|
486
|
+
importance: number,
|
|
487
|
+
customMetadata: Record<string, string>
|
|
488
|
+
): MemoryMetadata {
|
|
489
|
+
const contentBuffer = Buffer.from(content, 'utf-8');
|
|
490
|
+
const timestamp = Date.now();
|
|
491
|
+
|
|
492
|
+
return {
|
|
493
|
+
contentType: 'text/plain',
|
|
494
|
+
contentSize: contentBuffer.length,
|
|
495
|
+
contentHash: '', // Will be set to blobId after upload
|
|
496
|
+
category,
|
|
497
|
+
topic: topic || `Memory about ${category}`,
|
|
498
|
+
importance: Math.max(1, Math.min(10, importance)),
|
|
499
|
+
embeddingDimension: 3072,
|
|
500
|
+
createdTimestamp: timestamp,
|
|
501
|
+
customMetadata
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
private async uploadToWalrus(content: string, _metadata: MemoryMetadata): Promise<string> {
|
|
506
|
+
// Use Walrus SDK writeBlob when signer is available
|
|
507
|
+
if (this.walrusClient && this.config.signer) {
|
|
508
|
+
try {
|
|
509
|
+
const blob = new TextEncoder().encode(content);
|
|
510
|
+
const { blobId } = await this.walrusClient.walrus.writeBlob({
|
|
511
|
+
blob,
|
|
512
|
+
deletable: false,
|
|
513
|
+
epochs: this.config.storageEpochs,
|
|
514
|
+
signer: this.config.signer
|
|
515
|
+
});
|
|
516
|
+
return blobId;
|
|
517
|
+
} catch (error) {
|
|
518
|
+
throw new WalrusError(`Walrus SDK upload failed: ${error instanceof Error ? error.message : 'Unknown error'}`, error);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Fallback to REST API if no signer (read-only mode or relay upload)
|
|
523
|
+
try {
|
|
524
|
+
const response = await fetch(`${this.config.uploadRelayHost}/v1/store?epochs=${this.config.storageEpochs}`, {
|
|
525
|
+
method: 'PUT',
|
|
526
|
+
headers: {
|
|
527
|
+
'Content-Type': 'application/octet-stream'
|
|
528
|
+
},
|
|
529
|
+
body: new TextEncoder().encode(content)
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
if (!response.ok) {
|
|
533
|
+
throw new Error(`Upload failed: ${response.status} ${response.statusText}`);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const result = await response.json();
|
|
537
|
+
// Handle both newlyCreated and alreadyCertified responses
|
|
538
|
+
const newBlob = result.newlyCreated?.blobObject;
|
|
539
|
+
const certifiedBlob = result.alreadyCertified?.blobId;
|
|
540
|
+
return newBlob?.blobId || certifiedBlob || result.blobId;
|
|
541
|
+
} catch (error) {
|
|
542
|
+
throw new WalrusError(`Walrus REST upload failed: ${error instanceof Error ? error.message : 'Unknown error'}`, error);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
private async retrieveFromWalrus(blobId: string): Promise<{ content: string; metadata: MemoryMetadata }> {
|
|
547
|
+
// Use Walrus SDK readBlob when client is available
|
|
548
|
+
if (this.walrusClient) {
|
|
549
|
+
try {
|
|
550
|
+
const blob = await this.walrusClient.walrus.readBlob({ blobId });
|
|
551
|
+
const content = new TextDecoder().decode(blob);
|
|
552
|
+
|
|
553
|
+
const metadata: MemoryMetadata = {
|
|
554
|
+
contentType: 'text/plain',
|
|
555
|
+
contentSize: content.length,
|
|
556
|
+
contentHash: blobId,
|
|
557
|
+
category: 'unknown',
|
|
558
|
+
topic: 'Retrieved memory',
|
|
559
|
+
importance: 5,
|
|
560
|
+
embeddingDimension: 3072,
|
|
561
|
+
createdTimestamp: Date.now()
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
return { content, metadata };
|
|
565
|
+
} catch (error) {
|
|
566
|
+
// Fall through to REST API on SDK error
|
|
567
|
+
console.warn('Walrus SDK readBlob failed, falling back to REST API:', error);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// Fallback to REST API
|
|
572
|
+
try {
|
|
573
|
+
const response = await fetch(`${this.config.aggregatorHost}/v1/${blobId}`);
|
|
574
|
+
|
|
575
|
+
if (!response.ok) {
|
|
576
|
+
throw new Error(`Retrieval failed: ${response.status} ${response.statusText}`);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const content = await response.text();
|
|
580
|
+
|
|
581
|
+
const metadata: MemoryMetadata = {
|
|
582
|
+
contentType: 'text/plain',
|
|
583
|
+
contentSize: content.length,
|
|
584
|
+
contentHash: blobId,
|
|
585
|
+
category: 'unknown',
|
|
586
|
+
topic: 'Retrieved memory',
|
|
587
|
+
importance: 5,
|
|
588
|
+
embeddingDimension: 3072,
|
|
589
|
+
createdTimestamp: Date.now()
|
|
590
|
+
};
|
|
591
|
+
|
|
592
|
+
return { content, metadata };
|
|
593
|
+
} catch (error) {
|
|
594
|
+
throw new WalrusError(`Walrus retrieval failed: ${error instanceof Error ? error.message : 'Unknown error'}`, error);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
private isCacheValid(timestamp: Date): boolean {
|
|
599
|
+
return Date.now() - timestamp.getTime() < this.CACHE_TTL_MS;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
private updateAverageUploadTime(newTime: number): void {
|
|
603
|
+
const totalUploads = this.stats.successfulUploads;
|
|
604
|
+
this.stats.averageUploadTime =
|
|
605
|
+
(this.stats.averageUploadTime * (totalUploads - 1) + newTime) / totalUploads;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
private updateAverageRetrievalTime(newTime: number): void {
|
|
609
|
+
const totalRetrievals = this.stats.totalRetrievals;
|
|
610
|
+
this.stats.averageRetrievalTime =
|
|
611
|
+
(this.stats.averageRetrievalTime * (totalRetrievals - 1) + newTime) / totalRetrievals;
|
|
612
|
+
}
|
|
613
613
|
}
|