nodebench-mcp 2.33.0 → 2.34.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.
@@ -0,0 +1,61 @@
1
+ /**
2
+ * MemoryProviderRegistry — Singleton registry for MemoryProvider instances.
3
+ *
4
+ * Auto-registers LocalMemoryProvider as the default provider on first access.
5
+ * External providers (Supermemory, Zep, Graphiti) can be registered at runtime.
6
+ *
7
+ * Usage:
8
+ * const registry = MemoryProviderRegistry.getInstance();
9
+ * const local = registry.getDefault();
10
+ * await local.connect({ userId: "user-123" });
11
+ * const id = await local.store({ content: "Meeting notes..." });
12
+ */
13
+ import type { MemoryProvider } from "./memoryProvider.js";
14
+ export declare class MemoryProviderRegistry {
15
+ private static instance;
16
+ private providers;
17
+ private defaultName;
18
+ private constructor();
19
+ static getInstance(): MemoryProviderRegistry;
20
+ /** Reset the singleton (for tests only) */
21
+ static resetInstance(): void;
22
+ /**
23
+ * Register a provider. Overwrites if a provider with the same name
24
+ * already exists. Returns the registry for chaining.
25
+ */
26
+ register(provider: MemoryProvider): MemoryProviderRegistry;
27
+ /**
28
+ * Unregister a provider by name. Cannot unregister the default provider
29
+ * unless another default is set first.
30
+ */
31
+ unregister(name: string): boolean;
32
+ /**
33
+ * Set which registered provider is the default.
34
+ */
35
+ setDefault(name: string): void;
36
+ /**
37
+ * Get a provider by name. Returns undefined if not found.
38
+ */
39
+ get(name: string): MemoryProvider | undefined;
40
+ /**
41
+ * Get the default provider (LocalMemoryProvider unless changed).
42
+ */
43
+ getDefault(): MemoryProvider;
44
+ /**
45
+ * List all registered providers with their connection status.
46
+ */
47
+ listProviders(): Array<{
48
+ name: string;
49
+ type: string;
50
+ isDefault: boolean;
51
+ isConnected: boolean;
52
+ }>;
53
+ /**
54
+ * Get the count of registered providers.
55
+ */
56
+ get size(): number;
57
+ /**
58
+ * Check if a provider is registered.
59
+ */
60
+ has(name: string): boolean;
61
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * MemoryProviderRegistry — Singleton registry for MemoryProvider instances.
3
+ *
4
+ * Auto-registers LocalMemoryProvider as the default provider on first access.
5
+ * External providers (Supermemory, Zep, Graphiti) can be registered at runtime.
6
+ *
7
+ * Usage:
8
+ * const registry = MemoryProviderRegistry.getInstance();
9
+ * const local = registry.getDefault();
10
+ * await local.connect({ userId: "user-123" });
11
+ * const id = await local.store({ content: "Meeting notes..." });
12
+ */
13
+ import { LocalMemoryProvider } from "./localMemoryProvider.js";
14
+ // ═══════════════════════════════════════════════════════════════════════════
15
+ // Registry
16
+ // ═══════════════════════════════════════════════════════════════════════════
17
+ const DEFAULT_PROVIDER_NAME = "local-sqlite";
18
+ export class MemoryProviderRegistry {
19
+ static instance = null;
20
+ providers = new Map();
21
+ defaultName = DEFAULT_PROVIDER_NAME;
22
+ constructor() {
23
+ // Auto-register LocalMemoryProvider as the default
24
+ const local = new LocalMemoryProvider();
25
+ this.providers.set(local.name, local);
26
+ }
27
+ // ── Singleton access ───────────────────────────────────────────────────
28
+ static getInstance() {
29
+ if (!MemoryProviderRegistry.instance) {
30
+ MemoryProviderRegistry.instance = new MemoryProviderRegistry();
31
+ }
32
+ return MemoryProviderRegistry.instance;
33
+ }
34
+ /** Reset the singleton (for tests only) */
35
+ static resetInstance() {
36
+ MemoryProviderRegistry.instance = null;
37
+ }
38
+ // ── Registration ───────────────────────────────────────────────────────
39
+ /**
40
+ * Register a provider. Overwrites if a provider with the same name
41
+ * already exists. Returns the registry for chaining.
42
+ */
43
+ register(provider) {
44
+ this.providers.set(provider.name, provider);
45
+ return this;
46
+ }
47
+ /**
48
+ * Unregister a provider by name. Cannot unregister the default provider
49
+ * unless another default is set first.
50
+ */
51
+ unregister(name) {
52
+ if (name === this.defaultName && this.providers.size > 1) {
53
+ throw new Error(`Cannot unregister default provider "${name}" — set a new default first`);
54
+ }
55
+ return this.providers.delete(name);
56
+ }
57
+ /**
58
+ * Set which registered provider is the default.
59
+ */
60
+ setDefault(name) {
61
+ if (!this.providers.has(name)) {
62
+ throw new Error(`Provider "${name}" is not registered`);
63
+ }
64
+ this.defaultName = name;
65
+ }
66
+ // ── Retrieval ──────────────────────────────────────────────────────────
67
+ /**
68
+ * Get a provider by name. Returns undefined if not found.
69
+ */
70
+ get(name) {
71
+ return this.providers.get(name);
72
+ }
73
+ /**
74
+ * Get the default provider (LocalMemoryProvider unless changed).
75
+ */
76
+ getDefault() {
77
+ const provider = this.providers.get(this.defaultName);
78
+ if (!provider) {
79
+ throw new Error(`Default provider "${this.defaultName}" not found — registry may be corrupted`);
80
+ }
81
+ return provider;
82
+ }
83
+ /**
84
+ * List all registered providers with their connection status.
85
+ */
86
+ listProviders() {
87
+ return [...this.providers.values()].map((p) => ({
88
+ name: p.name,
89
+ type: p.type,
90
+ isDefault: p.name === this.defaultName,
91
+ isConnected: p.isConnected(),
92
+ }));
93
+ }
94
+ /**
95
+ * Get the count of registered providers.
96
+ */
97
+ get size() {
98
+ return this.providers.size;
99
+ }
100
+ /**
101
+ * Check if a provider is registered.
102
+ */
103
+ has(name) {
104
+ return this.providers.has(name);
105
+ }
106
+ }
107
+ //# sourceMappingURL=memoryProviderRegistry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memoryProviderRegistry.js","sourceRoot":"","sources":["../../src/providers/memoryProviderRegistry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * SupermemoryProvider — Cloud-backed MemoryProvider using the Supermemory REST API.
3
+ *
4
+ * Authenticates via Bearer token, supports project scoping via x-sm-project header.
5
+ * All data lives in Supermemory's cloud — sync() is a no-op since it's cloud-native.
6
+ *
7
+ * API base: https://api.supermemory.com/v3
8
+ */
9
+ import type { MemoryProvider, ProviderConfig, MemoryInput, Memory, MemoryRelation, RecallOptions, ListOptions, UserProfile, SyncResult } from "./memoryProvider.js";
10
+ export declare class SupermemoryError extends Error {
11
+ readonly statusCode?: number | undefined;
12
+ readonly responseBody?: string | undefined;
13
+ constructor(message: string, statusCode?: number | undefined, responseBody?: string | undefined);
14
+ }
15
+ export declare class SupermemoryAuthError extends SupermemoryError {
16
+ constructor(message?: string);
17
+ }
18
+ export declare class SupermemoryRateLimitError extends SupermemoryError {
19
+ readonly retryAfterMs?: number | undefined;
20
+ constructor(retryAfterMs?: number | undefined);
21
+ }
22
+ export declare class SupermemoryProvider implements MemoryProvider {
23
+ readonly name = "supermemory";
24
+ readonly type: "supermemory";
25
+ private config;
26
+ private connected;
27
+ private baseUrl;
28
+ private apiKey;
29
+ private projectId?;
30
+ connect(config: ProviderConfig): Promise<void>;
31
+ disconnect(): Promise<void>;
32
+ isConnected(): boolean;
33
+ private ensureConnected;
34
+ private request;
35
+ store(memory: MemoryInput): Promise<string>;
36
+ update(id: string, memory: MemoryInput): Promise<void>;
37
+ delete(id: string): Promise<void>;
38
+ recall(query: string, options?: RecallOptions): Promise<Memory[]>;
39
+ get(id: string): Promise<Memory | null>;
40
+ list(options?: ListOptions): Promise<Memory[]>;
41
+ relate(fromId: string, toId: string, relation: MemoryRelation): Promise<void>;
42
+ getProfile(userId?: string): Promise<UserProfile | null>;
43
+ /**
44
+ * Fallback: build a basic UserProfile by listing memories for the user.
45
+ * Used when the API doesn't have a dedicated profile endpoint.
46
+ */
47
+ private buildProfileFromList;
48
+ sync(direction: "push" | "pull" | "both"): Promise<SyncResult>;
49
+ /**
50
+ * Convert a Supermemory API memory response to the MemoryProvider Memory type.
51
+ * Supermemory stores scope/tags/source inside metadata — we extract them.
52
+ */
53
+ private apiMemoryToMemory;
54
+ /**
55
+ * Convert a Supermemory search result to a Memory with relevance score.
56
+ */
57
+ private searchResultToMemory;
58
+ }
@@ -0,0 +1,432 @@
1
+ /**
2
+ * SupermemoryProvider — Cloud-backed MemoryProvider using the Supermemory REST API.
3
+ *
4
+ * Authenticates via Bearer token, supports project scoping via x-sm-project header.
5
+ * All data lives in Supermemory's cloud — sync() is a no-op since it's cloud-native.
6
+ *
7
+ * API base: https://api.supermemory.com/v3
8
+ */
9
+ // ═══════════════════════════════════════════════════════════════════════════
10
+ // Constants
11
+ // ═══════════════════════════════════════════════════════════════════════════
12
+ const DEFAULT_BASE_URL = "https://api.supermemory.com/v3";
13
+ const REQUEST_TIMEOUT_MS = 30_000;
14
+ // ═══════════════════════════════════════════════════════════════════════════
15
+ // Error types
16
+ // ═══════════════════════════════════════════════════════════════════════════
17
+ export class SupermemoryError extends Error {
18
+ statusCode;
19
+ responseBody;
20
+ constructor(message, statusCode, responseBody) {
21
+ super(message);
22
+ this.statusCode = statusCode;
23
+ this.responseBody = responseBody;
24
+ this.name = "SupermemoryError";
25
+ }
26
+ }
27
+ export class SupermemoryAuthError extends SupermemoryError {
28
+ constructor(message = "Supermemory authentication failed — check your API key") {
29
+ super(message, 401);
30
+ this.name = "SupermemoryAuthError";
31
+ }
32
+ }
33
+ export class SupermemoryRateLimitError extends SupermemoryError {
34
+ retryAfterMs;
35
+ constructor(retryAfterMs) {
36
+ super("Supermemory rate limit exceeded — retry later", 429);
37
+ this.retryAfterMs = retryAfterMs;
38
+ this.name = "SupermemoryRateLimitError";
39
+ }
40
+ }
41
+ // ═══════════════════════════════════════════════════════════════════════════
42
+ // SupermemoryProvider
43
+ // ═══════════════════════════════════════════════════════════════════════════
44
+ export class SupermemoryProvider {
45
+ name = "supermemory";
46
+ type = "supermemory";
47
+ config = {};
48
+ connected = false;
49
+ baseUrl = DEFAULT_BASE_URL;
50
+ apiKey = "";
51
+ projectId;
52
+ // ── Lifecycle ──────────────────────────────────────────────────────────
53
+ async connect(config) {
54
+ if (!config.apiKey) {
55
+ throw new SupermemoryError("SupermemoryProvider requires an apiKey in ProviderConfig");
56
+ }
57
+ this.config = config;
58
+ this.apiKey = config.apiKey;
59
+ this.baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;
60
+ this.projectId = config.options?.projectId ?? undefined;
61
+ // Validate connectivity with a lightweight request
62
+ try {
63
+ // TODO: Verify health/ping endpoint — may be GET /health or GET /me
64
+ await this.request("GET", "/memories", { params: { limit: "1" } });
65
+ this.connected = true;
66
+ }
67
+ catch (err) {
68
+ if (err instanceof SupermemoryAuthError) {
69
+ throw err;
70
+ }
71
+ // Network errors during connect — mark as connected anyway since
72
+ // the config is valid; individual calls will surface errors.
73
+ this.connected = true;
74
+ }
75
+ }
76
+ async disconnect() {
77
+ this.connected = false;
78
+ this.apiKey = "";
79
+ this.projectId = undefined;
80
+ }
81
+ isConnected() {
82
+ return this.connected && this.apiKey.length > 0;
83
+ }
84
+ ensureConnected() {
85
+ if (!this.connected || !this.apiKey) {
86
+ throw new SupermemoryError("SupermemoryProvider is not connected — call connect() first");
87
+ }
88
+ }
89
+ // ── HTTP Layer ─────────────────────────────────────────────────────────
90
+ async request(method, path, options) {
91
+ let url = `${this.baseUrl}${path}`;
92
+ // Append query params
93
+ if (options?.params) {
94
+ const qs = new URLSearchParams(options.params).toString();
95
+ if (qs)
96
+ url += `?${qs}`;
97
+ }
98
+ const headers = {
99
+ Authorization: `Bearer ${this.apiKey}`,
100
+ "Content-Type": "application/json",
101
+ Accept: "application/json",
102
+ };
103
+ if (this.projectId) {
104
+ headers["x-sm-project"] = this.projectId;
105
+ }
106
+ const controller = new AbortController();
107
+ const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
108
+ try {
109
+ const response = await fetch(url, {
110
+ method,
111
+ headers,
112
+ body: options?.body ? JSON.stringify(options.body) : undefined,
113
+ signal: controller.signal,
114
+ });
115
+ if (response.status === 401 || response.status === 403) {
116
+ throw new SupermemoryAuthError();
117
+ }
118
+ if (response.status === 429) {
119
+ const retryAfter = response.headers.get("Retry-After");
120
+ const retryMs = retryAfter ? parseInt(retryAfter, 10) * 1000 : undefined;
121
+ throw new SupermemoryRateLimitError(retryMs);
122
+ }
123
+ if (response.status === 404) {
124
+ return null;
125
+ }
126
+ if (!response.ok) {
127
+ const bodyText = await response.text().catch(() => "");
128
+ throw new SupermemoryError(`Supermemory API error: ${response.status} ${response.statusText}`, response.status, bodyText);
129
+ }
130
+ // 204 No Content — common for DELETE/PUT
131
+ if (response.status === 204) {
132
+ return undefined;
133
+ }
134
+ return (await response.json());
135
+ }
136
+ catch (err) {
137
+ if (err instanceof SupermemoryError)
138
+ throw err;
139
+ if (err instanceof DOMException && err.name === "AbortError") {
140
+ throw new SupermemoryError(`Supermemory request timed out after ${REQUEST_TIMEOUT_MS}ms: ${method} ${path}`);
141
+ }
142
+ throw new SupermemoryError(`Supermemory network error: ${err instanceof Error ? err.message : String(err)}`);
143
+ }
144
+ finally {
145
+ clearTimeout(timeoutId);
146
+ }
147
+ }
148
+ // ── CRUD ───────────────────────────────────────────────────────────────
149
+ async store(memory) {
150
+ this.ensureConnected();
151
+ const userId = memory.userId ?? this.config.userId ?? "default";
152
+ /** TODO: Verify exact POST /memories request body shape */
153
+ const body = {
154
+ content: memory.content,
155
+ metadata: {
156
+ ...(memory.metadata ?? {}),
157
+ scope: memory.scope ?? "general",
158
+ tags: memory.tags ?? [],
159
+ source: memory.source ?? "",
160
+ userId,
161
+ ...(memory.timestamp ? { timestamp: memory.timestamp } : {}),
162
+ },
163
+ };
164
+ const result = await this.request("POST", "/memories", { body });
165
+ return result.id;
166
+ }
167
+ async update(id, memory) {
168
+ this.ensureConnected();
169
+ /** TODO: Verify PUT /memories/:id request body shape */
170
+ const body = {
171
+ content: memory.content,
172
+ metadata: {
173
+ ...(memory.metadata ?? {}),
174
+ scope: memory.scope ?? "general",
175
+ tags: memory.tags ?? [],
176
+ source: memory.source ?? "",
177
+ },
178
+ };
179
+ const result = await this.request("PUT", `/memories/${encodeURIComponent(id)}`, { body });
180
+ if (result === null) {
181
+ throw new SupermemoryError(`Memory not found: ${id}`, 404);
182
+ }
183
+ }
184
+ async delete(id) {
185
+ this.ensureConnected();
186
+ await this.request("DELETE", `/memories/${encodeURIComponent(id)}`);
187
+ }
188
+ // ── Retrieval ──────────────────────────────────────────────────────────
189
+ async recall(query, options) {
190
+ this.ensureConnected();
191
+ if (!query.trim())
192
+ return [];
193
+ const userId = options?.userId ?? this.config.userId;
194
+ /** TODO: Verify POST /search request body — may use "q" or "query" field */
195
+ const body = {
196
+ query,
197
+ limit: options?.limit ?? 10,
198
+ };
199
+ // Build filters object
200
+ const filters = {};
201
+ if (options?.scope)
202
+ filters.scope = options.scope;
203
+ if (options?.source)
204
+ filters.source = options.source;
205
+ if (userId)
206
+ filters.userId = userId;
207
+ if (options?.tags?.length)
208
+ filters.tags = options.tags;
209
+ if (options?.after)
210
+ filters.after = options.after;
211
+ if (options?.before)
212
+ filters.before = options.before;
213
+ if (options?.minScore !== undefined)
214
+ filters.minScore = options.minScore;
215
+ if (Object.keys(filters).length > 0) {
216
+ body.filters = filters;
217
+ }
218
+ const response = await this.request("POST", "/search", { body });
219
+ if (!response?.results)
220
+ return [];
221
+ return response.results.map((r) => this.searchResultToMemory(r));
222
+ }
223
+ async get(id) {
224
+ this.ensureConnected();
225
+ const result = await this.request("GET", `/memories/${encodeURIComponent(id)}`);
226
+ if (!result)
227
+ return null;
228
+ return this.apiMemoryToMemory(result);
229
+ }
230
+ async list(options) {
231
+ this.ensureConnected();
232
+ const params = {};
233
+ params.limit = String(options?.limit ?? 50);
234
+ if (options?.offset)
235
+ params.offset = String(options.offset);
236
+ if (options?.orderBy)
237
+ params.orderBy = options.orderBy;
238
+ if (options?.orderDirection)
239
+ params.orderDirection = options.orderDirection;
240
+ // Filters as query params
241
+ const userId = options?.userId ?? this.config.userId;
242
+ if (options?.scope)
243
+ params.scope = options.scope;
244
+ if (options?.source)
245
+ params.source = options.source;
246
+ if (userId)
247
+ params.userId = userId;
248
+ if (options?.tags?.length)
249
+ params.tags = options.tags.join(",");
250
+ /** TODO: Verify GET /memories query param names — may differ from this mapping */
251
+ const response = await this.request("GET", "/memories", { params });
252
+ if (!response?.memories)
253
+ return [];
254
+ let results = response.memories.map((m) => this.apiMemoryToMemory(m));
255
+ // Post-filter by tags if the API doesn't support tag filtering natively
256
+ if (options?.tags?.length) {
257
+ const tagSet = new Set(options.tags);
258
+ results = results.filter((m) => m.tags.some((t) => tagSet.has(t)));
259
+ }
260
+ return results;
261
+ }
262
+ // ── Relations ──────────────────────────────────────────────────────────
263
+ async relate(fromId, toId, relation) {
264
+ this.ensureConnected();
265
+ /**
266
+ * TODO: Verify if Supermemory has a native relations API.
267
+ * If POST /memories/:id/relations exists, use it directly.
268
+ * Fallback: store the relation as metadata on the source memory.
269
+ */
270
+ try {
271
+ await this.request("POST", `/memories/${encodeURIComponent(fromId)}/relations`, {
272
+ body: {
273
+ targetId: toId,
274
+ type: relation.type,
275
+ label: relation.label,
276
+ confidence: relation.confidence ?? 1.0,
277
+ metadata: relation.metadata ?? {},
278
+ },
279
+ });
280
+ }
281
+ catch (err) {
282
+ // If the relations endpoint doesn't exist (404 or 405), fall back to
283
+ // storing the relation as metadata on the source memory.
284
+ if (err instanceof SupermemoryError &&
285
+ (err.statusCode === 404 || err.statusCode === 405)) {
286
+ const sourceMemory = await this.get(fromId);
287
+ if (!sourceMemory) {
288
+ throw new SupermemoryError(`Source memory not found: ${fromId}`, 404);
289
+ }
290
+ const existingRelations = sourceMemory.metadata._relations ?? [];
291
+ existingRelations.push({
292
+ targetId: toId,
293
+ type: relation.type,
294
+ label: relation.label,
295
+ confidence: relation.confidence ?? 1.0,
296
+ metadata: relation.metadata ?? {},
297
+ createdAt: new Date().toISOString(),
298
+ });
299
+ await this.update(fromId, {
300
+ content: sourceMemory.content,
301
+ metadata: {
302
+ ...sourceMemory.metadata,
303
+ _relations: existingRelations,
304
+ },
305
+ scope: sourceMemory.scope,
306
+ tags: sourceMemory.tags,
307
+ source: sourceMemory.source,
308
+ });
309
+ return;
310
+ }
311
+ throw err;
312
+ }
313
+ }
314
+ // ── Profile ────────────────────────────────────────────────────────────
315
+ async getProfile(userId) {
316
+ this.ensureConnected();
317
+ const uid = userId ?? this.config.userId ?? "default";
318
+ /** TODO: Verify profile endpoint — may be GET /profile, GET /users/me, or GET /users/:id/profile */
319
+ try {
320
+ const response = await this.request("GET", "/profile", { params: { userId: uid } });
321
+ if (!response)
322
+ return null;
323
+ return {
324
+ userId: response.userId ?? uid,
325
+ memoryCount: response.memoryCount ?? 0,
326
+ scopes: response.scopes ?? [],
327
+ topTags: response.topTags ?? [],
328
+ topSources: response.topSources ?? [],
329
+ firstMemoryAt: response.firstMemoryAt ?? null,
330
+ lastMemoryAt: response.lastMemoryAt ?? null,
331
+ };
332
+ }
333
+ catch (err) {
334
+ // If profile endpoint doesn't exist, build a basic profile from list
335
+ if (err instanceof SupermemoryError &&
336
+ (err.statusCode === 404 || err.statusCode === 405)) {
337
+ return this.buildProfileFromList(uid);
338
+ }
339
+ throw err;
340
+ }
341
+ }
342
+ /**
343
+ * Fallback: build a basic UserProfile by listing memories for the user.
344
+ * Used when the API doesn't have a dedicated profile endpoint.
345
+ */
346
+ async buildProfileFromList(userId) {
347
+ const memories = await this.list({ userId, limit: 50, orderBy: "createdAt", orderDirection: "asc" });
348
+ if (memories.length === 0)
349
+ return null;
350
+ const scopeSet = new Set();
351
+ const tagCounts = new Map();
352
+ const sourceCounts = new Map();
353
+ for (const m of memories) {
354
+ scopeSet.add(m.scope);
355
+ for (const tag of m.tags) {
356
+ tagCounts.set(tag, (tagCounts.get(tag) ?? 0) + 1);
357
+ }
358
+ if (m.source) {
359
+ sourceCounts.set(m.source, (sourceCounts.get(m.source) ?? 0) + 1);
360
+ }
361
+ }
362
+ return {
363
+ userId,
364
+ memoryCount: memories.length,
365
+ scopes: [...scopeSet],
366
+ topTags: [...tagCounts.entries()]
367
+ .sort((a, b) => b[1] - a[1])
368
+ .slice(0, 10)
369
+ .map(([tag, count]) => ({ tag, count })),
370
+ topSources: [...sourceCounts.entries()]
371
+ .sort((a, b) => b[1] - a[1])
372
+ .slice(0, 10)
373
+ .map(([source, count]) => ({ source, count })),
374
+ firstMemoryAt: memories[0]?.createdAt ?? null,
375
+ lastMemoryAt: memories[memories.length - 1]?.createdAt ?? null,
376
+ };
377
+ }
378
+ // ── Sync ───────────────────────────────────────────────────────────────
379
+ async sync(direction) {
380
+ // Supermemory is cloud-native — all operations hit the API directly.
381
+ // No local cache to sync. Return a no-op result.
382
+ return {
383
+ direction,
384
+ pushed: 0,
385
+ pulled: 0,
386
+ conflicts: 0,
387
+ errors: 0,
388
+ completedAt: new Date().toISOString(),
389
+ };
390
+ }
391
+ // ── Mapping helpers ────────────────────────────────────────────────────
392
+ /**
393
+ * Convert a Supermemory API memory response to the MemoryProvider Memory type.
394
+ * Supermemory stores scope/tags/source inside metadata — we extract them.
395
+ */
396
+ apiMemoryToMemory(m) {
397
+ const metadata = m.metadata ?? {};
398
+ const scope = metadata.scope ?? "general";
399
+ const tags = metadata.tags ?? [];
400
+ const source = metadata.source ?? "";
401
+ const userId = metadata.userId ?? this.config.userId ?? "default";
402
+ // Remove our injected fields from metadata to keep it clean
403
+ const cleanMeta = { ...metadata };
404
+ delete cleanMeta.scope;
405
+ delete cleanMeta.tags;
406
+ delete cleanMeta.source;
407
+ delete cleanMeta.userId;
408
+ delete cleanMeta.timestamp;
409
+ return {
410
+ id: m.id,
411
+ content: m.content,
412
+ metadata: cleanMeta,
413
+ scope,
414
+ tags: Array.isArray(tags) ? tags : [],
415
+ source,
416
+ userId,
417
+ createdAt: m.createdAt ?? new Date().toISOString(),
418
+ updatedAt: m.updatedAt ?? m.createdAt ?? new Date().toISOString(),
419
+ };
420
+ }
421
+ /**
422
+ * Convert a Supermemory search result to a Memory with relevance score.
423
+ */
424
+ searchResultToMemory(r) {
425
+ const base = this.apiMemoryToMemory(r);
426
+ return {
427
+ ...base,
428
+ relevanceScore: r.score ?? undefined,
429
+ };
430
+ }
431
+ }
432
+ //# sourceMappingURL=supermemoryProvider.js.map