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.
- package/dist/providers/index.d.ts +9 -0
- package/dist/providers/index.js +12 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/localMemoryProvider.d.ts +35 -0
- package/dist/providers/localMemoryProvider.js +370 -0
- package/dist/providers/localMemoryProvider.js.map +1 -0
- package/dist/providers/memoryProvider.d.ts +187 -0
- package/dist/providers/memoryProvider.js +15 -0
- package/dist/providers/memoryProvider.js.map +1 -0
- package/dist/providers/memoryProviderRegistry.d.ts +61 -0
- package/dist/providers/memoryProviderRegistry.js +107 -0
- package/dist/providers/memoryProviderRegistry.js.map +1 -0
- package/dist/providers/supermemoryProvider.d.ts +58 -0
- package/dist/providers/supermemoryProvider.js +432 -0
- package/dist/providers/supermemoryProvider.js.map +1 -0
- package/dist/tools/causalMemoryTools.d.ts +11 -0
- package/dist/tools/causalMemoryTools.js +639 -0
- package/dist/tools/causalMemoryTools.js.map +1 -0
- package/dist/tools/toolRegistry.js +126 -0
- package/dist/tools/toolRegistry.js.map +1 -1
- package/dist/toolsetRegistry.js +2 -1
- package/dist/toolsetRegistry.js.map +1 -1
- package/package.json +1 -1
|
@@ -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
|