mr-memory 3.7.4 → 3.7.6
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/index.js +72 -68
- package/index.ts +70 -64
- package/memoryrouter-api.js +49 -0
- package/memoryrouter-api.ts +60 -0
- package/package.json +5 -1
- package/upload-api.js +40 -0
- package/upload-api.ts +45 -0
- package/upload.js +9 -34
- package/upload.ts +9 -39
package/index.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* BYOK — provider API keys never touch MemoryRouter.
|
|
9
9
|
*/
|
|
10
10
|
import { readFile, lstat, writeFile, copyFile, mkdir } from "node:fs/promises";
|
|
11
|
+
import { deleteMemories, getMemoryStats, ingestMemory, prepareMemory, searchMemory } from "./memoryrouter-api.js";
|
|
11
12
|
import { join, resolve, relative, isAbsolute, sep, dirname } from "node:path";
|
|
12
13
|
import os from "node:os";
|
|
13
14
|
const DEFAULT_ENDPOINT = "https://api.memoryrouter.ai";
|
|
@@ -265,9 +266,6 @@ const WORKSPACE_FILES = [
|
|
|
265
266
|
"IDENTITY.md", "USER.md", "MEMORY.md", "HEARTBEAT.md",
|
|
266
267
|
"TOOLS.md", "AGENTS.md", "SOUL.md", "BOOTSTRAP.md",
|
|
267
268
|
];
|
|
268
|
-
// ──────────────────────────────────────────────────────
|
|
269
|
-
// Helpers
|
|
270
|
-
// ──────────────────────────────────────────────────────
|
|
271
269
|
function openClawConfigPath() {
|
|
272
270
|
return process.env.OPENCLAW_CONFIG || join(os.homedir(), ".openclaw", "openclaw.json");
|
|
273
271
|
}
|
|
@@ -291,8 +289,47 @@ async function writeOpenClawConfigFile(config) {
|
|
|
291
289
|
if (err?.code !== "ENOENT")
|
|
292
290
|
throw err;
|
|
293
291
|
}
|
|
294
|
-
await writeFile(configPath, `${JSON.stringify(config, null, 2)}
|
|
292
|
+
await writeFile(configPath, `${JSON.stringify(config, null, 2)}
|
|
293
|
+
`, "utf8");
|
|
294
|
+
}
|
|
295
|
+
// ──────────────────────────────────────────────────────
|
|
296
|
+
// Full-replace: disable OpenClaw's native memory search so MemoryRouter
|
|
297
|
+
// owns the memory_search tool (no name conflict, no native embeddings 429).
|
|
298
|
+
// memoryFlush (durable MEMORY.md writes) is intentionally left untouched.
|
|
299
|
+
// Global + subagents: writes agents.defaults.memorySearch.enabled.
|
|
300
|
+
// ──────────────────────────────────────────────────────
|
|
301
|
+
async function setNativeMemorySearchEnabled(enabled) {
|
|
302
|
+
const config = await readOpenClawConfigFile();
|
|
303
|
+
config.agents ??= {};
|
|
304
|
+
config.agents.defaults ??= {};
|
|
305
|
+
config.agents.defaults.memorySearch ??= {};
|
|
306
|
+
if (config.agents.defaults.memorySearch.enabled === enabled) return false; // no-op
|
|
307
|
+
config.agents.defaults.memorySearch.enabled = enabled;
|
|
308
|
+
await writeOpenClawConfigFile(config);
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Boot-time self-heal: if a key is configured but native search is still on,
|
|
313
|
+
// flip it off so already-installed users are fixed on restart after updating
|
|
314
|
+
// (no need to re-run `openclaw mr <key>`).
|
|
315
|
+
async function selfHealNativeMemorySearch(api) {
|
|
316
|
+
try {
|
|
317
|
+
const config = await readOpenClawConfigFile();
|
|
318
|
+
const ms = config?.agents?.defaults?.memorySearch;
|
|
319
|
+
if (ms?.enabled === false) return; // already disabled — nothing to do
|
|
320
|
+
const changed = await setNativeMemorySearchEnabled(false);
|
|
321
|
+
if (changed) {
|
|
322
|
+
api.logger.info?.(
|
|
323
|
+
"memoryrouter: disabled native memory search (full-replace). Restart gateway to apply.",
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
} catch (err) {
|
|
327
|
+
api.logger.info?.(
|
|
328
|
+
`memoryrouter: self-heal skipped — ${err instanceof Error ? err.message : String(err)}`,
|
|
329
|
+
);
|
|
330
|
+
}
|
|
295
331
|
}
|
|
332
|
+
|
|
296
333
|
async function updateOpenClawPluginEntry(api, patch) {
|
|
297
334
|
const config = await readOpenClawConfigFile();
|
|
298
335
|
config.plugins ??= {};
|
|
@@ -402,6 +439,8 @@ const memoryRouterPlugin = {
|
|
|
402
439
|
api.logger.info?.(`memoryrouter: active (key: ${memoryKey.slice(0, 6)}...)`);
|
|
403
440
|
if (agentKeys)
|
|
404
441
|
api.logger.info?.(`memoryrouter: ${Object.keys(agentKeys).length} agent key(s) configured`);
|
|
442
|
+
// Self-heal already-configured installs: disable native search if still on.
|
|
443
|
+
void selfHealNativeMemorySearch(api);
|
|
405
444
|
}
|
|
406
445
|
else {
|
|
407
446
|
api.logger.info?.("memoryrouter: no key configured — run: openclaw mr <key>");
|
|
@@ -545,17 +584,10 @@ If setting up MemoryRouter for another agent:
|
|
|
545
584
|
contextPayload.push({ role: "user", content: taskPrompt });
|
|
546
585
|
}
|
|
547
586
|
}
|
|
548
|
-
const res = await
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
Authorization: `Bearer ${activeKey}`,
|
|
553
|
-
},
|
|
554
|
-
body: JSON.stringify({
|
|
555
|
-
messages: contextPayload,
|
|
556
|
-
density,
|
|
557
|
-
...(embeddings && { embeddings }),
|
|
558
|
-
}),
|
|
587
|
+
const res = await prepareMemory(endpoint, activeKey, {
|
|
588
|
+
messages: contextPayload,
|
|
589
|
+
density,
|
|
590
|
+
...(embeddings && { embeddings }),
|
|
559
591
|
});
|
|
560
592
|
if (!res.ok) {
|
|
561
593
|
const errBody = await res.json().catch(() => null);
|
|
@@ -650,17 +682,10 @@ If setting up MemoryRouter for another agent:
|
|
|
650
682
|
}
|
|
651
683
|
}
|
|
652
684
|
// 4. Call /v1/memory/prepare (no session_id — always search core vault)
|
|
653
|
-
const res = await
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
Authorization: `Bearer ${activeKey}`,
|
|
658
|
-
},
|
|
659
|
-
body: JSON.stringify({
|
|
660
|
-
messages: contextPayload,
|
|
661
|
-
density,
|
|
662
|
-
...(embeddings && { embeddings }),
|
|
663
|
-
}),
|
|
685
|
+
const res = await prepareMemory(endpoint, activeKey, {
|
|
686
|
+
messages: contextPayload,
|
|
687
|
+
density,
|
|
688
|
+
...(embeddings && { embeddings }),
|
|
664
689
|
});
|
|
665
690
|
if (!res.ok) {
|
|
666
691
|
const errBody = await res.json().catch(() => null);
|
|
@@ -762,17 +787,10 @@ If setting up MemoryRouter for another agent:
|
|
|
762
787
|
return;
|
|
763
788
|
}
|
|
764
789
|
// Fire and forget — don't block message delivery waiting for ingest
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
Authorization: `Bearer ${activeKey}`,
|
|
770
|
-
},
|
|
771
|
-
body: JSON.stringify({
|
|
772
|
-
messages: toStore,
|
|
773
|
-
model: "unknown",
|
|
774
|
-
...(embeddings && { embeddings }),
|
|
775
|
-
}),
|
|
790
|
+
ingestMemory(endpoint, activeKey, {
|
|
791
|
+
messages: toStore,
|
|
792
|
+
model: "unknown",
|
|
793
|
+
...(embeddings && { embeddings }),
|
|
776
794
|
}).then(async (res) => {
|
|
777
795
|
if (!res.ok) {
|
|
778
796
|
const details = await res.text().catch(() => "");
|
|
@@ -822,15 +840,7 @@ If setting up MemoryRouter for another agent:
|
|
|
822
840
|
return jsonToolResult({ results: [], error: "query required" });
|
|
823
841
|
const limit = typeof params.maxResults === "number" ? params.maxResults : 50;
|
|
824
842
|
try {
|
|
825
|
-
const res = await
|
|
826
|
-
method: "POST",
|
|
827
|
-
headers: {
|
|
828
|
-
Authorization: `Bearer ${toolKey}`,
|
|
829
|
-
"Content-Type": "application/json",
|
|
830
|
-
...(embeddings && { "X-Embedding-Model": embeddings }),
|
|
831
|
-
},
|
|
832
|
-
body: JSON.stringify({ query, limit }),
|
|
833
|
-
});
|
|
843
|
+
const res = await searchMemory(endpoint, toolKey, { query, limit, embeddings });
|
|
834
844
|
if (!res.ok) {
|
|
835
845
|
const errBody = await res.text().catch(() => "");
|
|
836
846
|
return jsonToolResult({ results: [], error: `Search failed: HTTP ${res.status}`, details: errBody.slice(0, 200) });
|
|
@@ -911,6 +921,12 @@ If setting up MemoryRouter for another agent:
|
|
|
911
921
|
try {
|
|
912
922
|
await setPluginConfig(api, { key });
|
|
913
923
|
await setPluginEnabled(api, true);
|
|
924
|
+
try {
|
|
925
|
+
const changed = await setNativeMemorySearchEnabled(false);
|
|
926
|
+
if (changed) console.log(` • Native memory search disabled — MemoryRouter now owns memory_search`);
|
|
927
|
+
} catch (err) {
|
|
928
|
+
console.error(` ⚠ Could not disable native memory search: ${err instanceof Error ? err.message : String(err)}`);
|
|
929
|
+
}
|
|
914
930
|
console.log(`✓ MemoryRouter enabled. Key: ${key.slice(0, 6)}...${key.slice(-3)}`);
|
|
915
931
|
console.log(` • memory_search + memory_get tools registered via plugin`);
|
|
916
932
|
console.log(`\nRun: openclaw mr upload to upload your memories`);
|
|
@@ -943,6 +959,13 @@ If setting up MemoryRouter for another agent:
|
|
|
943
959
|
// This avoids the catch-22 where disabling the plugin removes the `mr` command
|
|
944
960
|
await setPluginConfig(api, {});
|
|
945
961
|
// Note: we intentionally do NOT call setPluginEnabled(api, false)
|
|
962
|
+
// Restore native memory search so the user isn't left with NO memory search.
|
|
963
|
+
try {
|
|
964
|
+
const changed = await setNativeMemorySearchEnabled(true);
|
|
965
|
+
if (changed) console.log(" • Native memory search restored");
|
|
966
|
+
} catch (err) {
|
|
967
|
+
console.error(` ⚠ Could not restore native memory search: ${err instanceof Error ? err.message : String(err)}`);
|
|
968
|
+
}
|
|
946
969
|
console.log("✓ MemoryRouter disabled (key cleared).");
|
|
947
970
|
console.log(" • CLI still available — run `openclaw mr <key>` to re-enable");
|
|
948
971
|
}
|
|
@@ -1023,12 +1046,7 @@ If setting up MemoryRouter for another agent:
|
|
|
1023
1046
|
return;
|
|
1024
1047
|
}
|
|
1025
1048
|
try {
|
|
1026
|
-
const
|
|
1027
|
-
? `${endpoint}/v1/memory/stats?embeddings=${encodeURIComponent(embeddings)}`
|
|
1028
|
-
: `${endpoint}/v1/memory/stats`;
|
|
1029
|
-
const res = await fetch(statsUrl, {
|
|
1030
|
-
headers: { Authorization: `Bearer ${effectiveKey}` },
|
|
1031
|
-
});
|
|
1049
|
+
const res = await getMemoryStats(endpoint, effectiveKey, embeddings);
|
|
1032
1050
|
const data = (await res.json());
|
|
1033
1051
|
if (opts.json) {
|
|
1034
1052
|
console.log(JSON.stringify({ enabled: true, key: effectiveKey, density, stats: data }, null, 2));
|
|
@@ -1086,15 +1104,7 @@ If setting up MemoryRouter for another agent:
|
|
|
1086
1104
|
}
|
|
1087
1105
|
const limit = parseInt(opts.limit, 10) || 50;
|
|
1088
1106
|
try {
|
|
1089
|
-
const res = await
|
|
1090
|
-
method: "POST",
|
|
1091
|
-
headers: {
|
|
1092
|
-
Authorization: `Bearer ${effectiveKey}`,
|
|
1093
|
-
"Content-Type": "application/json",
|
|
1094
|
-
...(embeddings && { "X-Embedding-Model": embeddings }),
|
|
1095
|
-
},
|
|
1096
|
-
body: JSON.stringify({ query, limit }),
|
|
1097
|
-
});
|
|
1107
|
+
const res = await searchMemory(endpoint, effectiveKey, { query, limit, embeddings });
|
|
1098
1108
|
if (!res.ok) {
|
|
1099
1109
|
const err = await res.json();
|
|
1100
1110
|
console.error(`Search failed: ${err.error || `HTTP ${res.status}`}`);
|
|
@@ -1142,13 +1152,7 @@ If setting up MemoryRouter for another agent:
|
|
|
1142
1152
|
return;
|
|
1143
1153
|
}
|
|
1144
1154
|
try {
|
|
1145
|
-
const
|
|
1146
|
-
? `${endpoint}/v1/memory?embeddings=${encodeURIComponent(embeddings)}`
|
|
1147
|
-
: `${endpoint}/v1/memory`;
|
|
1148
|
-
const res = await fetch(deleteUrl, {
|
|
1149
|
-
method: "DELETE",
|
|
1150
|
-
headers: { Authorization: `Bearer ${effectiveKey}` },
|
|
1151
|
-
});
|
|
1155
|
+
const res = await deleteMemories(endpoint, effectiveKey, embeddings);
|
|
1152
1156
|
const data = (await res.json());
|
|
1153
1157
|
const modelLabel = embeddings ? ` (${embeddings})` : "";
|
|
1154
1158
|
console.log(`✓ ${data.message || `Vault cleared${modelLabel}`}`);
|
package/index.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { readFile, readdir, stat, lstat, writeFile, copyFile, mkdir } from "node:fs/promises";
|
|
12
|
+
import { deleteMemories, getMemoryStats, ingestMemory, prepareMemory, searchMemory } from "./memoryrouter-api.js";
|
|
12
13
|
import { join, resolve, relative, isAbsolute, sep, dirname } from "node:path";
|
|
13
14
|
import path from "node:path";
|
|
14
15
|
import os from "node:os";
|
|
@@ -319,6 +320,44 @@ async function writeOpenClawConfigFile(config: JsonObject): Promise<void> {
|
|
|
319
320
|
`, "utf8");
|
|
320
321
|
}
|
|
321
322
|
|
|
323
|
+
// ──────────────────────────────────────────────────────
|
|
324
|
+
// Full-replace: disable OpenClaw's native memory search so MemoryRouter
|
|
325
|
+
// owns the memory_search tool (no name conflict, no native embeddings 429).
|
|
326
|
+
// memoryFlush (durable MEMORY.md writes) is intentionally left untouched.
|
|
327
|
+
// Global + subagents: writes agents.defaults.memorySearch.enabled.
|
|
328
|
+
// ──────────────────────────────────────────────────────
|
|
329
|
+
async function setNativeMemorySearchEnabled(enabled: boolean): Promise<boolean> {
|
|
330
|
+
const config = await readOpenClawConfigFile();
|
|
331
|
+
config.agents ??= {};
|
|
332
|
+
config.agents.defaults ??= {};
|
|
333
|
+
config.agents.defaults.memorySearch ??= {};
|
|
334
|
+
if (config.agents.defaults.memorySearch.enabled === enabled) return false; // no-op
|
|
335
|
+
config.agents.defaults.memorySearch.enabled = enabled;
|
|
336
|
+
await writeOpenClawConfigFile(config);
|
|
337
|
+
return true;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Boot-time self-heal: if a key is configured but native search is still on,
|
|
341
|
+
// flip it off so already-installed users are fixed on restart after updating
|
|
342
|
+
// (no need to re-run `openclaw mr <key>`).
|
|
343
|
+
async function selfHealNativeMemorySearch(api: OpenClawPluginApi): Promise<void> {
|
|
344
|
+
try {
|
|
345
|
+
const config = await readOpenClawConfigFile();
|
|
346
|
+
const ms = config?.agents?.defaults?.memorySearch;
|
|
347
|
+
if (ms?.enabled === false) return; // already disabled — nothing to do
|
|
348
|
+
const changed = await setNativeMemorySearchEnabled(false);
|
|
349
|
+
if (changed) {
|
|
350
|
+
api.logger.info?.(
|
|
351
|
+
"memoryrouter: disabled native memory search (full-replace). Restart gateway to apply.",
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
} catch (err) {
|
|
355
|
+
api.logger.info?.(
|
|
356
|
+
`memoryrouter: self-heal skipped — ${err instanceof Error ? err.message : String(err)}`,
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
322
361
|
async function updateOpenClawPluginEntry(api: OpenClawPluginApi, patch: JsonObject): Promise<void> {
|
|
323
362
|
const config = await readOpenClawConfigFile();
|
|
324
363
|
config.plugins ??= {};
|
|
@@ -425,6 +464,8 @@ const memoryRouterPlugin = {
|
|
|
425
464
|
if (hasAnyKey) {
|
|
426
465
|
if (memoryKey) api.logger.info?.(`memoryrouter: active (key: ${memoryKey.slice(0, 6)}...)`);
|
|
427
466
|
if (agentKeys) api.logger.info?.(`memoryrouter: ${Object.keys(agentKeys).length} agent key(s) configured`);
|
|
467
|
+
// Self-heal already-configured installs: disable native search if still on.
|
|
468
|
+
void selfHealNativeMemorySearch(api);
|
|
428
469
|
} else {
|
|
429
470
|
api.logger.info?.("memoryrouter: no key configured — run: openclaw mr <key>");
|
|
430
471
|
api.logger.info?.("memoryrouter: get your free API key at https://memoryrouter.ai");
|
|
@@ -570,17 +611,10 @@ If setting up MemoryRouter for another agent:
|
|
|
570
611
|
}
|
|
571
612
|
|
|
572
613
|
|
|
573
|
-
const res = await
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
Authorization: `Bearer ${activeKey}`,
|
|
578
|
-
},
|
|
579
|
-
body: JSON.stringify({
|
|
580
|
-
messages: contextPayload,
|
|
581
|
-
density,
|
|
582
|
-
...(embeddings && { embeddings }),
|
|
583
|
-
}),
|
|
614
|
+
const res = await prepareMemory(endpoint, activeKey, {
|
|
615
|
+
messages: contextPayload,
|
|
616
|
+
density,
|
|
617
|
+
...(embeddings && { embeddings }),
|
|
584
618
|
});
|
|
585
619
|
|
|
586
620
|
if (!res.ok) {
|
|
@@ -686,17 +720,10 @@ If setting up MemoryRouter for another agent:
|
|
|
686
720
|
|
|
687
721
|
// 4. Call /v1/memory/prepare (no session_id — always search core vault)
|
|
688
722
|
|
|
689
|
-
const res = await
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
Authorization: `Bearer ${activeKey}`,
|
|
694
|
-
},
|
|
695
|
-
body: JSON.stringify({
|
|
696
|
-
messages: contextPayload,
|
|
697
|
-
density,
|
|
698
|
-
...(embeddings && { embeddings }),
|
|
699
|
-
}),
|
|
723
|
+
const res = await prepareMemory(endpoint, activeKey, {
|
|
724
|
+
messages: contextPayload,
|
|
725
|
+
density,
|
|
726
|
+
...(embeddings && { embeddings }),
|
|
700
727
|
});
|
|
701
728
|
|
|
702
729
|
if (!res.ok) {
|
|
@@ -811,17 +838,10 @@ If setting up MemoryRouter for another agent:
|
|
|
811
838
|
}
|
|
812
839
|
|
|
813
840
|
// Fire and forget — don't block message delivery waiting for ingest
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
Authorization: `Bearer ${activeKey}`,
|
|
819
|
-
},
|
|
820
|
-
body: JSON.stringify({
|
|
821
|
-
messages: toStore,
|
|
822
|
-
model: "unknown",
|
|
823
|
-
...(embeddings && { embeddings }),
|
|
824
|
-
}),
|
|
841
|
+
ingestMemory(endpoint, activeKey, {
|
|
842
|
+
messages: toStore,
|
|
843
|
+
model: "unknown",
|
|
844
|
+
...(embeddings && { embeddings }),
|
|
825
845
|
}).then(async (res) => {
|
|
826
846
|
if (!res.ok) {
|
|
827
847
|
const details = await res.text().catch(() => "");
|
|
@@ -869,15 +889,7 @@ If setting up MemoryRouter for another agent:
|
|
|
869
889
|
if (!query) return jsonToolResult({ results: [], error: "query required" });
|
|
870
890
|
const limit = typeof params.maxResults === "number" ? params.maxResults : 50;
|
|
871
891
|
try {
|
|
872
|
-
const res = await
|
|
873
|
-
method: "POST",
|
|
874
|
-
headers: {
|
|
875
|
-
Authorization: `Bearer ${toolKey}`,
|
|
876
|
-
"Content-Type": "application/json",
|
|
877
|
-
...(embeddings && { "X-Embedding-Model": embeddings }),
|
|
878
|
-
},
|
|
879
|
-
body: JSON.stringify({ query, limit }),
|
|
880
|
-
});
|
|
892
|
+
const res = await searchMemory(endpoint, toolKey, { query, limit, embeddings });
|
|
881
893
|
if (!res.ok) {
|
|
882
894
|
const errBody = await res.text().catch(() => "");
|
|
883
895
|
return jsonToolResult({ results: [], error: `Search failed: HTTP ${res.status}`, details: errBody.slice(0, 200) });
|
|
@@ -963,6 +975,12 @@ If setting up MemoryRouter for another agent:
|
|
|
963
975
|
try {
|
|
964
976
|
await setPluginConfig(api, { key });
|
|
965
977
|
await setPluginEnabled(api, true);
|
|
978
|
+
try {
|
|
979
|
+
const changed = await setNativeMemorySearchEnabled(false);
|
|
980
|
+
if (changed) console.log(` • Native memory search disabled — MemoryRouter now owns memory_search`);
|
|
981
|
+
} catch (err) {
|
|
982
|
+
console.error(` ⚠ Could not disable native memory search: ${err instanceof Error ? err.message : String(err)}`);
|
|
983
|
+
}
|
|
966
984
|
console.log(`✓ MemoryRouter enabled. Key: ${key.slice(0, 6)}...${key.slice(-3)}`);
|
|
967
985
|
console.log(` • memory_search + memory_get tools registered via plugin`);
|
|
968
986
|
console.log(`\nRun: openclaw mr upload to upload your memories`);
|
|
@@ -994,6 +1012,13 @@ If setting up MemoryRouter for another agent:
|
|
|
994
1012
|
// This avoids the catch-22 where disabling the plugin removes the `mr` command
|
|
995
1013
|
await setPluginConfig(api, {});
|
|
996
1014
|
// Note: we intentionally do NOT call setPluginEnabled(api, false)
|
|
1015
|
+
// Restore native memory search so the user isn't left with NO memory search.
|
|
1016
|
+
try {
|
|
1017
|
+
const changed = await setNativeMemorySearchEnabled(true);
|
|
1018
|
+
if (changed) console.log(" • Native memory search restored");
|
|
1019
|
+
} catch (err) {
|
|
1020
|
+
console.error(` ⚠ Could not restore native memory search: ${err instanceof Error ? err.message : String(err)}`);
|
|
1021
|
+
}
|
|
997
1022
|
console.log("✓ MemoryRouter disabled (key cleared).");
|
|
998
1023
|
console.log(" • CLI still available — run `openclaw mr <key>` to re-enable");
|
|
999
1024
|
} catch (err) {
|
|
@@ -1075,12 +1100,7 @@ If setting up MemoryRouter for another agent:
|
|
|
1075
1100
|
return;
|
|
1076
1101
|
}
|
|
1077
1102
|
try {
|
|
1078
|
-
const
|
|
1079
|
-
? `${endpoint}/v1/memory/stats?embeddings=${encodeURIComponent(embeddings)}`
|
|
1080
|
-
: `${endpoint}/v1/memory/stats`;
|
|
1081
|
-
const res = await fetch(statsUrl, {
|
|
1082
|
-
headers: { Authorization: `Bearer ${effectiveKey}` },
|
|
1083
|
-
});
|
|
1103
|
+
const res = await getMemoryStats(endpoint, effectiveKey, embeddings);
|
|
1084
1104
|
const data = (await res.json()) as { totalVectors?: number; totalTokens?: number };
|
|
1085
1105
|
if (opts.json) {
|
|
1086
1106
|
console.log(JSON.stringify({ enabled: true, key: effectiveKey, density, stats: data }, null, 2));
|
|
@@ -1132,15 +1152,7 @@ If setting up MemoryRouter for another agent:
|
|
|
1132
1152
|
if (!effectiveKey) { console.error("Not configured. Run: openclaw mr <key> or pass --key <key>"); return; }
|
|
1133
1153
|
const limit = parseInt(opts.limit, 10) || 50;
|
|
1134
1154
|
try {
|
|
1135
|
-
const res = await
|
|
1136
|
-
method: "POST",
|
|
1137
|
-
headers: {
|
|
1138
|
-
Authorization: `Bearer ${effectiveKey}`,
|
|
1139
|
-
"Content-Type": "application/json",
|
|
1140
|
-
...(embeddings && { "X-Embedding-Model": embeddings }),
|
|
1141
|
-
},
|
|
1142
|
-
body: JSON.stringify({ query, limit }),
|
|
1143
|
-
});
|
|
1155
|
+
const res = await searchMemory(endpoint, effectiveKey, { query, limit, embeddings });
|
|
1144
1156
|
if (!res.ok) {
|
|
1145
1157
|
const err = await res.json() as { error?: string };
|
|
1146
1158
|
console.error(`Search failed: ${err.error || `HTTP ${res.status}`}`);
|
|
@@ -1205,13 +1217,7 @@ If setting up MemoryRouter for another agent:
|
|
|
1205
1217
|
const effectiveKey = opts.key || memoryKey;
|
|
1206
1218
|
if (!effectiveKey) { console.error("Not configured. Run: openclaw mr <key> or pass --key <key>"); return; }
|
|
1207
1219
|
try {
|
|
1208
|
-
const
|
|
1209
|
-
? `${endpoint}/v1/memory?embeddings=${encodeURIComponent(embeddings)}`
|
|
1210
|
-
: `${endpoint}/v1/memory`;
|
|
1211
|
-
const res = await fetch(deleteUrl, {
|
|
1212
|
-
method: "DELETE",
|
|
1213
|
-
headers: { Authorization: `Bearer ${effectiveKey}` },
|
|
1214
|
-
});
|
|
1220
|
+
const res = await deleteMemories(endpoint, effectiveKey, embeddings);
|
|
1215
1221
|
const data = (await res.json()) as { message?: string };
|
|
1216
1222
|
const modelLabel = embeddings ? ` (${embeddings})` : "";
|
|
1217
1223
|
console.log(`✓ ${data.message || `Vault cleared${modelLabel}`}`);
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const JSON_HEADERS = { "Content-Type": "application/json" };
|
|
2
|
+
export async function prepareMemory(endpoint, key, payload) {
|
|
3
|
+
return fetch(`${endpoint}/v1/memory/prepare`, {
|
|
4
|
+
method: "POST",
|
|
5
|
+
headers: {
|
|
6
|
+
...JSON_HEADERS,
|
|
7
|
+
Authorization: `Bearer ${key}`,
|
|
8
|
+
},
|
|
9
|
+
body: JSON.stringify(payload),
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
export function ingestMemory(endpoint, key, payload) {
|
|
13
|
+
return fetch(`${endpoint}/v1/memory/ingest`, {
|
|
14
|
+
method: "POST",
|
|
15
|
+
headers: {
|
|
16
|
+
...JSON_HEADERS,
|
|
17
|
+
Authorization: `Bearer ${key}`,
|
|
18
|
+
},
|
|
19
|
+
body: JSON.stringify(payload),
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
export async function searchMemory(endpoint, key, payload) {
|
|
23
|
+
return fetch(`${endpoint}/v1/memory/search`, {
|
|
24
|
+
method: "POST",
|
|
25
|
+
headers: {
|
|
26
|
+
Authorization: `Bearer ${key}`,
|
|
27
|
+
"Content-Type": "application/json",
|
|
28
|
+
...(payload.embeddings && { "X-Embedding-Model": payload.embeddings }),
|
|
29
|
+
},
|
|
30
|
+
body: JSON.stringify({ query: payload.query, limit: payload.limit }),
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
export async function getMemoryStats(endpoint, key, embeddings) {
|
|
34
|
+
const statsUrl = embeddings
|
|
35
|
+
? `${endpoint}/v1/memory/stats?embeddings=${encodeURIComponent(embeddings)}`
|
|
36
|
+
: `${endpoint}/v1/memory/stats`;
|
|
37
|
+
return fetch(statsUrl, {
|
|
38
|
+
headers: { Authorization: `Bearer ${key}` },
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
export async function deleteMemories(endpoint, key, embeddings) {
|
|
42
|
+
const deleteUrl = embeddings
|
|
43
|
+
? `${endpoint}/v1/memory?embeddings=${encodeURIComponent(embeddings)}`
|
|
44
|
+
: `${endpoint}/v1/memory`;
|
|
45
|
+
return fetch(deleteUrl, {
|
|
46
|
+
method: "DELETE",
|
|
47
|
+
headers: { Authorization: `Bearer ${key}` },
|
|
48
|
+
});
|
|
49
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const JSON_HEADERS = { "Content-Type": "application/json" };
|
|
2
|
+
|
|
3
|
+
type PreparePayload = {
|
|
4
|
+
messages: Array<{ role: string; content: string }>;
|
|
5
|
+
density: string;
|
|
6
|
+
embeddings?: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export async function prepareMemory(endpoint: string, key: string, payload: PreparePayload): Promise<Response> {
|
|
10
|
+
return fetch(`${endpoint}/v1/memory/prepare`, {
|
|
11
|
+
method: "POST",
|
|
12
|
+
headers: {
|
|
13
|
+
...JSON_HEADERS,
|
|
14
|
+
Authorization: `Bearer ${key}`,
|
|
15
|
+
},
|
|
16
|
+
body: JSON.stringify(payload),
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function ingestMemory(endpoint: string, key: string, payload: { messages: Array<{ role: string; content: string }>; model: string; embeddings?: string }): Promise<Response> {
|
|
21
|
+
return fetch(`${endpoint}/v1/memory/ingest`, {
|
|
22
|
+
method: "POST",
|
|
23
|
+
headers: {
|
|
24
|
+
...JSON_HEADERS,
|
|
25
|
+
Authorization: `Bearer ${key}`,
|
|
26
|
+
},
|
|
27
|
+
body: JSON.stringify(payload),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function searchMemory(endpoint: string, key: string, payload: { query: string; limit: number; embeddings?: string }): Promise<Response> {
|
|
32
|
+
return fetch(`${endpoint}/v1/memory/search`, {
|
|
33
|
+
method: "POST",
|
|
34
|
+
headers: {
|
|
35
|
+
Authorization: `Bearer ${key}`,
|
|
36
|
+
"Content-Type": "application/json",
|
|
37
|
+
...(payload.embeddings && { "X-Embedding-Model": payload.embeddings }),
|
|
38
|
+
},
|
|
39
|
+
body: JSON.stringify({ query: payload.query, limit: payload.limit }),
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function getMemoryStats(endpoint: string, key: string, embeddings?: string): Promise<Response> {
|
|
44
|
+
const statsUrl = embeddings
|
|
45
|
+
? `${endpoint}/v1/memory/stats?embeddings=${encodeURIComponent(embeddings)}`
|
|
46
|
+
: `${endpoint}/v1/memory/stats`;
|
|
47
|
+
return fetch(statsUrl, {
|
|
48
|
+
headers: { Authorization: `Bearer ${key}` },
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function deleteMemories(endpoint: string, key: string, embeddings?: string): Promise<Response> {
|
|
53
|
+
const deleteUrl = embeddings
|
|
54
|
+
? `${endpoint}/v1/memory?embeddings=${encodeURIComponent(embeddings)}`
|
|
55
|
+
: `${endpoint}/v1/memory`;
|
|
56
|
+
return fetch(deleteUrl, {
|
|
57
|
+
method: "DELETE",
|
|
58
|
+
headers: { Authorization: `Bearer ${key}` },
|
|
59
|
+
});
|
|
60
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mr-memory",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.6",
|
|
4
4
|
"description": "mr-memory is the MemoryRouter plugin for OpenClaw — persistent memory across every conversation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
"upload.js",
|
|
10
10
|
"index.ts",
|
|
11
11
|
"upload.ts",
|
|
12
|
+
"memoryrouter-api.js",
|
|
13
|
+
"memoryrouter-api.ts",
|
|
14
|
+
"upload-api.js",
|
|
15
|
+
"upload-api.ts",
|
|
12
16
|
"openclaw.plugin.json"
|
|
13
17
|
],
|
|
14
18
|
"keywords": [
|
package/upload-api.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const MAX_HTTP_RETRIES = 3;
|
|
2
|
+
function sleep(ms) {
|
|
3
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
4
|
+
}
|
|
5
|
+
export async function checkMemoryRouterHealth(endpoint) {
|
|
6
|
+
try {
|
|
7
|
+
const res = await fetch(`${endpoint}/health`, { signal: AbortSignal.timeout(5000) });
|
|
8
|
+
return res.ok;
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export async function uploadMemoryBatch(params) {
|
|
15
|
+
const uploadUrl = `${params.endpoint}/v1/memory/upload`;
|
|
16
|
+
let lastError = null;
|
|
17
|
+
for (let attempt = 1; attempt <= MAX_HTTP_RETRIES; attempt++) {
|
|
18
|
+
try {
|
|
19
|
+
const res = await fetch(uploadUrl, {
|
|
20
|
+
method: "POST",
|
|
21
|
+
headers: {
|
|
22
|
+
Authorization: `Bearer ${params.memoryKey}`,
|
|
23
|
+
"Content-Type": "text/plain",
|
|
24
|
+
...(params.embeddings && { "X-Embedding-Model": params.embeddings }),
|
|
25
|
+
},
|
|
26
|
+
body: params.jsonlBody,
|
|
27
|
+
signal: AbortSignal.timeout(30000),
|
|
28
|
+
});
|
|
29
|
+
if (res.ok || res.status < 500)
|
|
30
|
+
return res;
|
|
31
|
+
lastError = new Error(`HTTP ${res.status}`);
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
35
|
+
}
|
|
36
|
+
if (attempt < MAX_HTTP_RETRIES)
|
|
37
|
+
await sleep(1000 * attempt);
|
|
38
|
+
}
|
|
39
|
+
throw lastError ?? new Error(`${params.label}: failed after ${MAX_HTTP_RETRIES} attempts`);
|
|
40
|
+
}
|
package/upload-api.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const MAX_HTTP_RETRIES = 3;
|
|
2
|
+
|
|
3
|
+
function sleep(ms: number): Promise<void> {
|
|
4
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export async function checkMemoryRouterHealth(endpoint: string): Promise<boolean> {
|
|
8
|
+
try {
|
|
9
|
+
const res = await fetch(`${endpoint}/health`, { signal: AbortSignal.timeout(5000) });
|
|
10
|
+
return res.ok;
|
|
11
|
+
} catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function uploadMemoryBatch(params: {
|
|
17
|
+
endpoint: string;
|
|
18
|
+
memoryKey: string;
|
|
19
|
+
jsonlBody: string;
|
|
20
|
+
embeddings?: string;
|
|
21
|
+
label: string;
|
|
22
|
+
}): Promise<Response> {
|
|
23
|
+
const uploadUrl = `${params.endpoint}/v1/memory/upload`;
|
|
24
|
+
let lastError: Error | null = null;
|
|
25
|
+
for (let attempt = 1; attempt <= MAX_HTTP_RETRIES; attempt++) {
|
|
26
|
+
try {
|
|
27
|
+
const res = await fetch(uploadUrl, {
|
|
28
|
+
method: "POST",
|
|
29
|
+
headers: {
|
|
30
|
+
Authorization: `Bearer ${params.memoryKey}`,
|
|
31
|
+
"Content-Type": "text/plain",
|
|
32
|
+
...(params.embeddings && { "X-Embedding-Model": params.embeddings }),
|
|
33
|
+
},
|
|
34
|
+
body: params.jsonlBody,
|
|
35
|
+
signal: AbortSignal.timeout(30000),
|
|
36
|
+
});
|
|
37
|
+
if (res.ok || res.status < 500) return res;
|
|
38
|
+
lastError = new Error(`HTTP ${res.status}`);
|
|
39
|
+
} catch (err) {
|
|
40
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
41
|
+
}
|
|
42
|
+
if (attempt < MAX_HTTP_RETRIES) await sleep(1000 * attempt);
|
|
43
|
+
}
|
|
44
|
+
throw lastError ?? new Error(`${params.label}: failed after ${MAX_HTTP_RETRIES} attempts`);
|
|
45
|
+
}
|
package/upload.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import fs from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
6
|
+
import { checkMemoryRouterHealth, uploadMemoryBatch } from "./upload-api.js";
|
|
6
7
|
// ── Sanitization utilities (shared with index.ts ingest logic)
|
|
7
8
|
// These MUST stay in sync with the patterns in index.ts
|
|
8
9
|
const MEMORY_TAG_RE = /<mr-memory>[\s\S]*?<\/mr-memory>\s*/g;
|
|
@@ -132,7 +133,6 @@ const MAX_BATCH_BYTES = 2_000_000;
|
|
|
132
133
|
const MAX_BATCH_COUNT_DEFAULT = 100;
|
|
133
134
|
const MAX_BATCH_COUNT_QWEN = 25;
|
|
134
135
|
const BATCH_SLEEP_MS = 150;
|
|
135
|
-
const MAX_HTTP_RETRIES = 3;
|
|
136
136
|
function sleep(ms) {
|
|
137
137
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
138
138
|
}
|
|
@@ -302,33 +302,10 @@ async function discoverBrainFiles(stateDir) {
|
|
|
302
302
|
}
|
|
303
303
|
return files;
|
|
304
304
|
}
|
|
305
|
-
async function fetchWithRetry(url, init, label) {
|
|
306
|
-
let lastError = null;
|
|
307
|
-
for (let attempt = 1; attempt <= MAX_HTTP_RETRIES; attempt++) {
|
|
308
|
-
try {
|
|
309
|
-
const res = await fetch(url, { ...init, signal: AbortSignal.timeout(30000) });
|
|
310
|
-
if (res.ok || res.status < 500)
|
|
311
|
-
return res;
|
|
312
|
-
lastError = new Error(`HTTP ${res.status}`);
|
|
313
|
-
}
|
|
314
|
-
catch (err) {
|
|
315
|
-
lastError = err instanceof Error ? err : new Error(String(err));
|
|
316
|
-
}
|
|
317
|
-
if (attempt < MAX_HTTP_RETRIES)
|
|
318
|
-
await sleep(1000 * attempt);
|
|
319
|
-
}
|
|
320
|
-
throw lastError ?? new Error(`${label}: failed after ${MAX_HTTP_RETRIES} attempts`);
|
|
321
|
-
}
|
|
322
305
|
export async function runUpload(params) {
|
|
323
306
|
const { memoryKey, endpoint, targetPath, stateDir, embeddings } = params;
|
|
324
|
-
const uploadUrl = `${endpoint}/v1/memory/upload`;
|
|
325
307
|
// Validate API reachability
|
|
326
|
-
|
|
327
|
-
const res = await fetch(`${endpoint}/health`, { signal: AbortSignal.timeout(5000) });
|
|
328
|
-
if (!res.ok)
|
|
329
|
-
throw new Error(`HTTP ${res.status}`);
|
|
330
|
-
}
|
|
331
|
-
catch {
|
|
308
|
+
if (!(await checkMemoryRouterHealth(endpoint))) {
|
|
332
309
|
console.error("Error: Could not reach MemoryRouter API.");
|
|
333
310
|
return;
|
|
334
311
|
}
|
|
@@ -427,15 +404,13 @@ export async function runUpload(params) {
|
|
|
427
404
|
process.stdout.write(` Batch ${i + 1}/${batches.length} (${batch.length} items)... `);
|
|
428
405
|
}
|
|
429
406
|
try {
|
|
430
|
-
const response = await
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
body: jsonlBody,
|
|
438
|
-
}, `Batch ${i + 1}`);
|
|
407
|
+
const response = await uploadMemoryBatch({
|
|
408
|
+
endpoint,
|
|
409
|
+
memoryKey,
|
|
410
|
+
jsonlBody,
|
|
411
|
+
embeddings,
|
|
412
|
+
label: `Batch ${i + 1}`,
|
|
413
|
+
});
|
|
439
414
|
const result = (await response.json());
|
|
440
415
|
const batchStored = result.stats?.stored ?? result.stats?.inputItems ?? batch.length;
|
|
441
416
|
const batchFailed = result.stats?.failed ?? 0;
|
package/upload.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import fs from "node:fs/promises";
|
|
6
6
|
import path from "node:path";
|
|
7
|
+
import { checkMemoryRouterHealth, uploadMemoryBatch } from "./upload-api.js";
|
|
7
8
|
|
|
8
9
|
// ── Sanitization utilities (shared with index.ts ingest logic)
|
|
9
10
|
// These MUST stay in sync with the patterns in index.ts
|
|
@@ -146,7 +147,6 @@ const MAX_BATCH_BYTES = 2_000_000;
|
|
|
146
147
|
const MAX_BATCH_COUNT_DEFAULT = 100;
|
|
147
148
|
const MAX_BATCH_COUNT_QWEN = 25;
|
|
148
149
|
const BATCH_SLEEP_MS = 150;
|
|
149
|
-
const MAX_HTTP_RETRIES = 3;
|
|
150
150
|
|
|
151
151
|
function sleep(ms: number): Promise<void> {
|
|
152
152
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -327,25 +327,6 @@ async function discoverBrainFiles(stateDir: string): Promise<string[]> {
|
|
|
327
327
|
return files;
|
|
328
328
|
}
|
|
329
329
|
|
|
330
|
-
async function fetchWithRetry(
|
|
331
|
-
url: string,
|
|
332
|
-
init: RequestInit,
|
|
333
|
-
label: string,
|
|
334
|
-
): Promise<Response> {
|
|
335
|
-
let lastError: Error | null = null;
|
|
336
|
-
for (let attempt = 1; attempt <= MAX_HTTP_RETRIES; attempt++) {
|
|
337
|
-
try {
|
|
338
|
-
const res = await fetch(url, { ...init, signal: AbortSignal.timeout(30000) });
|
|
339
|
-
if (res.ok || res.status < 500) return res;
|
|
340
|
-
lastError = new Error(`HTTP ${res.status}`);
|
|
341
|
-
} catch (err) {
|
|
342
|
-
lastError = err instanceof Error ? err : new Error(String(err));
|
|
343
|
-
}
|
|
344
|
-
if (attempt < MAX_HTTP_RETRIES) await sleep(1000 * attempt);
|
|
345
|
-
}
|
|
346
|
-
throw lastError ?? new Error(`${label}: failed after ${MAX_HTTP_RETRIES} attempts`);
|
|
347
|
-
}
|
|
348
|
-
|
|
349
330
|
export async function runUpload(params: {
|
|
350
331
|
memoryKey: string;
|
|
351
332
|
endpoint: string;
|
|
@@ -357,13 +338,8 @@ export async function runUpload(params: {
|
|
|
357
338
|
embeddings?: string;
|
|
358
339
|
}): Promise<void> {
|
|
359
340
|
const { memoryKey, endpoint, targetPath, stateDir, embeddings } = params;
|
|
360
|
-
const uploadUrl = `${endpoint}/v1/memory/upload`;
|
|
361
|
-
|
|
362
341
|
// Validate API reachability
|
|
363
|
-
|
|
364
|
-
const res = await fetch(`${endpoint}/health`, { signal: AbortSignal.timeout(5000) });
|
|
365
|
-
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
366
|
-
} catch {
|
|
342
|
+
if (!(await checkMemoryRouterHealth(endpoint))) {
|
|
367
343
|
console.error("Error: Could not reach MemoryRouter API.");
|
|
368
344
|
return;
|
|
369
345
|
}
|
|
@@ -467,19 +443,13 @@ export async function runUpload(params: {
|
|
|
467
443
|
}
|
|
468
444
|
|
|
469
445
|
try {
|
|
470
|
-
const response = await
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
...(embeddings && { "X-Embedding-Model": embeddings }),
|
|
478
|
-
},
|
|
479
|
-
body: jsonlBody,
|
|
480
|
-
},
|
|
481
|
-
`Batch ${i + 1}`,
|
|
482
|
-
);
|
|
446
|
+
const response = await uploadMemoryBatch({
|
|
447
|
+
endpoint,
|
|
448
|
+
memoryKey,
|
|
449
|
+
jsonlBody,
|
|
450
|
+
embeddings,
|
|
451
|
+
label: `Batch ${i + 1}`,
|
|
452
|
+
});
|
|
483
453
|
|
|
484
454
|
const result = (await response.json()) as {
|
|
485
455
|
stats?: { stored?: number; failed?: number; inputItems?: number; path?: string; diagnostic?: string };
|