@persistio/openclaw-plugin 0.1.8 → 0.2.0
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 +84 -70
- package/dist/capture.d.ts +17 -0
- package/dist/capture.js +112 -0
- package/dist/client.d.ts +34 -57
- package/dist/client.js +43 -81
- package/dist/config.d.ts +29 -0
- package/dist/config.js +86 -0
- package/dist/index.js +293 -742
- package/dist/memory-format.d.ts +8 -0
- package/dist/memory-format.js +121 -0
- package/openclaw.plugin.json +67 -103
- package/package.json +10 -11
- package/src/capture.ts +132 -0
- package/src/client.ts +70 -128
- package/src/config.ts +125 -0
- package/src/index.ts +301 -860
- package/src/memory-format.ts +127 -0
- package/dist/ingest-policy.d.ts +0 -48
- package/dist/ingest-policy.js +0 -380
- package/src/ingest-policy.ts +0 -508
package/src/client.ts
CHANGED
|
@@ -1,61 +1,43 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
export interface PersistioConfig {
|
|
4
|
-
baseURL: string;
|
|
5
|
-
apiKey: string;
|
|
6
|
-
tokenBudget: number;
|
|
7
|
-
recallTopK: number;
|
|
8
|
-
recallMinSimilarity?: number;
|
|
9
|
-
recallTimeout: number;
|
|
10
|
-
recallIncludePending: boolean;
|
|
11
|
-
includeRelatedMemories: boolean;
|
|
12
|
-
ingest: PersistioIngestPolicy;
|
|
13
|
-
send: PersistioSendConfig;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export type PersistioSendRoleStatus = 'enabled' | 'disabled';
|
|
17
|
-
|
|
18
|
-
export interface PersistioSendConfig {
|
|
19
|
-
roles: {
|
|
20
|
-
user: PersistioSendRoleStatus;
|
|
21
|
-
agent: PersistioSendRoleStatus;
|
|
22
|
-
tool: PersistioSendRoleStatus;
|
|
23
|
-
};
|
|
24
|
-
}
|
|
1
|
+
import type { PersistioV2Config } from './config.js';
|
|
25
2
|
|
|
26
3
|
export interface PersistioMemory {
|
|
27
4
|
id: string;
|
|
28
5
|
data: string;
|
|
29
6
|
subject: string;
|
|
30
7
|
similarity?: number;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
export interface GetMemoryOptions {
|
|
36
|
-
includePending?: boolean;
|
|
8
|
+
confidence?: number;
|
|
9
|
+
categories?: string[];
|
|
10
|
+
source?: string;
|
|
11
|
+
edge_type?: string | null;
|
|
37
12
|
}
|
|
38
13
|
|
|
39
14
|
export interface RecallBundle {
|
|
40
15
|
global_user_rules?: string[];
|
|
41
|
-
user_rules
|
|
42
|
-
user_preferences
|
|
43
|
-
task_patterns
|
|
44
|
-
workflows
|
|
45
|
-
project
|
|
46
|
-
constraints
|
|
47
|
-
decisions
|
|
48
|
-
system_facts
|
|
49
|
-
domain_knowledge
|
|
16
|
+
user_rules?: string[];
|
|
17
|
+
user_preferences?: string[];
|
|
18
|
+
task_patterns?: string[];
|
|
19
|
+
workflows?: string[];
|
|
20
|
+
project?: string[];
|
|
21
|
+
constraints?: string[];
|
|
22
|
+
decisions?: string[];
|
|
23
|
+
system_facts?: string[];
|
|
24
|
+
domain_knowledge?: string[];
|
|
50
25
|
}
|
|
51
26
|
|
|
52
27
|
export interface RecallBundleResponse {
|
|
53
|
-
bundle
|
|
28
|
+
bundle?: RecallBundle;
|
|
54
29
|
related_bundle?: RecallBundle;
|
|
55
30
|
}
|
|
56
31
|
|
|
57
|
-
export interface
|
|
58
|
-
|
|
32
|
+
export interface RecallResult {
|
|
33
|
+
memories: PersistioMemory[];
|
|
34
|
+
relatedMemories: PersistioMemory[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface IngestChunk {
|
|
38
|
+
role: string;
|
|
39
|
+
content: string;
|
|
40
|
+
timestamp: string;
|
|
59
41
|
}
|
|
60
42
|
|
|
61
43
|
export class PersistioTimeoutError extends Error {
|
|
@@ -68,83 +50,49 @@ export class PersistioTimeoutError extends Error {
|
|
|
68
50
|
export class PersistioClient {
|
|
69
51
|
private readonly baseURL: string;
|
|
70
52
|
private readonly apiKey: string;
|
|
71
|
-
|
|
72
|
-
private readonly
|
|
73
|
-
private readonly recallTimeout: number;
|
|
74
|
-
private readonly recallIncludePending: boolean;
|
|
75
|
-
private readonly includeRelatedMemories: boolean;
|
|
76
|
-
private readonly ingestTimeout: number;
|
|
77
|
-
private readonly writeTimeout: number;
|
|
78
|
-
|
|
79
|
-
constructor(config: PersistioConfig) {
|
|
53
|
+
|
|
54
|
+
constructor(private readonly config: PersistioV2Config) {
|
|
80
55
|
this.baseURL = config.baseURL.replace(/\/$/, '');
|
|
81
56
|
this.apiKey = config.apiKey;
|
|
82
|
-
this.recallTopK = config.recallTopK;
|
|
83
|
-
this.recallMinSimilarity = config.recallMinSimilarity;
|
|
84
|
-
this.recallTimeout = config.recallTimeout;
|
|
85
|
-
this.recallIncludePending = config.recallIncludePending;
|
|
86
|
-
this.includeRelatedMemories = config.includeRelatedMemories;
|
|
87
|
-
this.ingestTimeout = config.ingest.timeoutMs;
|
|
88
|
-
this.writeTimeout = config.ingest.timeoutMs;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
private headers(): Record<string, string> {
|
|
92
|
-
return {
|
|
93
|
-
'Content-Type': 'application/json',
|
|
94
|
-
'Authorization': `Bearer ${this.apiKey}`,
|
|
95
|
-
};
|
|
96
57
|
}
|
|
97
58
|
|
|
98
|
-
async recall(query: string): Promise<
|
|
99
|
-
return withRequestDeadline('recall', this.
|
|
100
|
-
const body
|
|
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
|
-
|
|
59
|
+
async recall(query: string, options: { maxResults?: number } = {}): Promise<RecallResult> {
|
|
60
|
+
return withRequestDeadline('recall', this.config.recall.timeoutMs, async (signal) => {
|
|
61
|
+
const body = this.buildRecallBody(query, options.maxResults);
|
|
109
62
|
const res = await fetch(`${this.baseURL}/v1/recall`, {
|
|
110
63
|
method: 'POST',
|
|
111
64
|
headers: this.headers(),
|
|
112
65
|
body: JSON.stringify(body),
|
|
113
66
|
signal,
|
|
114
67
|
});
|
|
115
|
-
if (!res.ok) throw new Error(
|
|
116
|
-
const data = await res.json() as { memories
|
|
117
|
-
return
|
|
68
|
+
if (!res.ok) throw new Error(await formatHttpError('recall', res));
|
|
69
|
+
const data = await res.json() as { memories?: PersistioMemory[]; related_memories?: PersistioMemory[] };
|
|
70
|
+
return {
|
|
71
|
+
memories: Array.isArray(data.memories) ? data.memories : [],
|
|
72
|
+
relatedMemories: Array.isArray(data.related_memories) ? data.related_memories : [],
|
|
73
|
+
};
|
|
118
74
|
});
|
|
119
75
|
}
|
|
120
76
|
|
|
121
|
-
async recallBundle(query: string
|
|
122
|
-
return withRequestDeadline('recallBundle', this.
|
|
123
|
-
const body
|
|
124
|
-
query,
|
|
125
|
-
top_k: topK ?? this.recallTopK,
|
|
126
|
-
include_pending: this.recallIncludePending,
|
|
127
|
-
include_related: options.includeRelated ?? this.includeRelatedMemories
|
|
77
|
+
async recallBundle(query: string): Promise<RecallBundleResponse> {
|
|
78
|
+
return withRequestDeadline('recallBundle', this.config.recall.timeoutMs, async (signal) => {
|
|
79
|
+
const body = {
|
|
80
|
+
...this.buildRecallBody(query, this.config.recall.maxResults),
|
|
128
81
|
};
|
|
129
|
-
if (typeof this.recallMinSimilarity === 'number') {
|
|
130
|
-
body.min_similarity = this.recallMinSimilarity;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
82
|
const res = await fetch(`${this.baseURL}/v1/recall?format=bundle`, {
|
|
134
83
|
method: 'POST',
|
|
135
84
|
headers: this.headers(),
|
|
136
85
|
body: JSON.stringify(body),
|
|
137
86
|
signal,
|
|
138
87
|
});
|
|
139
|
-
if (!res.ok) throw new Error(
|
|
140
|
-
|
|
141
|
-
return data;
|
|
88
|
+
if (!res.ok) throw new Error(await formatHttpError('recallBundle', res));
|
|
89
|
+
return await res.json() as RecallBundleResponse;
|
|
142
90
|
});
|
|
143
91
|
}
|
|
144
92
|
|
|
145
|
-
async ingest(sessionId: string, chunks:
|
|
93
|
+
async ingest(sessionId: string, chunks: IngestChunk[]): Promise<void> {
|
|
146
94
|
if (chunks.length === 0) return;
|
|
147
|
-
await withRequestDeadline('ingest', this.
|
|
95
|
+
await withRequestDeadline('ingest', this.config.capture.timeoutMs, async (signal) => {
|
|
148
96
|
const res = await fetch(`${this.baseURL}/v1/ingest`, {
|
|
149
97
|
method: 'POST',
|
|
150
98
|
headers: this.headers(),
|
|
@@ -155,56 +103,52 @@ export class PersistioClient {
|
|
|
155
103
|
});
|
|
156
104
|
}
|
|
157
105
|
|
|
158
|
-
async
|
|
159
|
-
|
|
106
|
+
async storeMemory(data: string, subject: string): Promise<PersistioMemory> {
|
|
107
|
+
return withRequestDeadline('memory_store', this.config.capture.timeoutMs, async (signal) => {
|
|
160
108
|
const res = await fetch(`${this.baseURL}/v1/memories`, {
|
|
161
109
|
method: 'POST',
|
|
162
110
|
headers: this.headers(),
|
|
163
111
|
body: JSON.stringify({ data, subject }),
|
|
164
112
|
signal,
|
|
165
113
|
});
|
|
166
|
-
if (!res.ok) throw new Error(
|
|
114
|
+
if (!res.ok) throw new Error(await formatHttpError('memory_store', res));
|
|
115
|
+
return await res.json() as PersistioMemory;
|
|
167
116
|
});
|
|
168
117
|
}
|
|
169
118
|
|
|
170
|
-
async
|
|
171
|
-
await withRequestDeadline('
|
|
172
|
-
const res = await fetch(`${this.baseURL}/v1/memories/${id}`, {
|
|
119
|
+
async forgetMemory(id: string): Promise<void> {
|
|
120
|
+
await withRequestDeadline('memory_forget', this.config.capture.timeoutMs, async (signal) => {
|
|
121
|
+
const res = await fetch(`${this.baseURL}/v1/memories/${encodeURIComponent(id)}`, {
|
|
173
122
|
method: 'DELETE',
|
|
174
123
|
headers: this.headers(),
|
|
175
124
|
signal,
|
|
176
125
|
});
|
|
177
|
-
if (!res.ok) throw new Error(
|
|
126
|
+
if (!res.ok) throw new Error(await formatHttpError('memory_forget', res));
|
|
178
127
|
});
|
|
179
128
|
}
|
|
180
129
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
130
|
+
private buildRecallBody(query: string, maxResults = this.config.recall.maxResults): Record<string, unknown> {
|
|
131
|
+
const body: Record<string, unknown> = {
|
|
132
|
+
query,
|
|
133
|
+
top_k: maxResults,
|
|
134
|
+
include_pending: this.config.recall.includePending,
|
|
135
|
+
include_related: this.config.recall.includeRelated,
|
|
136
|
+
};
|
|
137
|
+
if (typeof this.config.recall.minSimilarity === 'number') {
|
|
138
|
+
body.min_similarity = this.config.recall.minSimilarity;
|
|
139
|
+
}
|
|
140
|
+
return body;
|
|
192
141
|
}
|
|
193
142
|
|
|
194
|
-
|
|
195
|
-
return
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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 ?? [];
|
|
203
|
-
});
|
|
143
|
+
private headers(): Record<string, string> {
|
|
144
|
+
return {
|
|
145
|
+
'Content-Type': 'application/json',
|
|
146
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
147
|
+
};
|
|
204
148
|
}
|
|
205
149
|
}
|
|
206
150
|
|
|
207
|
-
async function withRequestDeadline<T>(
|
|
151
|
+
export async function withRequestDeadline<T>(
|
|
208
152
|
operation: string,
|
|
209
153
|
timeoutMs: number,
|
|
210
154
|
run: (signal: AbortSignal) => Promise<T>,
|
|
@@ -215,7 +159,6 @@ async function withRequestDeadline<T>(
|
|
|
215
159
|
|
|
216
160
|
const controller = new AbortController();
|
|
217
161
|
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
218
|
-
|
|
219
162
|
const deadline = new Promise<never>((_resolve, reject) => {
|
|
220
163
|
timeout = setTimeout(() => {
|
|
221
164
|
controller.abort();
|
|
@@ -236,8 +179,7 @@ async function withRequestDeadline<T>(
|
|
|
236
179
|
}
|
|
237
180
|
|
|
238
181
|
function isAbortLikeError(err: unknown): boolean {
|
|
239
|
-
|
|
240
|
-
return err.name === 'AbortError' || err.name === 'TimeoutError';
|
|
182
|
+
return err instanceof Error && (err.name === 'AbortError' || err.name === 'TimeoutError');
|
|
241
183
|
}
|
|
242
184
|
|
|
243
185
|
async function formatHttpError(operation: string, res: Response): Promise<string> {
|
|
@@ -245,7 +187,7 @@ async function formatHttpError(operation: string, res: Response): Promise<string
|
|
|
245
187
|
try {
|
|
246
188
|
detail = (await res.text()).trim().slice(0, 500);
|
|
247
189
|
} catch {
|
|
248
|
-
//
|
|
190
|
+
// Status code is still useful if the body cannot be read.
|
|
249
191
|
}
|
|
250
192
|
|
|
251
193
|
return detail
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
export type PersistioCaptureRoleStatus = 'enabled' | 'bounded' | 'disabled';
|
|
2
|
+
|
|
3
|
+
export interface PersistioV2Config {
|
|
4
|
+
baseURL: string;
|
|
5
|
+
apiKey: string;
|
|
6
|
+
autoRecall: boolean;
|
|
7
|
+
autoCapture: boolean;
|
|
8
|
+
recall: {
|
|
9
|
+
timeoutMs: number;
|
|
10
|
+
maxResults: number;
|
|
11
|
+
tokenBudget: number;
|
|
12
|
+
minSimilarity?: number;
|
|
13
|
+
includePending: boolean;
|
|
14
|
+
includeRelated: boolean;
|
|
15
|
+
queryMaxChars: number;
|
|
16
|
+
};
|
|
17
|
+
capture: {
|
|
18
|
+
timeoutMs: number;
|
|
19
|
+
maxCharsPerTurn: number;
|
|
20
|
+
maxCharsPerMessage: number;
|
|
21
|
+
maxChunksPerTurn: number;
|
|
22
|
+
maxChunkChars: number;
|
|
23
|
+
roles: {
|
|
24
|
+
user: 'enabled' | 'disabled';
|
|
25
|
+
assistant: PersistioCaptureRoleStatus;
|
|
26
|
+
tool: 'enabled' | 'disabled';
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const DEFAULT_CONFIG: PersistioV2Config = {
|
|
32
|
+
baseURL: '',
|
|
33
|
+
apiKey: '',
|
|
34
|
+
autoRecall: true,
|
|
35
|
+
autoCapture: true,
|
|
36
|
+
recall: {
|
|
37
|
+
timeoutMs: 1200,
|
|
38
|
+
maxResults: 4,
|
|
39
|
+
tokenBudget: 400,
|
|
40
|
+
includePending: false,
|
|
41
|
+
includeRelated: false,
|
|
42
|
+
queryMaxChars: 1200,
|
|
43
|
+
},
|
|
44
|
+
capture: {
|
|
45
|
+
timeoutMs: 10000,
|
|
46
|
+
maxCharsPerTurn: 6000,
|
|
47
|
+
maxCharsPerMessage: 3000,
|
|
48
|
+
maxChunksPerTurn: 4,
|
|
49
|
+
maxChunkChars: 2000,
|
|
50
|
+
roles: {
|
|
51
|
+
user: 'enabled',
|
|
52
|
+
assistant: 'bounded',
|
|
53
|
+
tool: 'disabled',
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
function readObject(value: unknown): Record<string, unknown> {
|
|
59
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
60
|
+
? value as Record<string, unknown>
|
|
61
|
+
: {};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function readString(value: unknown, fallback = ''): string {
|
|
65
|
+
return typeof value === 'string' ? value : fallback;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function readBoolean(value: unknown, fallback: boolean): boolean {
|
|
69
|
+
return typeof value === 'boolean' ? value : fallback;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function readPositiveInteger(value: unknown, fallback: number, min = 1): number {
|
|
73
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= min
|
|
74
|
+
? Math.floor(value)
|
|
75
|
+
: fallback;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function readSimilarity(value: unknown): number | undefined {
|
|
79
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= 0 && value <= 1
|
|
80
|
+
? value
|
|
81
|
+
: undefined;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function readEnabledDisabled(value: unknown, fallback: 'enabled' | 'disabled'): 'enabled' | 'disabled' {
|
|
85
|
+
return value === 'enabled' || value === 'disabled' ? value : fallback;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function readAssistantRole(value: unknown, fallback: PersistioCaptureRoleStatus): PersistioCaptureRoleStatus {
|
|
89
|
+
return value === 'enabled' || value === 'bounded' || value === 'disabled' ? value : fallback;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function resolveConfig(raw: unknown): PersistioV2Config {
|
|
93
|
+
const input = readObject(raw);
|
|
94
|
+
const recall = readObject(input['recall']);
|
|
95
|
+
const capture = readObject(input['capture']);
|
|
96
|
+
const roles = readObject(capture['roles']);
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
baseURL: readString(input['baseURL'], DEFAULT_CONFIG.baseURL),
|
|
100
|
+
apiKey: readString(input['apiKey'], DEFAULT_CONFIG.apiKey),
|
|
101
|
+
autoRecall: readBoolean(input['autoRecall'], DEFAULT_CONFIG.autoRecall),
|
|
102
|
+
autoCapture: readBoolean(input['autoCapture'], DEFAULT_CONFIG.autoCapture),
|
|
103
|
+
recall: {
|
|
104
|
+
timeoutMs: readPositiveInteger(recall['timeoutMs'], DEFAULT_CONFIG.recall.timeoutMs),
|
|
105
|
+
maxResults: readPositiveInteger(recall['maxResults'], DEFAULT_CONFIG.recall.maxResults),
|
|
106
|
+
tokenBudget: readPositiveInteger(recall['tokenBudget'], DEFAULT_CONFIG.recall.tokenBudget),
|
|
107
|
+
minSimilarity: readSimilarity(recall['minSimilarity']),
|
|
108
|
+
includePending: readBoolean(recall['includePending'], DEFAULT_CONFIG.recall.includePending),
|
|
109
|
+
includeRelated: readBoolean(recall['includeRelated'], DEFAULT_CONFIG.recall.includeRelated),
|
|
110
|
+
queryMaxChars: readPositiveInteger(recall['queryMaxChars'], DEFAULT_CONFIG.recall.queryMaxChars, 100),
|
|
111
|
+
},
|
|
112
|
+
capture: {
|
|
113
|
+
timeoutMs: readPositiveInteger(capture['timeoutMs'], DEFAULT_CONFIG.capture.timeoutMs),
|
|
114
|
+
maxCharsPerTurn: readPositiveInteger(capture['maxCharsPerTurn'], DEFAULT_CONFIG.capture.maxCharsPerTurn),
|
|
115
|
+
maxCharsPerMessage: readPositiveInteger(capture['maxCharsPerMessage'], DEFAULT_CONFIG.capture.maxCharsPerMessage),
|
|
116
|
+
maxChunksPerTurn: readPositiveInteger(capture['maxChunksPerTurn'], DEFAULT_CONFIG.capture.maxChunksPerTurn),
|
|
117
|
+
maxChunkChars: readPositiveInteger(capture['maxChunkChars'], DEFAULT_CONFIG.capture.maxChunkChars, 256),
|
|
118
|
+
roles: {
|
|
119
|
+
user: readEnabledDisabled(roles['user'], DEFAULT_CONFIG.capture.roles.user),
|
|
120
|
+
assistant: readAssistantRole(roles['assistant'], DEFAULT_CONFIG.capture.roles.assistant),
|
|
121
|
+
tool: readEnabledDisabled(roles['tool'], DEFAULT_CONFIG.capture.roles.tool),
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|