@remnic/import-mem0 0.1.0 → 9.3.517
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/LICENSE +21 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +91 -20
- package/dist/index.js.map +1 -1
- package/package.json +20 -13
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Joshua Warren
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.ts
CHANGED
|
@@ -43,6 +43,13 @@ interface Mem0ClientOptions {
|
|
|
43
43
|
fetchImpl?: typeof fetch;
|
|
44
44
|
/** Requests per second limiter. Applied between pages. */
|
|
45
45
|
rateLimit?: number;
|
|
46
|
+
/**
|
|
47
|
+
* Filters required by hosted mem0 list requests. Defaults to a broad empty
|
|
48
|
+
* filter body for the current hosted API.
|
|
49
|
+
*/
|
|
50
|
+
filters?: Record<string, unknown>;
|
|
51
|
+
/** Use the legacy GET list contract for self-hosted or older v1 deployments. */
|
|
52
|
+
legacyGet?: boolean;
|
|
46
53
|
/** Abort signal wired through to fetch. */
|
|
47
54
|
signal?: AbortSignal;
|
|
48
55
|
/** Sleep function for rate limiting; injectable so tests run instantly. */
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,8 @@ import { defaultWriteMemoriesToOrchestrator } from "@remnic/core";
|
|
|
5
5
|
|
|
6
6
|
// src/client.ts
|
|
7
7
|
var DEFAULT_BASE_URL = "https://api.mem0.ai";
|
|
8
|
-
var DEFAULT_LIST_PATH = "/
|
|
8
|
+
var DEFAULT_LIST_PATH = "/v3/memories/?page=1&page_size=50";
|
|
9
|
+
var DEFAULT_LEGACY_GET_LIST_PATH = "/v1/memories/";
|
|
9
10
|
async function fetchAllMem0Memories(options) {
|
|
10
11
|
if (!options.apiKey || typeof options.apiKey !== "string") {
|
|
11
12
|
throw new Error("mem0 import requires a non-empty apiKey");
|
|
@@ -18,7 +19,8 @@ async function fetchAllMem0Memories(options) {
|
|
|
18
19
|
}
|
|
19
20
|
const sleep = options.sleep ?? defaultSleep;
|
|
20
21
|
const base = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
21
|
-
const
|
|
22
|
+
const defaultListPath = options.legacyGet === true ? DEFAULT_LEGACY_GET_LIST_PATH : DEFAULT_LIST_PATH;
|
|
23
|
+
const listPath = normalizeListPath(options.listPath ?? defaultListPath);
|
|
22
24
|
const intervalMs = options.rateLimit && options.rateLimit > 0 ? 1e3 / options.rateLimit : 0;
|
|
23
25
|
const all = [];
|
|
24
26
|
const firstUrl = `${base}${listPath}`;
|
|
@@ -28,16 +30,9 @@ async function fetchAllMem0Memories(options) {
|
|
|
28
30
|
while (nextUrl) {
|
|
29
31
|
throwIfAborted(options.signal);
|
|
30
32
|
if (pageIndex > 0 && intervalMs > 0) {
|
|
31
|
-
await sleep
|
|
33
|
+
await sleepWithAbort(sleep, intervalMs, options.signal);
|
|
32
34
|
}
|
|
33
|
-
const response = await fetchImpl(nextUrl,
|
|
34
|
-
method: "GET",
|
|
35
|
-
headers: {
|
|
36
|
-
Authorization: `Token ${options.apiKey}`,
|
|
37
|
-
Accept: "application/json"
|
|
38
|
-
},
|
|
39
|
-
...options.signal ? { signal: options.signal } : {}
|
|
40
|
-
});
|
|
35
|
+
const response = await fetchImpl(nextUrl, buildMem0ListRequest(options));
|
|
41
36
|
if (!response.ok) {
|
|
42
37
|
const body = await safeText(response);
|
|
43
38
|
throw new Error(
|
|
@@ -56,8 +51,31 @@ async function fetchAllMem0Memories(options) {
|
|
|
56
51
|
}
|
|
57
52
|
return all;
|
|
58
53
|
}
|
|
54
|
+
function buildMem0ListRequest(options) {
|
|
55
|
+
if (options.legacyGet === true) {
|
|
56
|
+
return {
|
|
57
|
+
method: "GET",
|
|
58
|
+
headers: {
|
|
59
|
+
Authorization: `Token ${options.apiKey}`,
|
|
60
|
+
Accept: "application/json"
|
|
61
|
+
},
|
|
62
|
+
...options.signal ? { signal: options.signal } : {}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
method: "POST",
|
|
67
|
+
headers: {
|
|
68
|
+
Authorization: `Token ${options.apiKey}`,
|
|
69
|
+
Accept: "application/json",
|
|
70
|
+
"Content-Type": "application/json"
|
|
71
|
+
},
|
|
72
|
+
body: JSON.stringify({ filters: options.filters ?? {} }),
|
|
73
|
+
...options.signal ? { signal: options.signal } : {}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
59
76
|
function normalizeListPath(p) {
|
|
60
77
|
const withLeadingSlash = p.startsWith("/") ? p : `/${p}`;
|
|
78
|
+
if (withLeadingSlash.includes("?")) return withLeadingSlash;
|
|
61
79
|
return withLeadingSlash.endsWith("/") ? withLeadingSlash : `${withLeadingSlash}/`;
|
|
62
80
|
}
|
|
63
81
|
function safeUrlOrigin(url) {
|
|
@@ -113,11 +131,30 @@ function resolveCursorOrThrow(cursor, firstUrl, allowedOrigin) {
|
|
|
113
131
|
}
|
|
114
132
|
function throwIfAborted(signal) {
|
|
115
133
|
if (signal?.aborted) {
|
|
116
|
-
|
|
117
|
-
err.name = "AbortError";
|
|
118
|
-
throw err;
|
|
134
|
+
throw makeAbortError();
|
|
119
135
|
}
|
|
120
136
|
}
|
|
137
|
+
function makeAbortError() {
|
|
138
|
+
const err = new Error("mem0 import aborted");
|
|
139
|
+
err.name = "AbortError";
|
|
140
|
+
return err;
|
|
141
|
+
}
|
|
142
|
+
function sleepWithAbort(sleep, ms, signal) {
|
|
143
|
+
if (!signal) return sleep(ms);
|
|
144
|
+
if (signal.aborted) return Promise.reject(makeAbortError());
|
|
145
|
+
let removeAbortListener;
|
|
146
|
+
const abortPromise = new Promise((_, reject) => {
|
|
147
|
+
const onAbort = () => {
|
|
148
|
+
removeAbortListener?.();
|
|
149
|
+
reject(makeAbortError());
|
|
150
|
+
};
|
|
151
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
152
|
+
removeAbortListener = () => signal.removeEventListener("abort", onAbort);
|
|
153
|
+
});
|
|
154
|
+
return Promise.race([sleep(ms), abortPromise]).finally(() => {
|
|
155
|
+
removeAbortListener?.();
|
|
156
|
+
});
|
|
157
|
+
}
|
|
121
158
|
async function safeText(response) {
|
|
122
159
|
try {
|
|
123
160
|
return (await response.text()).slice(0, 500);
|
|
@@ -139,14 +176,17 @@ function parseMem0Export(input, options = {}) {
|
|
|
139
176
|
}
|
|
140
177
|
if (raw && typeof raw === "object") {
|
|
141
178
|
const obj = raw;
|
|
179
|
+
let recognizedTopLevelShape = false;
|
|
142
180
|
for (const key of ["results", "memories", "all_pages"]) {
|
|
143
181
|
const v = obj[key];
|
|
144
182
|
if (Array.isArray(v)) {
|
|
183
|
+
recognizedTopLevelShape = true;
|
|
145
184
|
appendMemories(memories, v, options);
|
|
146
185
|
}
|
|
147
186
|
}
|
|
148
187
|
const pages = obj.pages;
|
|
149
188
|
if (Array.isArray(pages)) {
|
|
189
|
+
recognizedTopLevelShape = true;
|
|
150
190
|
for (const page of pages) {
|
|
151
191
|
if (page && typeof page === "object") {
|
|
152
192
|
const p = page;
|
|
@@ -157,14 +197,16 @@ function parseMem0Export(input, options = {}) {
|
|
|
157
197
|
}
|
|
158
198
|
}
|
|
159
199
|
}
|
|
200
|
+
if (!recognizedTopLevelShape) {
|
|
201
|
+
throw new Error(
|
|
202
|
+
"mem0 export object must contain a results, memories, all_pages, or pages array"
|
|
203
|
+
);
|
|
204
|
+
}
|
|
160
205
|
return withFilePath(memories, options.filePath);
|
|
161
206
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
return withFilePath(memories, options.filePath);
|
|
207
|
+
throw new Error(
|
|
208
|
+
`mem0 export must be an array or recognized object; received ${describePayloadType(raw)}`
|
|
209
|
+
);
|
|
168
210
|
}
|
|
169
211
|
function appendMemories(dest, src, options) {
|
|
170
212
|
for (const entry of src) {
|
|
@@ -198,6 +240,11 @@ function coerceJson(input) {
|
|
|
198
240
|
}
|
|
199
241
|
return input;
|
|
200
242
|
}
|
|
243
|
+
function describePayloadType(value) {
|
|
244
|
+
if (value === null) return "null";
|
|
245
|
+
if (Array.isArray(value)) return "array";
|
|
246
|
+
return typeof value;
|
|
247
|
+
}
|
|
201
248
|
function extractMemoryBody(entry) {
|
|
202
249
|
for (const candidate of [entry.memory, entry.content, entry.text]) {
|
|
203
250
|
if (typeof candidate === "string") {
|
|
@@ -266,12 +313,16 @@ var adapter = {
|
|
|
266
313
|
}
|
|
267
314
|
const baseUrl = overrideClientOptionsForTesting?.baseUrl ?? process.env.MEM0_BASE_URL;
|
|
268
315
|
const listPath = overrideClientOptionsForTesting?.listPath ?? process.env.MEM0_LIST_PATH;
|
|
316
|
+
const legacyGet = overrideClientOptionsForTesting?.legacyGet ?? parseBooleanEnv(process.env.MEM0_LEGACY_GET);
|
|
317
|
+
const filters = overrideClientOptionsForTesting?.filters ?? parseJsonObjectEnv("MEM0_FILTERS", process.env.MEM0_FILTERS);
|
|
269
318
|
const importedFromPath = baseUrl ?? "https://api.mem0.ai";
|
|
270
319
|
const rateLimit = typeof options?.rateLimit === "number" ? options.rateLimit : void 0;
|
|
271
320
|
const memories = await fetchAllMem0Memories({
|
|
272
321
|
apiKey,
|
|
273
322
|
...baseUrl !== void 0 ? { baseUrl } : {},
|
|
274
323
|
...listPath !== void 0 ? { listPath } : {},
|
|
324
|
+
...legacyGet !== void 0 ? { legacyGet } : {},
|
|
325
|
+
...filters !== void 0 ? { filters } : {},
|
|
275
326
|
...rateLimit !== void 0 ? { rateLimit } : {},
|
|
276
327
|
...overrideClientOptionsForTesting?.fetchImpl ? { fetchImpl: overrideClientOptionsForTesting.fetchImpl } : {},
|
|
277
328
|
...overrideClientOptionsForTesting?.sleep ? { sleep: overrideClientOptionsForTesting.sleep } : {}
|
|
@@ -288,6 +339,26 @@ var adapter = {
|
|
|
288
339
|
}
|
|
289
340
|
};
|
|
290
341
|
var mem0Adapter = adapter;
|
|
342
|
+
function parseBooleanEnv(value) {
|
|
343
|
+
if (value === void 0) return void 0;
|
|
344
|
+
const normalized = value.trim().toLowerCase();
|
|
345
|
+
if (["1", "true", "yes", "on"].includes(normalized)) return true;
|
|
346
|
+
if (["0", "false", "no", "off"].includes(normalized)) return false;
|
|
347
|
+
throw new Error(`MEM0_LEGACY_GET must be a boolean-like value, got ${value}`);
|
|
348
|
+
}
|
|
349
|
+
function parseJsonObjectEnv(name, value) {
|
|
350
|
+
if (value === void 0 || value.trim().length === 0) return void 0;
|
|
351
|
+
let parsed;
|
|
352
|
+
try {
|
|
353
|
+
parsed = JSON.parse(value);
|
|
354
|
+
} catch (err) {
|
|
355
|
+
throw new Error(`${name} must be valid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
356
|
+
}
|
|
357
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
358
|
+
throw new Error(`${name} must be a JSON object`);
|
|
359
|
+
}
|
|
360
|
+
return parsed;
|
|
361
|
+
}
|
|
291
362
|
export {
|
|
292
363
|
MEM0_SOURCE_LABEL,
|
|
293
364
|
adapter,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/adapter.ts","../src/client.ts","../src/parser.ts","../src/transform.ts"],"sourcesContent":["// ---------------------------------------------------------------------------\n// mem0 importer adapter (issue #568 slice 5)\n// ---------------------------------------------------------------------------\n//\n// The mem0 adapter is API-driven rather than file-driven. Two call patterns\n// are supported:\n//\n// 1. CLI: `remnic import --adapter mem0 --rate-limit 2`\n// - No `--file`, so the CLI passes `undefined` as the input.\n// - The adapter reads `MEM0_API_KEY` + optional `MEM0_BASE_URL` from env\n// and walks the paginated API using `fetchAllMem0Memories`.\n//\n// 2. Record/replay tests: callers pass a JSON string (replay fixture) OR\n// a pre-fetched `Mem0Memory[]` directly via parse. In this mode no\n// network I/O happens.\n//\n// Adapters are pure by contract (parse MUST NOT call the orchestrator), so\n// the API fetch lives in `parse`. This mirrors the file-reading importers\n// where parse does the I/O of decoding JSON — here it's HTTP instead.\n\nimport type {\n ImportedMemory,\n ImporterAdapter,\n ImporterParseOptions,\n ImporterTransformOptions,\n ImporterWriteResult,\n ImporterWriteTarget,\n RunImportOptions,\n} from \"@remnic/core\";\nimport { defaultWriteMemoriesToOrchestrator } from \"@remnic/core\";\n\nimport { fetchAllMem0Memories, type Mem0ClientOptions } from \"./client.js\";\nimport { parseMem0Export, type ParsedMem0Export } from \"./parser.js\";\nimport { MEM0_SOURCE_LABEL, transformMem0Export } from \"./transform.js\";\n\n/**\n * Test-only backdoor used by `adapter.test.ts` to inject a fake fetch without\n * exposing the ImporterAdapter surface to client-options plumbing at runtime.\n * Cleared after each test.\n */\nlet overrideClientOptionsForTesting:\n | Partial<Mem0ClientOptions>\n | undefined;\n\n/** Visible for tests. */\nexport function setMem0ClientOptionsForTesting(\n options: Partial<Mem0ClientOptions> | undefined,\n): void {\n overrideClientOptionsForTesting = options;\n}\n\nexport const adapter: ImporterAdapter<ParsedMem0Export> = {\n name: \"mem0\",\n sourceLabel: MEM0_SOURCE_LABEL,\n\n async parse(\n input: unknown,\n options?: ImporterParseOptions,\n ): Promise<ParsedMem0Export> {\n // Replay / in-memory path: caller supplied JSON or an array already.\n if (input !== undefined && input !== null) {\n return parseMem0Export(input, {\n ...(options?.strict !== undefined ? { strict: options.strict } : {}),\n ...(options?.filePath !== undefined\n ? { filePath: options.filePath }\n : {}),\n });\n }\n\n // Live path: pull from the API.\n const apiKey = overrideClientOptionsForTesting?.apiKey ?? process.env.MEM0_API_KEY;\n if (!apiKey) {\n throw new Error(\n \"mem0 import requires an API key. Set MEM0_API_KEY in your environment \" +\n \"or pass a replay fixture via --file <export.json>.\",\n );\n }\n const baseUrl =\n overrideClientOptionsForTesting?.baseUrl ?? process.env.MEM0_BASE_URL;\n // Self-hosted mem0-oss exposes `/memories/` without the `/v1` prefix.\n // Let operators override via MEM0_LIST_PATH so those deployments work\n // without patching. Codex review on PR #602.\n const listPath =\n overrideClientOptionsForTesting?.listPath ?? process.env.MEM0_LIST_PATH;\n const importedFromPath = baseUrl ?? \"https://api.mem0.ai\";\n // Forward the validated CLI `--rate-limit` (now carried through\n // ImporterParseOptions.rateLimit by runImporter) into the fetch client\n // so `remnic import --adapter mem0 --rate-limit 2` actually throttles.\n // Cursor review on PR #602 — the original wiring silently ignored it.\n const rateLimit =\n typeof options?.rateLimit === \"number\" ? options.rateLimit : undefined;\n const memories = await fetchAllMem0Memories({\n apiKey,\n ...(baseUrl !== undefined ? { baseUrl } : {}),\n ...(listPath !== undefined ? { listPath } : {}),\n ...(rateLimit !== undefined ? { rateLimit } : {}),\n ...(overrideClientOptionsForTesting?.fetchImpl\n ? { fetchImpl: overrideClientOptionsForTesting.fetchImpl }\n : {}),\n ...(overrideClientOptionsForTesting?.sleep\n ? { sleep: overrideClientOptionsForTesting.sleep }\n : {}),\n });\n return { memories, importedFromPath };\n },\n\n transform(\n parsed: ParsedMem0Export,\n options?: ImporterTransformOptions,\n ): ImportedMemory[] {\n return transformMem0Export(parsed, {\n ...(options?.maxMemories !== undefined\n ? { maxMemories: options.maxMemories }\n : {}),\n });\n },\n\n async writeTo(\n target: ImporterWriteTarget,\n memories: ImportedMemory[],\n _options: RunImportOptions,\n ): Promise<ImporterWriteResult> {\n return defaultWriteMemoriesToOrchestrator(target, memories);\n },\n};\n\n/** Alias kept for symmetry with other @remnic/import-* packages. */\nexport const mem0Adapter = adapter;\n","// ---------------------------------------------------------------------------\n// Mem0 REST client (issue #568 slice 5)\n// ---------------------------------------------------------------------------\n//\n// mem0.ai exposes a paginated memories list endpoint. A production user will\n// typically hit the hosted service at `https://api.mem0.ai/v1/memories/`,\n// supply a Bearer API key, and pull down their account's memories page by\n// page. Some users self-host and need a configurable base URL.\n//\n// This client is intentionally tiny:\n// - fetch-based; no SDK dependency.\n// - Injectable `fetch` impl so tests can replay a record/replay fixture.\n// - Abort-signal aware for clean cancellation.\n// - Rate-limit aware (sleeps between page requests when `rateLimit` is set\n// on `RunImportOptions`).\n//\n// The adapter calls `fetchAllMem0Memories()` once; it walks pagination and\n// returns a flat array. The transform layer then maps each raw record to an\n// `ImportedMemory`.\n\nexport interface Mem0Memory {\n /** Stable memory id. */\n id: string;\n /** Memory body. API older responses nest this in `memory`. */\n memory?: string;\n content?: string;\n text?: string;\n user_id?: string;\n agent_id?: string;\n created_at?: string;\n updated_at?: string;\n metadata?: Record<string, unknown>;\n categories?: string[];\n score?: number;\n}\n\n/**\n * Shape returned by the paginated memories endpoint. The real API uses\n * `results` + `next` (cursor URL) on v1 and `memories` + `page` + `total`\n * on v0; the client accepts either so tests can replay both.\n */\nexport interface Mem0ListResponse {\n results?: Mem0Memory[];\n memories?: Mem0Memory[];\n next?: string | null;\n total?: number;\n page?: number;\n per_page?: number;\n}\n\nexport interface Mem0ClientOptions {\n apiKey: string;\n /** Default: `https://api.mem0.ai`. Trailing slash tolerated. */\n baseUrl?: string;\n /**\n * Path prefix for the list endpoint. Defaults to `/v1/memories/` for\n * the hosted API. Self-hosted mem0-oss deployments typically expose\n * `/memories/` without the `/v1` prefix — set `MEM0_LIST_PATH` /\n * pass this explicitly in that case. Codex review on PR #602.\n */\n listPath?: string;\n /** Injected for tests. Falls back to `globalThis.fetch`. */\n fetchImpl?: typeof fetch;\n /** Requests per second limiter. Applied between pages. */\n rateLimit?: number;\n /** Abort signal wired through to fetch. */\n signal?: AbortSignal;\n /** Sleep function for rate limiting; injectable so tests run instantly. */\n sleep?: (ms: number) => Promise<void>;\n}\n\nconst DEFAULT_BASE_URL = \"https://api.mem0.ai\";\nconst DEFAULT_LIST_PATH = \"/v1/memories/\";\n\n/**\n * Fetch all mem0 memories across pagination. Returns a flat array; the\n * caller is responsible for deduplication (the orchestrator does this\n * naturally via content hashing).\n */\nexport async function fetchAllMem0Memories(\n options: Mem0ClientOptions,\n): Promise<Mem0Memory[]> {\n if (!options.apiKey || typeof options.apiKey !== \"string\") {\n throw new Error(\"mem0 import requires a non-empty apiKey\");\n }\n const fetchImpl = options.fetchImpl ?? globalThis.fetch;\n if (typeof fetchImpl !== \"function\") {\n throw new Error(\n \"No fetch implementation available. Provide `fetchImpl` or run on Node 18+.\",\n );\n }\n const sleep = options.sleep ?? defaultSleep;\n const base = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, \"\");\n const listPath = normalizeListPath(options.listPath ?? DEFAULT_LIST_PATH);\n const intervalMs =\n options.rateLimit && options.rateLimit > 0 ? 1000 / options.rateLimit : 0;\n\n const all: Mem0Memory[] = [];\n const firstUrl = `${base}${listPath}`;\n // Capture the allow-listed origin ONCE so cross-origin cursors can't\n // tunnel the API key to an attacker-controlled host. Codex review on\n // PR #602. We also validate that the configured base URL itself is a\n // parseable absolute URL — mem0 servers that return relative cursors\n // are then resolved against this origin.\n const allowedOrigin = safeUrlOrigin(firstUrl);\n let nextUrl: string | null = firstUrl;\n let pageIndex = 0;\n while (nextUrl) {\n throwIfAborted(options.signal);\n if (pageIndex > 0 && intervalMs > 0) {\n await sleep(intervalMs);\n }\n const response = await fetchImpl(nextUrl, {\n method: \"GET\",\n headers: {\n Authorization: `Token ${options.apiKey}`,\n Accept: \"application/json\",\n },\n ...(options.signal ? { signal: options.signal } : {}),\n });\n if (!response.ok) {\n const body = await safeText(response);\n throw new Error(\n `mem0 API request to ${nextUrl} failed with ${response.status}: ${body}`,\n );\n }\n const json = (await response.json()) as Mem0ListResponse;\n const page = json.results ?? json.memories ?? [];\n for (const entry of page) {\n if (entry && typeof entry === \"object\" && typeof entry.id === \"string\") {\n all.push(entry);\n }\n }\n nextUrl = resolveNextUrl(json, firstUrl, pageIndex, page.length, allowedOrigin);\n pageIndex += 1;\n }\n return all;\n}\n\nfunction normalizeListPath(p: string): string {\n const withLeadingSlash = p.startsWith(\"/\") ? p : `/${p}`;\n return withLeadingSlash.endsWith(\"/\") ? withLeadingSlash : `${withLeadingSlash}/`;\n}\n\nfunction safeUrlOrigin(url: string): string | undefined {\n try {\n return new URL(url).origin;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Decide the next URL to request.\n *\n * Preferred: the `next` cursor the server returns (v1 shape). When `next`\n * is a **relative** path, it's resolved against the configured base URL.\n * When absolute, it MUST match the allow-listed origin — cross-origin\n * cursors are rejected so an upstream/proxy can't exfiltrate the API key\n * by redirecting the pagination walk. Codex review on PR #602.\n *\n * Fallback: when `next` is explicitly `null` (server says \"no more pages\"),\n * stop. When `next` is absent (field omitted) AND the response exposes\n * numeric pagination fields (`page` / `total` / `per_page`, the older v0\n * shape), synthesize a `?page=<N>` request for the next page. Cursor\n * review on PR #602 flagged that servers returning only numeric\n * pagination were silently truncated under the original code, AND that\n * the implementation conflated `null` with `undefined`.\n */\nfunction resolveNextUrl(\n json: Mem0ListResponse,\n firstUrl: string,\n currentPageIndex: number,\n pageSize: number,\n allowedOrigin: string | undefined,\n): string | null {\n // Has the server explicitly returned a next value?\n if (\"next\" in json) {\n if (json.next === null) return null; // authoritative stop\n if (typeof json.next === \"string\" && json.next.length > 0) {\n return resolveCursorOrThrow(json.next, firstUrl, allowedOrigin);\n }\n // `next` was present but not a non-empty string or null (e.g. number).\n // Don't fall through to numeric pagination — the server sent a signal\n // we can't interpret. Treat as end-of-stream.\n return null;\n }\n\n // Numeric-pagination fallback. `page` is 1-based in the real API.\n const responsePage = typeof json.page === \"number\" && Number.isFinite(json.page)\n ? json.page\n : currentPageIndex + 1;\n const perPage = typeof json.per_page === \"number\" && Number.isFinite(json.per_page)\n ? json.per_page\n : pageSize;\n const total = typeof json.total === \"number\" && Number.isFinite(json.total)\n ? json.total\n : undefined;\n if (total === undefined || perPage <= 0) return null;\n const fetchedSoFar = responsePage * perPage;\n if (fetchedSoFar >= total) return null;\n const nextPage = responsePage + 1;\n try {\n const u = new URL(firstUrl);\n u.searchParams.set(\"page\", String(nextPage));\n return u.toString();\n } catch {\n return null;\n }\n}\n\n/**\n * Resolve a server-provided cursor against the base URL and enforce\n * same-origin. Relative cursors are accepted (resolved against firstUrl);\n * absolute cursors must match `allowedOrigin`. Throws on cross-origin to\n * surface the security-relevant mismatch immediately instead of silently\n * leaking the API key.\n */\nfunction resolveCursorOrThrow(\n cursor: string,\n firstUrl: string,\n allowedOrigin: string | undefined,\n): string {\n if (!allowedOrigin) {\n // Base URL wasn't parseable as an absolute URL; refuse to follow any\n // cursor because we can't compare origins.\n throw new Error(\n `mem0 pagination cursor '${cursor}' cannot be followed: configured baseUrl is not an absolute URL.`,\n );\n }\n let resolved: URL;\n try {\n resolved = new URL(cursor, firstUrl);\n } catch {\n throw new Error(\n `mem0 pagination cursor '${cursor}' is not a valid URL.`,\n );\n }\n if (resolved.origin !== allowedOrigin) {\n throw new Error(\n `mem0 pagination cursor '${cursor}' points to origin '${resolved.origin}', ` +\n `but the configured mem0 origin is '${allowedOrigin}'. ` +\n \"Refusing to forward the API key to a cross-origin endpoint.\",\n );\n }\n return resolved.toString();\n}\n\nfunction throwIfAborted(signal: AbortSignal | undefined): void {\n if (signal?.aborted) {\n const err = new Error(\"mem0 import aborted\");\n (err as Error & { name: string }).name = \"AbortError\";\n throw err;\n }\n}\n\nasync function safeText(response: Response): Promise<string> {\n try {\n return (await response.text()).slice(0, 500);\n } catch {\n return \"(failed to read response body)\";\n }\n}\n\nfunction defaultSleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","// ---------------------------------------------------------------------------\n// mem0 parser (issue #568 slice 5)\n// ---------------------------------------------------------------------------\n//\n// Unlike the file-based importers, mem0 pulls data directly from an API. The\n// \"parse\" step therefore either:\n//\n// 1. Accepts an already-fetched `Mem0Memory[]` (the adapter's primary path,\n// after the API client pulls everything down).\n// 2. Accepts a JSON string / parsed object for record/replay fixtures so\n// tests and offline flows don't need network access.\n//\n// Non-object entries are dropped in non-strict mode (CLAUDE.md rule 51\n// applies only to user-facing CLI inputs; parser leniency here protects\n// against server-side schema drift without crashing the import).\n\nimport type { Mem0Memory } from \"./client.js\";\n\nexport interface ParsedMem0Export {\n memories: Mem0Memory[];\n /** Provenance — API endpoint URL or replay fixture path. */\n importedFromPath?: string;\n}\n\nexport interface Mem0ParseOptions {\n strict?: boolean;\n filePath?: string;\n}\n\n/**\n * Parse a mem0 payload. Accepts:\n * - a `Mem0Memory[]` (already fetched)\n * - a JSON string\n * - an object like `{ results: [...] }` or `{ memories: [...] }` or\n * `{ all_pages: [...] }` (combined replay fixture)\n */\nexport function parseMem0Export(\n input: unknown,\n options: Mem0ParseOptions = {},\n): ParsedMem0Export {\n const raw = coerceJson(input);\n const memories: Mem0Memory[] = [];\n\n if (Array.isArray(raw)) {\n appendMemories(memories, raw, options);\n return withFilePath(memories, options.filePath);\n }\n\n if (raw && typeof raw === \"object\") {\n const obj = raw as Record<string, unknown>;\n for (const key of [\"results\", \"memories\", \"all_pages\"] as const) {\n const v = obj[key];\n if (Array.isArray(v)) {\n appendMemories(memories, v, options);\n }\n }\n // `pages`: an array of page responses (replay fixture for multi-page\n // pulls). We flatten each page's `results` / `memories`.\n const pages = obj.pages;\n if (Array.isArray(pages)) {\n for (const page of pages) {\n if (page && typeof page === \"object\") {\n const p = page as Record<string, unknown>;\n for (const key of [\"results\", \"memories\"] as const) {\n const v = p[key];\n if (Array.isArray(v)) appendMemories(memories, v, options);\n }\n }\n }\n }\n return withFilePath(memories, options.filePath);\n }\n\n if (options.strict) {\n throw new Error(\n \"mem0 export must be an array or object; received \" + typeof raw,\n );\n }\n return withFilePath(memories, options.filePath);\n}\n\nfunction appendMemories(\n dest: Mem0Memory[],\n src: unknown[],\n options: Mem0ParseOptions,\n): void {\n for (const entry of src) {\n if (!entry || typeof entry !== \"object\") {\n if (options.strict) throw new Error(\"mem0 entry must be an object\");\n continue;\n }\n const record = entry as Mem0Memory;\n if (typeof record.id !== \"string\" || record.id.length === 0) {\n if (options.strict) throw new Error(\"mem0 entry missing id\");\n continue;\n }\n dest.push(record);\n }\n}\n\nfunction withFilePath(\n memories: Mem0Memory[],\n importedFromPath: string | undefined,\n): ParsedMem0Export {\n return {\n memories,\n ...(importedFromPath !== undefined ? { importedFromPath } : {}),\n };\n}\n\nfunction coerceJson(input: unknown): unknown {\n if (typeof input === \"string\") {\n try {\n return JSON.parse(input);\n } catch (err) {\n throw new Error(\n `mem0 payload is not valid JSON: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n }\n return input;\n}\n\n/** Extract the memory body, preferring explicit `memory` then `content` then `text`. */\nexport function extractMemoryBody(entry: Mem0Memory): string | undefined {\n for (const candidate of [entry.memory, entry.content, entry.text]) {\n if (typeof candidate === \"string\") {\n const trimmed = candidate.trim();\n if (trimmed.length > 0) return trimmed;\n }\n }\n return undefined;\n}\n","// ---------------------------------------------------------------------------\n// mem0 parsed → ImportedMemory transform (issue #568 slice 5)\n// ---------------------------------------------------------------------------\n\nimport type { ImportedMemory } from \"@remnic/core\";\n\nimport type { Mem0Memory } from \"./client.js\";\nimport type { ParsedMem0Export } from \"./parser.js\";\nimport { extractMemoryBody } from \"./parser.js\";\n\nexport const MEM0_SOURCE_LABEL = \"mem0\";\n\nexport interface Mem0TransformOptions {\n /** Optional cap on total memories emitted — primarily for tests. */\n maxMemories?: number;\n}\n\nexport function transformMem0Export(\n parsed: ParsedMem0Export,\n options: Mem0TransformOptions = {},\n): ImportedMemory[] {\n const out: ImportedMemory[] = [];\n const cap = options.maxMemories;\n for (const entry of parsed.memories) {\n if (cap !== undefined && out.length >= cap) return out;\n const memory = mem0ToImported(entry, parsed.importedFromPath);\n if (memory) out.push(memory);\n }\n return out;\n}\n\nfunction mem0ToImported(\n entry: Mem0Memory,\n importedFromPath: string | undefined,\n): ImportedMemory | undefined {\n const content = extractMemoryBody(entry);\n if (!content) return undefined;\n const sourceTimestamp = entry.updated_at ?? entry.created_at;\n const metadata: Record<string, unknown> = { kind: \"mem0_memory\" };\n if (entry.user_id) metadata.userId = entry.user_id;\n if (entry.agent_id) metadata.agentId = entry.agent_id;\n if (Array.isArray(entry.categories) && entry.categories.length > 0) {\n metadata.categories = [...entry.categories];\n }\n if (entry.metadata && typeof entry.metadata === \"object\") {\n metadata.sourceMetadata = entry.metadata;\n }\n return {\n content,\n sourceLabel: MEM0_SOURCE_LABEL,\n sourceId: entry.id,\n ...(sourceTimestamp !== undefined ? { sourceTimestamp } : {}),\n ...(importedFromPath !== undefined ? { importedFromPath } : {}),\n metadata,\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/adapter.ts","../src/client.ts","../src/parser.ts","../src/transform.ts"],"sourcesContent":["// ---------------------------------------------------------------------------\n// mem0 importer adapter (issue #568 slice 5)\n// ---------------------------------------------------------------------------\n//\n// The mem0 adapter is API-driven rather than file-driven. Two call patterns\n// are supported:\n//\n// 1. CLI: `remnic import --adapter mem0 --rate-limit 2`\n// - No `--file`, so the CLI passes `undefined` as the input.\n// - The adapter reads `MEM0_API_KEY` + optional `MEM0_BASE_URL` from env\n// and walks the paginated API using `fetchAllMem0Memories`.\n//\n// 2. Record/replay tests: callers pass a JSON string (replay fixture) OR\n// a pre-fetched `Mem0Memory[]` directly via parse. In this mode no\n// network I/O happens.\n//\n// Adapters are pure by contract (parse MUST NOT call the orchestrator), so\n// the API fetch lives in `parse`. This mirrors the file-reading importers\n// where parse does the I/O of decoding JSON — here it's HTTP instead.\n\nimport type {\n ImportedMemory,\n ImporterAdapter,\n ImporterParseOptions,\n ImporterTransformOptions,\n ImporterWriteResult,\n ImporterWriteTarget,\n RunImportOptions,\n} from \"@remnic/core\";\nimport { defaultWriteMemoriesToOrchestrator } from \"@remnic/core\";\n\nimport { fetchAllMem0Memories, type Mem0ClientOptions } from \"./client.js\";\nimport { parseMem0Export, type ParsedMem0Export } from \"./parser.js\";\nimport { MEM0_SOURCE_LABEL, transformMem0Export } from \"./transform.js\";\n\n/**\n * Test-only backdoor used by `adapter.test.ts` to inject a fake fetch without\n * exposing the ImporterAdapter surface to client-options plumbing at runtime.\n * Cleared after each test.\n */\nlet overrideClientOptionsForTesting:\n | Partial<Mem0ClientOptions>\n | undefined;\n\n/** Visible for tests. */\nexport function setMem0ClientOptionsForTesting(\n options: Partial<Mem0ClientOptions> | undefined,\n): void {\n overrideClientOptionsForTesting = options;\n}\n\nexport const adapter: ImporterAdapter<ParsedMem0Export> = {\n name: \"mem0\",\n sourceLabel: MEM0_SOURCE_LABEL,\n\n async parse(\n input: unknown,\n options?: ImporterParseOptions,\n ): Promise<ParsedMem0Export> {\n // Replay / in-memory path: caller supplied JSON or an array already.\n if (input !== undefined && input !== null) {\n return parseMem0Export(input, {\n ...(options?.strict !== undefined ? { strict: options.strict } : {}),\n ...(options?.filePath !== undefined\n ? { filePath: options.filePath }\n : {}),\n });\n }\n\n // Live path: pull from the API.\n const apiKey = overrideClientOptionsForTesting?.apiKey ?? process.env.MEM0_API_KEY;\n if (!apiKey) {\n throw new Error(\n \"mem0 import requires an API key. Set MEM0_API_KEY in your environment \" +\n \"or pass a replay fixture via --file <export.json>.\",\n );\n }\n const baseUrl =\n overrideClientOptionsForTesting?.baseUrl ?? process.env.MEM0_BASE_URL;\n // Self-hosted mem0-oss exposes `/memories/` without the `/v1` prefix.\n // Let operators override via MEM0_LIST_PATH so those deployments work\n // without patching. Codex review on PR #602.\n const listPath =\n overrideClientOptionsForTesting?.listPath ?? process.env.MEM0_LIST_PATH;\n const legacyGet =\n overrideClientOptionsForTesting?.legacyGet ??\n parseBooleanEnv(process.env.MEM0_LEGACY_GET);\n const filters =\n overrideClientOptionsForTesting?.filters ?? parseJsonObjectEnv(\"MEM0_FILTERS\", process.env.MEM0_FILTERS);\n const importedFromPath = baseUrl ?? \"https://api.mem0.ai\";\n // Forward the validated CLI `--rate-limit` (now carried through\n // ImporterParseOptions.rateLimit by runImporter) into the fetch client\n // so `remnic import --adapter mem0 --rate-limit 2` actually throttles.\n // Cursor review on PR #602 — the original wiring silently ignored it.\n const rateLimit =\n typeof options?.rateLimit === \"number\" ? options.rateLimit : undefined;\n const memories = await fetchAllMem0Memories({\n apiKey,\n ...(baseUrl !== undefined ? { baseUrl } : {}),\n ...(listPath !== undefined ? { listPath } : {}),\n ...(legacyGet !== undefined ? { legacyGet } : {}),\n ...(filters !== undefined ? { filters } : {}),\n ...(rateLimit !== undefined ? { rateLimit } : {}),\n ...(overrideClientOptionsForTesting?.fetchImpl\n ? { fetchImpl: overrideClientOptionsForTesting.fetchImpl }\n : {}),\n ...(overrideClientOptionsForTesting?.sleep\n ? { sleep: overrideClientOptionsForTesting.sleep }\n : {}),\n });\n return { memories, importedFromPath };\n },\n\n transform(\n parsed: ParsedMem0Export,\n options?: ImporterTransformOptions,\n ): ImportedMemory[] {\n return transformMem0Export(parsed, {\n ...(options?.maxMemories !== undefined\n ? { maxMemories: options.maxMemories }\n : {}),\n });\n },\n\n async writeTo(\n target: ImporterWriteTarget,\n memories: ImportedMemory[],\n _options: RunImportOptions,\n ): Promise<ImporterWriteResult> {\n return defaultWriteMemoriesToOrchestrator(target, memories);\n },\n};\n\n/** Alias kept for symmetry with other @remnic/import-* packages. */\nexport const mem0Adapter = adapter;\n\nfunction parseBooleanEnv(value: string | undefined): boolean | undefined {\n if (value === undefined) return undefined;\n const normalized = value.trim().toLowerCase();\n if ([\"1\", \"true\", \"yes\", \"on\"].includes(normalized)) return true;\n if ([\"0\", \"false\", \"no\", \"off\"].includes(normalized)) return false;\n throw new Error(`MEM0_LEGACY_GET must be a boolean-like value, got ${value}`);\n}\n\nfunction parseJsonObjectEnv(name: string, value: string | undefined): Record<string, unknown> | undefined {\n if (value === undefined || value.trim().length === 0) return undefined;\n let parsed: unknown;\n try {\n parsed = JSON.parse(value);\n } catch (err) {\n throw new Error(`${name} must be valid JSON: ${err instanceof Error ? err.message : String(err)}`);\n }\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n throw new Error(`${name} must be a JSON object`);\n }\n return parsed as Record<string, unknown>;\n}\n","// ---------------------------------------------------------------------------\n// Mem0 REST client (issue #568 slice 5)\n// ---------------------------------------------------------------------------\n//\n// mem0.ai exposes a paginated memories list endpoint. A production user will\n// typically hit the hosted service at `https://api.mem0.ai/v1/memories/`,\n// supply a Bearer API key, and pull down their account's memories page by\n// page. Some users self-host and need a configurable base URL.\n//\n// This client is intentionally tiny:\n// - fetch-based; no SDK dependency.\n// - Injectable `fetch` impl so tests can replay a record/replay fixture.\n// - Abort-signal aware for clean cancellation.\n// - Rate-limit aware (sleeps between page requests when `rateLimit` is set\n// on `RunImportOptions`).\n//\n// The adapter calls `fetchAllMem0Memories()` once; it walks pagination and\n// returns a flat array. The transform layer then maps each raw record to an\n// `ImportedMemory`.\n\nexport interface Mem0Memory {\n /** Stable memory id. */\n id: string;\n /** Memory body. API older responses nest this in `memory`. */\n memory?: string;\n content?: string;\n text?: string;\n user_id?: string;\n agent_id?: string;\n created_at?: string;\n updated_at?: string;\n metadata?: Record<string, unknown>;\n categories?: string[];\n score?: number;\n}\n\n/**\n * Shape returned by the paginated memories endpoint. The real API uses\n * `results` + `next` (cursor URL) on v1 and `memories` + `page` + `total`\n * on v0; the client accepts either so tests can replay both.\n */\nexport interface Mem0ListResponse {\n results?: Mem0Memory[];\n memories?: Mem0Memory[];\n next?: string | null;\n total?: number;\n page?: number;\n per_page?: number;\n}\n\nexport interface Mem0ClientOptions {\n apiKey: string;\n /** Default: `https://api.mem0.ai`. Trailing slash tolerated. */\n baseUrl?: string;\n /**\n * Path prefix for the list endpoint. Defaults to `/v1/memories/` for\n * the hosted API. Self-hosted mem0-oss deployments typically expose\n * `/memories/` without the `/v1` prefix — set `MEM0_LIST_PATH` /\n * pass this explicitly in that case. Codex review on PR #602.\n */\n listPath?: string;\n /** Injected for tests. Falls back to `globalThis.fetch`. */\n fetchImpl?: typeof fetch;\n /** Requests per second limiter. Applied between pages. */\n rateLimit?: number;\n /**\n * Filters required by hosted mem0 list requests. Defaults to a broad empty\n * filter body for the current hosted API.\n */\n filters?: Record<string, unknown>;\n /** Use the legacy GET list contract for self-hosted or older v1 deployments. */\n legacyGet?: boolean;\n /** Abort signal wired through to fetch. */\n signal?: AbortSignal;\n /** Sleep function for rate limiting; injectable so tests run instantly. */\n sleep?: (ms: number) => Promise<void>;\n}\n\nconst DEFAULT_BASE_URL = \"https://api.mem0.ai\";\nconst DEFAULT_LIST_PATH = \"/v3/memories/?page=1&page_size=50\";\nconst DEFAULT_LEGACY_GET_LIST_PATH = \"/v1/memories/\";\n\n/**\n * Fetch all mem0 memories across pagination. Returns a flat array; the\n * caller is responsible for deduplication (the orchestrator does this\n * naturally via content hashing).\n */\nexport async function fetchAllMem0Memories(\n options: Mem0ClientOptions,\n): Promise<Mem0Memory[]> {\n if (!options.apiKey || typeof options.apiKey !== \"string\") {\n throw new Error(\"mem0 import requires a non-empty apiKey\");\n }\n const fetchImpl = options.fetchImpl ?? globalThis.fetch;\n if (typeof fetchImpl !== \"function\") {\n throw new Error(\n \"No fetch implementation available. Provide `fetchImpl` or run on Node 18+.\",\n );\n }\n const sleep = options.sleep ?? defaultSleep;\n const base = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, \"\");\n const defaultListPath = options.legacyGet === true\n ? DEFAULT_LEGACY_GET_LIST_PATH\n : DEFAULT_LIST_PATH;\n const listPath = normalizeListPath(options.listPath ?? defaultListPath);\n const intervalMs =\n options.rateLimit && options.rateLimit > 0 ? 1000 / options.rateLimit : 0;\n\n const all: Mem0Memory[] = [];\n const firstUrl = `${base}${listPath}`;\n // Capture the allow-listed origin ONCE so cross-origin cursors can't\n // tunnel the API key to an attacker-controlled host. Codex review on\n // PR #602. We also validate that the configured base URL itself is a\n // parseable absolute URL — mem0 servers that return relative cursors\n // are then resolved against this origin.\n const allowedOrigin = safeUrlOrigin(firstUrl);\n let nextUrl: string | null = firstUrl;\n let pageIndex = 0;\n while (nextUrl) {\n throwIfAborted(options.signal);\n if (pageIndex > 0 && intervalMs > 0) {\n await sleepWithAbort(sleep, intervalMs, options.signal);\n }\n const response = await fetchImpl(nextUrl, buildMem0ListRequest(options));\n if (!response.ok) {\n const body = await safeText(response);\n throw new Error(\n `mem0 API request to ${nextUrl} failed with ${response.status}: ${body}`,\n );\n }\n const json = (await response.json()) as Mem0ListResponse;\n const page = json.results ?? json.memories ?? [];\n for (const entry of page) {\n if (entry && typeof entry === \"object\" && typeof entry.id === \"string\") {\n all.push(entry);\n }\n }\n nextUrl = resolveNextUrl(json, firstUrl, pageIndex, page.length, allowedOrigin);\n pageIndex += 1;\n }\n return all;\n}\n\nfunction buildMem0ListRequest(options: Mem0ClientOptions): RequestInit {\n if (options.legacyGet === true) {\n return {\n method: \"GET\",\n headers: {\n Authorization: `Token ${options.apiKey}`,\n Accept: \"application/json\",\n },\n ...(options.signal ? { signal: options.signal } : {}),\n };\n }\n return {\n method: \"POST\",\n headers: {\n Authorization: `Token ${options.apiKey}`,\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ filters: options.filters ?? {} }),\n ...(options.signal ? { signal: options.signal } : {}),\n };\n}\n\nfunction normalizeListPath(p: string): string {\n const withLeadingSlash = p.startsWith(\"/\") ? p : `/${p}`;\n if (withLeadingSlash.includes(\"?\")) return withLeadingSlash;\n return withLeadingSlash.endsWith(\"/\") ? withLeadingSlash : `${withLeadingSlash}/`;\n}\n\nfunction safeUrlOrigin(url: string): string | undefined {\n try {\n return new URL(url).origin;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Decide the next URL to request.\n *\n * Preferred: the `next` cursor the server returns (v1 shape). When `next`\n * is a **relative** path, it's resolved against the configured base URL.\n * When absolute, it MUST match the allow-listed origin — cross-origin\n * cursors are rejected so an upstream/proxy can't exfiltrate the API key\n * by redirecting the pagination walk. Codex review on PR #602.\n *\n * Fallback: when `next` is explicitly `null` (server says \"no more pages\"),\n * stop. When `next` is absent (field omitted) AND the response exposes\n * numeric pagination fields (`page` / `total` / `per_page`, the older v0\n * shape), synthesize a `?page=<N>` request for the next page. Cursor\n * review on PR #602 flagged that servers returning only numeric\n * pagination were silently truncated under the original code, AND that\n * the implementation conflated `null` with `undefined`.\n */\nfunction resolveNextUrl(\n json: Mem0ListResponse,\n firstUrl: string,\n currentPageIndex: number,\n pageSize: number,\n allowedOrigin: string | undefined,\n): string | null {\n // Has the server explicitly returned a next value?\n if (\"next\" in json) {\n if (json.next === null) return null; // authoritative stop\n if (typeof json.next === \"string\" && json.next.length > 0) {\n return resolveCursorOrThrow(json.next, firstUrl, allowedOrigin);\n }\n // `next` was present but not a non-empty string or null (e.g. number).\n // Don't fall through to numeric pagination — the server sent a signal\n // we can't interpret. Treat as end-of-stream.\n return null;\n }\n\n // Numeric-pagination fallback. `page` is 1-based in the real API.\n const responsePage = typeof json.page === \"number\" && Number.isFinite(json.page)\n ? json.page\n : currentPageIndex + 1;\n const perPage = typeof json.per_page === \"number\" && Number.isFinite(json.per_page)\n ? json.per_page\n : pageSize;\n const total = typeof json.total === \"number\" && Number.isFinite(json.total)\n ? json.total\n : undefined;\n if (total === undefined || perPage <= 0) return null;\n const fetchedSoFar = responsePage * perPage;\n if (fetchedSoFar >= total) return null;\n const nextPage = responsePage + 1;\n try {\n const u = new URL(firstUrl);\n u.searchParams.set(\"page\", String(nextPage));\n return u.toString();\n } catch {\n return null;\n }\n}\n\n/**\n * Resolve a server-provided cursor against the base URL and enforce\n * same-origin. Relative cursors are accepted (resolved against firstUrl);\n * absolute cursors must match `allowedOrigin`. Throws on cross-origin to\n * surface the security-relevant mismatch immediately instead of silently\n * leaking the API key.\n */\nfunction resolveCursorOrThrow(\n cursor: string,\n firstUrl: string,\n allowedOrigin: string | undefined,\n): string {\n if (!allowedOrigin) {\n // Base URL wasn't parseable as an absolute URL; refuse to follow any\n // cursor because we can't compare origins.\n throw new Error(\n `mem0 pagination cursor '${cursor}' cannot be followed: configured baseUrl is not an absolute URL.`,\n );\n }\n let resolved: URL;\n try {\n resolved = new URL(cursor, firstUrl);\n } catch {\n throw new Error(\n `mem0 pagination cursor '${cursor}' is not a valid URL.`,\n );\n }\n if (resolved.origin !== allowedOrigin) {\n throw new Error(\n `mem0 pagination cursor '${cursor}' points to origin '${resolved.origin}', ` +\n `but the configured mem0 origin is '${allowedOrigin}'. ` +\n \"Refusing to forward the API key to a cross-origin endpoint.\",\n );\n }\n return resolved.toString();\n}\n\nfunction throwIfAborted(signal: AbortSignal | undefined): void {\n if (signal?.aborted) {\n throw makeAbortError();\n }\n}\n\nfunction makeAbortError(): Error {\n const err = new Error(\"mem0 import aborted\");\n (err as Error & { name: string }).name = \"AbortError\";\n return err;\n}\n\nfunction sleepWithAbort(\n sleep: (ms: number) => Promise<void>,\n ms: number,\n signal: AbortSignal | undefined,\n): Promise<void> {\n if (!signal) return sleep(ms);\n if (signal.aborted) return Promise.reject(makeAbortError());\n\n let removeAbortListener: (() => void) | undefined;\n const abortPromise = new Promise<never>((_, reject) => {\n const onAbort = () => {\n removeAbortListener?.();\n reject(makeAbortError());\n };\n signal.addEventListener(\"abort\", onAbort, { once: true });\n removeAbortListener = () => signal.removeEventListener(\"abort\", onAbort);\n });\n\n return Promise.race([sleep(ms), abortPromise]).finally(() => {\n removeAbortListener?.();\n });\n}\n\nasync function safeText(response: Response): Promise<string> {\n try {\n return (await response.text()).slice(0, 500);\n } catch {\n return \"(failed to read response body)\";\n }\n}\n\nfunction defaultSleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","// ---------------------------------------------------------------------------\n// mem0 parser (issue #568 slice 5)\n// ---------------------------------------------------------------------------\n//\n// Unlike the file-based importers, mem0 pulls data directly from an API. The\n// \"parse\" step therefore either:\n//\n// 1. Accepts an already-fetched `Mem0Memory[]` (the adapter's primary path,\n// after the API client pulls everything down).\n// 2. Accepts a JSON string / parsed object for record/replay fixtures so\n// tests and offline flows don't need network access.\n//\n// Non-object entries are dropped in non-strict mode (CLAUDE.md rule 51\n// applies only to user-facing CLI inputs; parser leniency here protects\n// against server-side schema drift without crashing the import).\n\nimport type { Mem0Memory } from \"./client.js\";\n\nexport interface ParsedMem0Export {\n memories: Mem0Memory[];\n /** Provenance — API endpoint URL or replay fixture path. */\n importedFromPath?: string;\n}\n\nexport interface Mem0ParseOptions {\n strict?: boolean;\n filePath?: string;\n}\n\n/**\n * Parse a mem0 payload. Accepts:\n * - a `Mem0Memory[]` (already fetched)\n * - a JSON string\n * - an object like `{ results: [...] }` or `{ memories: [...] }` or\n * `{ all_pages: [...] }` (combined replay fixture)\n */\nexport function parseMem0Export(\n input: unknown,\n options: Mem0ParseOptions = {},\n): ParsedMem0Export {\n const raw = coerceJson(input);\n const memories: Mem0Memory[] = [];\n\n if (Array.isArray(raw)) {\n appendMemories(memories, raw, options);\n return withFilePath(memories, options.filePath);\n }\n\n if (raw && typeof raw === \"object\") {\n const obj = raw as Record<string, unknown>;\n let recognizedTopLevelShape = false;\n for (const key of [\"results\", \"memories\", \"all_pages\"] as const) {\n const v = obj[key];\n if (Array.isArray(v)) {\n recognizedTopLevelShape = true;\n appendMemories(memories, v, options);\n }\n }\n // `pages`: an array of page responses (replay fixture for multi-page\n // pulls). We flatten each page's `results` / `memories`.\n const pages = obj.pages;\n if (Array.isArray(pages)) {\n recognizedTopLevelShape = true;\n for (const page of pages) {\n if (page && typeof page === \"object\") {\n const p = page as Record<string, unknown>;\n for (const key of [\"results\", \"memories\"] as const) {\n const v = p[key];\n if (Array.isArray(v)) appendMemories(memories, v, options);\n }\n }\n }\n }\n if (!recognizedTopLevelShape) {\n throw new Error(\n \"mem0 export object must contain a results, memories, all_pages, or pages array\",\n );\n }\n return withFilePath(memories, options.filePath);\n }\n\n throw new Error(\n `mem0 export must be an array or recognized object; received ${describePayloadType(raw)}`,\n );\n}\n\nfunction appendMemories(\n dest: Mem0Memory[],\n src: unknown[],\n options: Mem0ParseOptions,\n): void {\n for (const entry of src) {\n if (!entry || typeof entry !== \"object\") {\n if (options.strict) throw new Error(\"mem0 entry must be an object\");\n continue;\n }\n const record = entry as Mem0Memory;\n if (typeof record.id !== \"string\" || record.id.length === 0) {\n if (options.strict) throw new Error(\"mem0 entry missing id\");\n continue;\n }\n dest.push(record);\n }\n}\n\nfunction withFilePath(\n memories: Mem0Memory[],\n importedFromPath: string | undefined,\n): ParsedMem0Export {\n return {\n memories,\n ...(importedFromPath !== undefined ? { importedFromPath } : {}),\n };\n}\n\nfunction coerceJson(input: unknown): unknown {\n if (typeof input === \"string\") {\n try {\n return JSON.parse(input);\n } catch (err) {\n throw new Error(\n `mem0 payload is not valid JSON: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n }\n return input;\n}\n\nfunction describePayloadType(value: unknown): string {\n if (value === null) return \"null\";\n if (Array.isArray(value)) return \"array\";\n return typeof value;\n}\n\n/** Extract the memory body, preferring explicit `memory` then `content` then `text`. */\nexport function extractMemoryBody(entry: Mem0Memory): string | undefined {\n for (const candidate of [entry.memory, entry.content, entry.text]) {\n if (typeof candidate === \"string\") {\n const trimmed = candidate.trim();\n if (trimmed.length > 0) return trimmed;\n }\n }\n return undefined;\n}\n","// ---------------------------------------------------------------------------\n// mem0 parsed → ImportedMemory transform (issue #568 slice 5)\n// ---------------------------------------------------------------------------\n\nimport type { ImportedMemory } from \"@remnic/core\";\n\nimport type { Mem0Memory } from \"./client.js\";\nimport type { ParsedMem0Export } from \"./parser.js\";\nimport { extractMemoryBody } from \"./parser.js\";\n\nexport const MEM0_SOURCE_LABEL = \"mem0\";\n\nexport interface Mem0TransformOptions {\n /** Optional cap on total memories emitted — primarily for tests. */\n maxMemories?: number;\n}\n\nexport function transformMem0Export(\n parsed: ParsedMem0Export,\n options: Mem0TransformOptions = {},\n): ImportedMemory[] {\n const out: ImportedMemory[] = [];\n const cap = options.maxMemories;\n for (const entry of parsed.memories) {\n if (cap !== undefined && out.length >= cap) return out;\n const memory = mem0ToImported(entry, parsed.importedFromPath);\n if (memory) out.push(memory);\n }\n return out;\n}\n\nfunction mem0ToImported(\n entry: Mem0Memory,\n importedFromPath: string | undefined,\n): ImportedMemory | undefined {\n const content = extractMemoryBody(entry);\n if (!content) return undefined;\n const sourceTimestamp = entry.updated_at ?? entry.created_at;\n const metadata: Record<string, unknown> = { kind: \"mem0_memory\" };\n if (entry.user_id) metadata.userId = entry.user_id;\n if (entry.agent_id) metadata.agentId = entry.agent_id;\n if (Array.isArray(entry.categories) && entry.categories.length > 0) {\n metadata.categories = [...entry.categories];\n }\n if (entry.metadata && typeof entry.metadata === \"object\") {\n metadata.sourceMetadata = entry.metadata;\n }\n return {\n content,\n sourceLabel: MEM0_SOURCE_LABEL,\n sourceId: entry.id,\n ...(sourceTimestamp !== undefined ? { sourceTimestamp } : {}),\n ...(importedFromPath !== undefined ? { importedFromPath } : {}),\n metadata,\n };\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remnic/import-mem0",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "9.3.517",
|
|
4
4
|
"description": "Import memories from the mem0.ai REST API into Remnic (issue #568)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -11,25 +11,21 @@
|
|
|
11
11
|
"import": "./dist/index.js"
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
|
-
"files": [
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"check-types": "tsc --noEmit",
|
|
18
|
-
"test": "tsx --test src/adapter.test.ts src/parser.test.ts src/transform.test.ts src/client.test.ts",
|
|
19
|
-
"prepublishOnly": "npm run build"
|
|
20
|
-
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
21
17
|
"publishConfig": {
|
|
22
18
|
"access": "public",
|
|
23
19
|
"provenance": true
|
|
24
20
|
},
|
|
25
21
|
"peerDependencies": {
|
|
26
|
-
"@remnic/core": "
|
|
22
|
+
"@remnic/core": "^9.3.517"
|
|
27
23
|
},
|
|
28
24
|
"devDependencies": {
|
|
29
|
-
"@remnic/core": "workspace:*",
|
|
30
25
|
"tsup": "^8.0.0",
|
|
31
26
|
"tsx": "^4.0.0",
|
|
32
|
-
"typescript": "^5.7.0"
|
|
27
|
+
"typescript": "^5.7.0",
|
|
28
|
+
"@remnic/core": "9.3.517"
|
|
33
29
|
},
|
|
34
30
|
"license": "MIT",
|
|
35
31
|
"repository": {
|
|
@@ -37,5 +33,16 @@
|
|
|
37
33
|
"url": "https://github.com/joshuaswarren/remnic.git",
|
|
38
34
|
"directory": "packages/import-mem0"
|
|
39
35
|
},
|
|
40
|
-
"keywords": [
|
|
41
|
-
|
|
36
|
+
"keywords": [
|
|
37
|
+
"remnic",
|
|
38
|
+
"memory",
|
|
39
|
+
"mem0",
|
|
40
|
+
"import"
|
|
41
|
+
],
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsup src/index.ts --format esm --dts",
|
|
44
|
+
"precheck-types": "node ../../scripts/ensure-bench-build-deps.mjs",
|
|
45
|
+
"check-types": "tsc --noEmit",
|
|
46
|
+
"test": "tsx --test src/adapter.test.ts src/parser.test.ts src/transform.test.ts src/client.test.ts"
|
|
47
|
+
}
|
|
48
|
+
}
|