@persistio/openclaw-plugin 0.1.6 → 0.1.8
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/README.md +35 -10
- package/dist/client.d.ts +12 -1
- package/dist/client.js +130 -60
- package/dist/index.js +303 -64
- package/openclaw.plugin.json +48 -17
- package/package.json +2 -2
- package/src/client.ts +135 -53
- package/src/index.ts +366 -72
package/src/client.ts
CHANGED
|
@@ -7,6 +7,8 @@ export interface PersistioConfig {
|
|
|
7
7
|
recallTopK: number;
|
|
8
8
|
recallMinSimilarity?: number;
|
|
9
9
|
recallTimeout: number;
|
|
10
|
+
recallIncludePending: boolean;
|
|
11
|
+
includeRelatedMemories: boolean;
|
|
10
12
|
ingest: PersistioIngestPolicy;
|
|
11
13
|
send: PersistioSendConfig;
|
|
12
14
|
}
|
|
@@ -52,13 +54,27 @@ export interface RecallBundleResponse {
|
|
|
52
54
|
related_bundle?: RecallBundle;
|
|
53
55
|
}
|
|
54
56
|
|
|
57
|
+
export interface RecallBundleOptions {
|
|
58
|
+
includeRelated?: boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export class PersistioTimeoutError extends Error {
|
|
62
|
+
constructor(operation: string, timeoutMs: number) {
|
|
63
|
+
super(`Persistio ${operation} timed out after ${timeoutMs}ms`);
|
|
64
|
+
this.name = 'TimeoutError';
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
55
68
|
export class PersistioClient {
|
|
56
69
|
private readonly baseURL: string;
|
|
57
70
|
private readonly apiKey: string;
|
|
58
71
|
private readonly recallTopK: number;
|
|
59
72
|
private readonly recallMinSimilarity?: number;
|
|
60
73
|
private readonly recallTimeout: number;
|
|
74
|
+
private readonly recallIncludePending: boolean;
|
|
75
|
+
private readonly includeRelatedMemories: boolean;
|
|
61
76
|
private readonly ingestTimeout: number;
|
|
77
|
+
private readonly writeTimeout: number;
|
|
62
78
|
|
|
63
79
|
constructor(config: PersistioConfig) {
|
|
64
80
|
this.baseURL = config.baseURL.replace(/\/$/, '');
|
|
@@ -66,7 +82,10 @@ export class PersistioClient {
|
|
|
66
82
|
this.recallTopK = config.recallTopK;
|
|
67
83
|
this.recallMinSimilarity = config.recallMinSimilarity;
|
|
68
84
|
this.recallTimeout = config.recallTimeout;
|
|
85
|
+
this.recallIncludePending = config.recallIncludePending;
|
|
86
|
+
this.includeRelatedMemories = config.includeRelatedMemories;
|
|
69
87
|
this.ingestTimeout = config.ingest.timeoutMs;
|
|
88
|
+
this.writeTimeout = config.ingest.timeoutMs;
|
|
70
89
|
}
|
|
71
90
|
|
|
72
91
|
private headers(): Record<string, string> {
|
|
@@ -77,87 +96,150 @@ export class PersistioClient {
|
|
|
77
96
|
}
|
|
78
97
|
|
|
79
98
|
async recall(query: string): Promise<PersistioMemory[]> {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
99
|
+
return withRequestDeadline('recall', this.recallTimeout, async (signal) => {
|
|
100
|
+
const body: Record<string, unknown> = {
|
|
101
|
+
query,
|
|
102
|
+
top_k: this.recallTopK,
|
|
103
|
+
include_pending: this.recallIncludePending
|
|
104
|
+
};
|
|
105
|
+
if (typeof this.recallMinSimilarity === 'number') {
|
|
106
|
+
body.min_similarity = this.recallMinSimilarity;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const res = await fetch(`${this.baseURL}/v1/recall`, {
|
|
110
|
+
method: 'POST',
|
|
111
|
+
headers: this.headers(),
|
|
112
|
+
body: JSON.stringify(body),
|
|
113
|
+
signal,
|
|
114
|
+
});
|
|
115
|
+
if (!res.ok) throw new Error(`Persistio recall failed: ${res.status}`);
|
|
116
|
+
const data = await res.json() as { memories: PersistioMemory[] };
|
|
117
|
+
return data.memories ?? [];
|
|
90
118
|
});
|
|
91
|
-
if (!res.ok) throw new Error(`Persistio recall failed: ${res.status}`);
|
|
92
|
-
const data = await res.json() as { memories: PersistioMemory[] };
|
|
93
|
-
return data.memories ?? [];
|
|
94
119
|
}
|
|
95
120
|
|
|
96
|
-
async recallBundle(query: string, topK?: number): Promise<RecallBundleResponse> {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
121
|
+
async recallBundle(query: string, topK?: number, options: RecallBundleOptions = {}): Promise<RecallBundleResponse> {
|
|
122
|
+
return withRequestDeadline('recallBundle', this.recallTimeout, async (signal) => {
|
|
123
|
+
const body: Record<string, unknown> = {
|
|
124
|
+
query,
|
|
125
|
+
top_k: topK ?? this.recallTopK,
|
|
126
|
+
include_pending: this.recallIncludePending,
|
|
127
|
+
include_related: options.includeRelated ?? this.includeRelatedMemories
|
|
128
|
+
};
|
|
129
|
+
if (typeof this.recallMinSimilarity === 'number') {
|
|
130
|
+
body.min_similarity = this.recallMinSimilarity;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const res = await fetch(`${this.baseURL}/v1/recall?format=bundle`, {
|
|
134
|
+
method: 'POST',
|
|
135
|
+
headers: this.headers(),
|
|
136
|
+
body: JSON.stringify(body),
|
|
137
|
+
signal,
|
|
138
|
+
});
|
|
139
|
+
if (!res.ok) throw new Error(`Persistio recallBundle failed: ${res.status}`);
|
|
140
|
+
const data = await res.json() as RecallBundleResponse;
|
|
141
|
+
return data;
|
|
107
142
|
});
|
|
108
|
-
if (!res.ok) throw new Error(`Persistio recallBundle failed: ${res.status}`);
|
|
109
|
-
const data = await res.json() as RecallBundleResponse;
|
|
110
|
-
return data;
|
|
111
143
|
}
|
|
112
144
|
|
|
113
145
|
async ingest(sessionId: string, chunks: Array<{ role: string; content: string; timestamp: string }>): Promise<void> {
|
|
114
146
|
if (chunks.length === 0) return;
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
147
|
+
await withRequestDeadline('ingest', this.ingestTimeout, async (signal) => {
|
|
148
|
+
const res = await fetch(`${this.baseURL}/v1/ingest`, {
|
|
149
|
+
method: 'POST',
|
|
150
|
+
headers: this.headers(),
|
|
151
|
+
body: JSON.stringify({ session_id: sessionId, chunks }),
|
|
152
|
+
signal,
|
|
153
|
+
});
|
|
154
|
+
if (!res.ok) throw new Error(await formatHttpError('ingest', res));
|
|
120
155
|
});
|
|
121
|
-
if (!res.ok) throw new Error(await formatHttpError('ingest', res));
|
|
122
156
|
}
|
|
123
157
|
|
|
124
158
|
async addMemory(data: string, subject: string): Promise<void> {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
159
|
+
await withRequestDeadline('addMemory', this.writeTimeout, async (signal) => {
|
|
160
|
+
const res = await fetch(`${this.baseURL}/v1/memories`, {
|
|
161
|
+
method: 'POST',
|
|
162
|
+
headers: this.headers(),
|
|
163
|
+
body: JSON.stringify({ data, subject }),
|
|
164
|
+
signal,
|
|
165
|
+
});
|
|
166
|
+
if (!res.ok) throw new Error(`Persistio addMemory failed: ${res.status}`);
|
|
129
167
|
});
|
|
130
|
-
if (!res.ok) throw new Error(`Persistio addMemory failed: ${res.status}`);
|
|
131
168
|
}
|
|
132
169
|
|
|
133
170
|
async deleteMemory(id: string): Promise<void> {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
171
|
+
await withRequestDeadline('deleteMemory', this.writeTimeout, async (signal) => {
|
|
172
|
+
const res = await fetch(`${this.baseURL}/v1/memories/${id}`, {
|
|
173
|
+
method: 'DELETE',
|
|
174
|
+
headers: this.headers(),
|
|
175
|
+
signal,
|
|
176
|
+
});
|
|
177
|
+
if (!res.ok) throw new Error(`Persistio deleteMemory failed: ${res.status}`);
|
|
137
178
|
});
|
|
138
|
-
if (!res.ok) throw new Error(`Persistio deleteMemory failed: ${res.status}`);
|
|
139
179
|
}
|
|
140
180
|
|
|
141
181
|
async getMemory(id: string, options: GetMemoryOptions = {}): Promise<PersistioMemory | null> {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
182
|
+
return withRequestDeadline('getMemory', this.recallTimeout, async (signal) => {
|
|
183
|
+
const query = options.includePending ? '?include_pending=true' : '';
|
|
184
|
+
const res = await fetch(`${this.baseURL}/v1/memories/${id}${query}`, {
|
|
185
|
+
headers: this.headers(),
|
|
186
|
+
signal,
|
|
187
|
+
});
|
|
188
|
+
if (res.status === 404) return null;
|
|
189
|
+
if (!res.ok) throw new Error(`Persistio getMemory failed: ${res.status}`);
|
|
190
|
+
return await res.json() as PersistioMemory;
|
|
145
191
|
});
|
|
146
|
-
if (res.status === 404) return null;
|
|
147
|
-
if (!res.ok) throw new Error(`Persistio getMemory failed: ${res.status}`);
|
|
148
|
-
return await res.json() as PersistioMemory;
|
|
149
192
|
}
|
|
150
193
|
|
|
151
194
|
async listMemories(): Promise<PersistioMemory[]> {
|
|
152
|
-
|
|
153
|
-
|
|
195
|
+
return withRequestDeadline('listMemories', this.recallTimeout, async (signal) => {
|
|
196
|
+
const res = await fetch(`${this.baseURL}/v1/memories`, {
|
|
197
|
+
headers: this.headers(),
|
|
198
|
+
signal,
|
|
199
|
+
});
|
|
200
|
+
if (!res.ok) throw new Error(`Persistio listMemories failed: ${res.status}`);
|
|
201
|
+
const data = await res.json() as { items: PersistioMemory[] };
|
|
202
|
+
return data.items ?? [];
|
|
154
203
|
});
|
|
155
|
-
if (!res.ok) throw new Error(`Persistio listMemories failed: ${res.status}`);
|
|
156
|
-
const data = await res.json() as { items: PersistioMemory[] };
|
|
157
|
-
return data.items ?? [];
|
|
158
204
|
}
|
|
159
205
|
}
|
|
160
206
|
|
|
207
|
+
async function withRequestDeadline<T>(
|
|
208
|
+
operation: string,
|
|
209
|
+
timeoutMs: number,
|
|
210
|
+
run: (signal: AbortSignal) => Promise<T>,
|
|
211
|
+
): Promise<T> {
|
|
212
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
213
|
+
return run(new AbortController().signal);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const controller = new AbortController();
|
|
217
|
+
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
218
|
+
|
|
219
|
+
const deadline = new Promise<never>((_resolve, reject) => {
|
|
220
|
+
timeout = setTimeout(() => {
|
|
221
|
+
controller.abort();
|
|
222
|
+
reject(new PersistioTimeoutError(operation, timeoutMs));
|
|
223
|
+
}, timeoutMs);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
try {
|
|
227
|
+
return await Promise.race([run(controller.signal), deadline]);
|
|
228
|
+
} catch (err) {
|
|
229
|
+
if (controller.signal.aborted && isAbortLikeError(err)) {
|
|
230
|
+
throw new PersistioTimeoutError(operation, timeoutMs);
|
|
231
|
+
}
|
|
232
|
+
throw err;
|
|
233
|
+
} finally {
|
|
234
|
+
if (timeout) clearTimeout(timeout);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function isAbortLikeError(err: unknown): boolean {
|
|
239
|
+
if (!(err instanceof Error)) return false;
|
|
240
|
+
return err.name === 'AbortError' || err.name === 'TimeoutError';
|
|
241
|
+
}
|
|
242
|
+
|
|
161
243
|
async function formatHttpError(operation: string, res: Response): Promise<string> {
|
|
162
244
|
let detail = '';
|
|
163
245
|
try {
|