@echomem/echo-memory-cloud-openclaw-plugin 0.1.1 → 0.1.2

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.
@@ -2,7 +2,7 @@
2
2
  "id": "echo-memory-cloud-openclaw-plugin",
3
3
  "name": "Echo Memory Cloud OpenClaw Plugin",
4
4
  "description": "Sync OpenClaw local markdown memory files to Echo cloud",
5
- "version": "0.1.1",
5
+ "version": "0.1.2",
6
6
  "kind": "lifecycle",
7
7
  "main": "./index.js",
8
8
  "configSchema": {
package/lib/api-client.js CHANGED
@@ -1,35 +1,111 @@
1
- function buildHeaders(apiKey) {
2
- return {
3
- "Content-Type": "application/json",
4
- Authorization: `Bearer ${apiKey}`,
5
- };
6
- }
7
-
8
- async function requestJson(url, options, timeoutMs) {
9
- const controller = new AbortController();
10
- const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
1
+ function buildHeaders(apiKey) {
2
+ return {
3
+ "Content-Type": "application/json",
4
+ Authorization: `Bearer ${apiKey}`,
5
+ };
6
+ }
7
+
8
+ async function parseErrorResponse(response) {
9
+ const payload = await response.json().catch(() => ({}));
10
+ const detail = typeof payload?.details === "string" ? payload.details : payload?.error;
11
+ throw new Error(detail || `HTTP ${response.status}`);
12
+ }
13
+
14
+ async function requestJson(url, options, timeoutMs) {
15
+ const controller = new AbortController();
16
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
11
17
 
12
18
  try {
13
- const response = await fetch(url, {
14
- ...options,
15
- signal: controller.signal,
16
- });
17
-
18
- const payload = await response.json().catch(() => ({}));
19
- if (!response.ok) {
20
- const detail = typeof payload?.details === "string" ? payload.details : payload?.error;
21
- throw new Error(detail || `HTTP ${response.status}`);
22
- }
23
-
24
- return payload;
25
- } finally {
26
- clearTimeout(timeoutId);
27
- }
28
- }
29
-
30
- export function createApiClient(cfg) {
31
- const whoamiUrl = `${cfg.baseUrl}/api/openclaw/v1/whoami`;
32
- const importUrl = `${cfg.baseUrl}/api/openclaw/v1/import-markdown`;
19
+ const response = await fetch(url, {
20
+ ...options,
21
+ signal: controller.signal,
22
+ });
23
+
24
+ if (!response.ok) {
25
+ await parseErrorResponse(response);
26
+ }
27
+
28
+ return await response.json().catch(() => ({}));
29
+ } finally {
30
+ clearTimeout(timeoutId);
31
+ }
32
+ }
33
+
34
+ async function requestStreamJson(url, options, timeoutMs, { onStageEvent } = {}) {
35
+ const controller = new AbortController();
36
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
37
+
38
+ try {
39
+ const response = await fetch(url, {
40
+ ...options,
41
+ signal: controller.signal,
42
+ });
43
+
44
+ if (!response.ok) {
45
+ await parseErrorResponse(response);
46
+ }
47
+
48
+ const contentType = response.headers.get("content-type") || "";
49
+ if (!contentType.includes("application/x-ndjson") || !response.body) {
50
+ return await response.json().catch(() => ({}));
51
+ }
52
+
53
+ const reader = response.body.getReader();
54
+ const decoder = new TextDecoder();
55
+ let buffer = "";
56
+ let finalPayload = null;
57
+
58
+ while (true) {
59
+ const { done, value } = await reader.read();
60
+ if (done) break;
61
+ buffer += decoder.decode(value, { stream: true });
62
+
63
+ let newlineIndex = buffer.indexOf("\n");
64
+ while (newlineIndex >= 0) {
65
+ const line = buffer.slice(0, newlineIndex).trim();
66
+ buffer = buffer.slice(newlineIndex + 1);
67
+ if (line) {
68
+ const message = JSON.parse(line);
69
+ if (message?.type === "stage") {
70
+ onStageEvent?.(message);
71
+ } else if (message?.type === "result") {
72
+ finalPayload = message.payload ?? null;
73
+ }
74
+ }
75
+ newlineIndex = buffer.indexOf("\n");
76
+ }
77
+ }
78
+
79
+ const tail = buffer.trim();
80
+ if (tail) {
81
+ const message = JSON.parse(tail);
82
+ if (message?.type === "stage") {
83
+ onStageEvent?.(message);
84
+ } else if (message?.type === "result") {
85
+ finalPayload = message.payload ?? null;
86
+ }
87
+ }
88
+
89
+ if (!finalPayload) {
90
+ throw new Error("Stream completed without a final result payload");
91
+ }
92
+
93
+ if (
94
+ finalPayload?.error
95
+ && (!Array.isArray(finalPayload?.results) || finalPayload.results.length === 0)
96
+ ) {
97
+ throw new Error(finalPayload.error);
98
+ }
99
+
100
+ return finalPayload;
101
+ } finally {
102
+ clearTimeout(timeoutId);
103
+ }
104
+ }
105
+
106
+ export function createApiClient(cfg) {
107
+ const whoamiUrl = `${cfg.baseUrl}/api/openclaw/v1/whoami`;
108
+ const importUrl = `${cfg.baseUrl}/api/openclaw/v1/import-markdown`;
33
109
  const statusUrl = `${cfg.baseUrl}/api/openclaw/v1/import-status`;
34
110
  const sourcesUrl = `${cfg.baseUrl}/api/openclaw/v1/sources`;
35
111
  const searchUrl = `${cfg.baseUrl}/api/extension/memories/search`;
@@ -48,20 +124,25 @@ export function createApiClient(cfg) {
48
124
  );
49
125
  }
50
126
 
51
- async function importMarkdown(files) {
52
- if (!cfg.apiKey) {
53
- throw new Error("Missing Echo API key");
54
- }
55
- return requestJson(
56
- importUrl,
57
- {
58
- method: "POST",
59
- headers: buildHeaders(cfg.apiKey),
60
- body: JSON.stringify({ files }),
61
- },
62
- cfg.requestTimeoutMs,
63
- );
64
- }
127
+ async function importMarkdown(files, opts = {}) {
128
+ if (!cfg.apiKey) {
129
+ throw new Error("Missing Echo API key");
130
+ }
131
+ return requestStreamJson(
132
+ `${importUrl}?stream=1`,
133
+ {
134
+ method: "POST",
135
+ headers: {
136
+ ...buildHeaders(cfg.apiKey),
137
+ Accept: "application/x-ndjson",
138
+ "X-OpenClaw-Stream": "1",
139
+ },
140
+ body: JSON.stringify({ files }),
141
+ },
142
+ cfg.requestTimeoutMs,
143
+ opts,
144
+ );
145
+ }
65
146
 
66
147
  async function getImportStatus() {
67
148
  if (!cfg.apiKey) {