mr-memory 3.7.4 → 3.7.5
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 +19 -68
- package/index.ts +17 -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,7 +289,8 @@ 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");
|
|
295
294
|
}
|
|
296
295
|
async function updateOpenClawPluginEntry(api, patch) {
|
|
297
296
|
const config = await readOpenClawConfigFile();
|
|
@@ -545,17 +544,10 @@ If setting up MemoryRouter for another agent:
|
|
|
545
544
|
contextPayload.push({ role: "user", content: taskPrompt });
|
|
546
545
|
}
|
|
547
546
|
}
|
|
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
|
-
}),
|
|
547
|
+
const res = await prepareMemory(endpoint, activeKey, {
|
|
548
|
+
messages: contextPayload,
|
|
549
|
+
density,
|
|
550
|
+
...(embeddings && { embeddings }),
|
|
559
551
|
});
|
|
560
552
|
if (!res.ok) {
|
|
561
553
|
const errBody = await res.json().catch(() => null);
|
|
@@ -650,17 +642,10 @@ If setting up MemoryRouter for another agent:
|
|
|
650
642
|
}
|
|
651
643
|
}
|
|
652
644
|
// 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
|
-
}),
|
|
645
|
+
const res = await prepareMemory(endpoint, activeKey, {
|
|
646
|
+
messages: contextPayload,
|
|
647
|
+
density,
|
|
648
|
+
...(embeddings && { embeddings }),
|
|
664
649
|
});
|
|
665
650
|
if (!res.ok) {
|
|
666
651
|
const errBody = await res.json().catch(() => null);
|
|
@@ -762,17 +747,10 @@ If setting up MemoryRouter for another agent:
|
|
|
762
747
|
return;
|
|
763
748
|
}
|
|
764
749
|
// 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
|
-
}),
|
|
750
|
+
ingestMemory(endpoint, activeKey, {
|
|
751
|
+
messages: toStore,
|
|
752
|
+
model: "unknown",
|
|
753
|
+
...(embeddings && { embeddings }),
|
|
776
754
|
}).then(async (res) => {
|
|
777
755
|
if (!res.ok) {
|
|
778
756
|
const details = await res.text().catch(() => "");
|
|
@@ -822,15 +800,7 @@ If setting up MemoryRouter for another agent:
|
|
|
822
800
|
return jsonToolResult({ results: [], error: "query required" });
|
|
823
801
|
const limit = typeof params.maxResults === "number" ? params.maxResults : 50;
|
|
824
802
|
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
|
-
});
|
|
803
|
+
const res = await searchMemory(endpoint, toolKey, { query, limit, embeddings });
|
|
834
804
|
if (!res.ok) {
|
|
835
805
|
const errBody = await res.text().catch(() => "");
|
|
836
806
|
return jsonToolResult({ results: [], error: `Search failed: HTTP ${res.status}`, details: errBody.slice(0, 200) });
|
|
@@ -1023,12 +993,7 @@ If setting up MemoryRouter for another agent:
|
|
|
1023
993
|
return;
|
|
1024
994
|
}
|
|
1025
995
|
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
|
-
});
|
|
996
|
+
const res = await getMemoryStats(endpoint, effectiveKey, embeddings);
|
|
1032
997
|
const data = (await res.json());
|
|
1033
998
|
if (opts.json) {
|
|
1034
999
|
console.log(JSON.stringify({ enabled: true, key: effectiveKey, density, stats: data }, null, 2));
|
|
@@ -1086,15 +1051,7 @@ If setting up MemoryRouter for another agent:
|
|
|
1086
1051
|
}
|
|
1087
1052
|
const limit = parseInt(opts.limit, 10) || 50;
|
|
1088
1053
|
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
|
-
});
|
|
1054
|
+
const res = await searchMemory(endpoint, effectiveKey, { query, limit, embeddings });
|
|
1098
1055
|
if (!res.ok) {
|
|
1099
1056
|
const err = await res.json();
|
|
1100
1057
|
console.error(`Search failed: ${err.error || `HTTP ${res.status}`}`);
|
|
@@ -1142,13 +1099,7 @@ If setting up MemoryRouter for another agent:
|
|
|
1142
1099
|
return;
|
|
1143
1100
|
}
|
|
1144
1101
|
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
|
-
});
|
|
1102
|
+
const res = await deleteMemories(endpoint, effectiveKey, embeddings);
|
|
1152
1103
|
const data = (await res.json());
|
|
1153
1104
|
const modelLabel = embeddings ? ` (${embeddings})` : "";
|
|
1154
1105
|
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";
|
|
@@ -570,17 +571,10 @@ If setting up MemoryRouter for another agent:
|
|
|
570
571
|
}
|
|
571
572
|
|
|
572
573
|
|
|
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
|
-
}),
|
|
574
|
+
const res = await prepareMemory(endpoint, activeKey, {
|
|
575
|
+
messages: contextPayload,
|
|
576
|
+
density,
|
|
577
|
+
...(embeddings && { embeddings }),
|
|
584
578
|
});
|
|
585
579
|
|
|
586
580
|
if (!res.ok) {
|
|
@@ -686,17 +680,10 @@ If setting up MemoryRouter for another agent:
|
|
|
686
680
|
|
|
687
681
|
// 4. Call /v1/memory/prepare (no session_id — always search core vault)
|
|
688
682
|
|
|
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
|
-
}),
|
|
683
|
+
const res = await prepareMemory(endpoint, activeKey, {
|
|
684
|
+
messages: contextPayload,
|
|
685
|
+
density,
|
|
686
|
+
...(embeddings && { embeddings }),
|
|
700
687
|
});
|
|
701
688
|
|
|
702
689
|
if (!res.ok) {
|
|
@@ -811,17 +798,10 @@ If setting up MemoryRouter for another agent:
|
|
|
811
798
|
}
|
|
812
799
|
|
|
813
800
|
// 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
|
-
}),
|
|
801
|
+
ingestMemory(endpoint, activeKey, {
|
|
802
|
+
messages: toStore,
|
|
803
|
+
model: "unknown",
|
|
804
|
+
...(embeddings && { embeddings }),
|
|
825
805
|
}).then(async (res) => {
|
|
826
806
|
if (!res.ok) {
|
|
827
807
|
const details = await res.text().catch(() => "");
|
|
@@ -869,15 +849,7 @@ If setting up MemoryRouter for another agent:
|
|
|
869
849
|
if (!query) return jsonToolResult({ results: [], error: "query required" });
|
|
870
850
|
const limit = typeof params.maxResults === "number" ? params.maxResults : 50;
|
|
871
851
|
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
|
-
});
|
|
852
|
+
const res = await searchMemory(endpoint, toolKey, { query, limit, embeddings });
|
|
881
853
|
if (!res.ok) {
|
|
882
854
|
const errBody = await res.text().catch(() => "");
|
|
883
855
|
return jsonToolResult({ results: [], error: `Search failed: HTTP ${res.status}`, details: errBody.slice(0, 200) });
|
|
@@ -1075,12 +1047,7 @@ If setting up MemoryRouter for another agent:
|
|
|
1075
1047
|
return;
|
|
1076
1048
|
}
|
|
1077
1049
|
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
|
-
});
|
|
1050
|
+
const res = await getMemoryStats(endpoint, effectiveKey, embeddings);
|
|
1084
1051
|
const data = (await res.json()) as { totalVectors?: number; totalTokens?: number };
|
|
1085
1052
|
if (opts.json) {
|
|
1086
1053
|
console.log(JSON.stringify({ enabled: true, key: effectiveKey, density, stats: data }, null, 2));
|
|
@@ -1132,15 +1099,7 @@ If setting up MemoryRouter for another agent:
|
|
|
1132
1099
|
if (!effectiveKey) { console.error("Not configured. Run: openclaw mr <key> or pass --key <key>"); return; }
|
|
1133
1100
|
const limit = parseInt(opts.limit, 10) || 50;
|
|
1134
1101
|
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
|
-
});
|
|
1102
|
+
const res = await searchMemory(endpoint, effectiveKey, { query, limit, embeddings });
|
|
1144
1103
|
if (!res.ok) {
|
|
1145
1104
|
const err = await res.json() as { error?: string };
|
|
1146
1105
|
console.error(`Search failed: ${err.error || `HTTP ${res.status}`}`);
|
|
@@ -1205,13 +1164,7 @@ If setting up MemoryRouter for another agent:
|
|
|
1205
1164
|
const effectiveKey = opts.key || memoryKey;
|
|
1206
1165
|
if (!effectiveKey) { console.error("Not configured. Run: openclaw mr <key> or pass --key <key>"); return; }
|
|
1207
1166
|
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
|
-
});
|
|
1167
|
+
const res = await deleteMemories(endpoint, effectiveKey, embeddings);
|
|
1215
1168
|
const data = (await res.json()) as { message?: string };
|
|
1216
1169
|
const modelLabel = embeddings ? ` (${embeddings})` : "";
|
|
1217
1170
|
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.5",
|
|
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 };
|