@persistio/openclaw-plugin 0.1.7 → 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 +86 -53
- package/dist/capture.d.ts +17 -0
- package/dist/capture.js +112 -0
- package/dist/client.d.ts +35 -51
- package/dist/client.js +45 -70
- package/dist/config.d.ts +29 -0
- package/dist/config.js +86 -0
- package/dist/index.js +303 -623
- package/dist/memory-format.d.ts +8 -0
- package/dist/memory-format.js +121 -0
- package/openclaw.plugin.json +69 -95
- package/package.json +10 -11
- package/src/capture.ts +132 -0
- package/src/client.ts +72 -111
- package/src/config.ts +125 -0
- package/src/index.ts +308 -718
- 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/README.md
CHANGED
|
@@ -1,40 +1,62 @@
|
|
|
1
1
|
# @persistio/openclaw-plugin
|
|
2
2
|
|
|
3
|
-
OpenClaw
|
|
3
|
+
OpenClaw-native long-term memory powered by Persistio.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
This is the production Persistio plugin for OpenClaw. Version `0.2.x` promotes the OpenClaw-native memory-slot architecture that was tested separately as `openclaw-persistio-v2`.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Design
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
- OpenClaw `>=2026.3.24-beta.2`
|
|
9
|
+
Persistio v2 separates the memory surfaces:
|
|
11
10
|
|
|
12
|
-
|
|
11
|
+
- `memory_recall` lets the model explicitly retrieve durable Persistio memory.
|
|
12
|
+
- `memory_store` stores a deliberate durable fact, preference, decision, or project note.
|
|
13
|
+
- `memory_forget` deletes a known memory id or returns candidate memories for a query.
|
|
14
|
+
- `autoRecall` optionally injects a small bounded memory block before a turn.
|
|
15
|
+
- `autoCapture` optionally captures bounded post-turn messages without awaiting Persistio from the OpenClaw hook.
|
|
16
|
+
|
|
17
|
+
The plugin registers as an OpenClaw memory plugin and provides prompt guidance. It does not replace OpenClaw's generic `memory_search` / `memory_get` tools in this first v2 package.
|
|
18
|
+
|
|
19
|
+
## Install
|
|
13
20
|
|
|
14
21
|
```bash
|
|
15
|
-
openclaw plugins install npm:@persistio/openclaw-plugin
|
|
16
|
-
openclaw plugins enable openclaw-persistio
|
|
22
|
+
openclaw plugins install npm:@persistio/openclaw-plugin@0.2.0
|
|
23
|
+
openclaw plugins enable openclaw-persistio-v2
|
|
17
24
|
openclaw gateway restart
|
|
18
|
-
openclaw plugins inspect openclaw-persistio --runtime --json
|
|
19
25
|
```
|
|
20
26
|
|
|
21
|
-
|
|
27
|
+
To test it as the active memory slot:
|
|
22
28
|
|
|
23
29
|
```json
|
|
24
30
|
{
|
|
25
31
|
"plugins": {
|
|
32
|
+
"slots": {
|
|
33
|
+
"memory": "openclaw-persistio-v2"
|
|
34
|
+
},
|
|
26
35
|
"entries": {
|
|
27
|
-
"openclaw-persistio": {
|
|
36
|
+
"openclaw-persistio-v2": {
|
|
28
37
|
"enabled": true,
|
|
29
38
|
"package": "@persistio/openclaw-plugin",
|
|
39
|
+
"hooks": {
|
|
40
|
+
"allowConversationAccess": true
|
|
41
|
+
},
|
|
30
42
|
"config": {
|
|
31
43
|
"baseURL": "https://api.persistio.ai",
|
|
32
44
|
"apiKey": "your-vault-api-key",
|
|
33
|
-
"
|
|
34
|
-
"
|
|
45
|
+
"autoRecall": true,
|
|
46
|
+
"autoCapture": true,
|
|
47
|
+
"recall": {
|
|
48
|
+
"timeoutMs": 1200,
|
|
49
|
+
"maxResults": 4,
|
|
50
|
+
"tokenBudget": 400,
|
|
51
|
+
"minSimilarity": 0.45,
|
|
52
|
+
"includePending": false,
|
|
53
|
+
"includeRelated": false
|
|
54
|
+
},
|
|
55
|
+
"capture": {
|
|
56
|
+
"timeoutMs": 10000,
|
|
35
57
|
"roles": {
|
|
36
58
|
"user": "enabled",
|
|
37
|
-
"
|
|
59
|
+
"assistant": "bounded",
|
|
38
60
|
"tool": "disabled"
|
|
39
61
|
}
|
|
40
62
|
}
|
|
@@ -45,44 +67,55 @@ Then register it in your OpenClaw config:
|
|
|
45
67
|
}
|
|
46
68
|
```
|
|
47
69
|
|
|
70
|
+
`hooks.allowConversationAccess` is required when `autoCapture` is enabled because the plugin reads the completed conversation snapshot from `agent_end`.
|
|
71
|
+
|
|
48
72
|
## Configuration
|
|
49
73
|
|
|
50
|
-
| Option |
|
|
51
|
-
|
|
52
|
-
| `
|
|
53
|
-
| `
|
|
54
|
-
| `
|
|
55
|
-
| `
|
|
56
|
-
| `
|
|
57
|
-
| `
|
|
58
|
-
| `
|
|
59
|
-
| `
|
|
60
|
-
| `
|
|
61
|
-
| `
|
|
62
|
-
| `
|
|
63
|
-
| `
|
|
64
|
-
| `
|
|
65
|
-
| `
|
|
66
|
-
| `
|
|
67
|
-
| `
|
|
68
|
-
| `
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
##
|
|
87
|
-
|
|
88
|
-
|
|
74
|
+
| Option | Default | Description |
|
|
75
|
+
|---|---:|---|
|
|
76
|
+
| `autoRecall` | `true` | Inject a small fail-open memory block before the model turn |
|
|
77
|
+
| `autoCapture` | `true` | Capture bounded post-turn messages asynchronously |
|
|
78
|
+
| `recall.timeoutMs` | `1200` | HTTP timeout for recall and recall-bundle calls |
|
|
79
|
+
| `recall.maxResults` | `4` | Maximum memories returned by recall |
|
|
80
|
+
| `recall.tokenBudget` | `400` | Approximate prompt-token budget for auto-recall |
|
|
81
|
+
| `recall.minSimilarity` | unset | Optional Persistio similarity floor |
|
|
82
|
+
| `recall.includePending` | `false` | Include pending candidate memories in hot-path recall |
|
|
83
|
+
| `recall.includeRelated` | `false` | Include graph-related memories in hot-path recall |
|
|
84
|
+
| `recall.queryMaxChars` | `1200` | Maximum latest-user query characters embedded for recall |
|
|
85
|
+
| `capture.timeoutMs` | `10000` | HTTP timeout for post-turn ingest and manual writes |
|
|
86
|
+
| `capture.maxCharsPerTurn` | `6000` | Maximum captured characters per turn |
|
|
87
|
+
| `capture.maxCharsPerMessage` | `3000` | Maximum captured characters per message |
|
|
88
|
+
| `capture.maxChunksPerTurn` | `4` | Maximum chunks sent to Persistio per turn |
|
|
89
|
+
| `capture.maxChunkChars` | `2000` | Maximum characters per capture chunk |
|
|
90
|
+
| `capture.roles.user` | `enabled` | Capture user messages |
|
|
91
|
+
| `capture.roles.assistant` | `bounded` | Capture assistant messages after deterministic noise filtering |
|
|
92
|
+
| `capture.roles.tool` | `disabled` | Capture tool messages |
|
|
93
|
+
|
|
94
|
+
## Upgrade from 0.1.x
|
|
95
|
+
|
|
96
|
+
Install the `0.2.x` package, configure `openclaw-persistio-v2`, and point the OpenClaw memory slot at the new plugin id:
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"plugins": {
|
|
101
|
+
"slots": {
|
|
102
|
+
"memory": "openclaw-persistio-v2"
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Keep the old `openclaw-persistio` entry disabled or remove it after confirming the new slot behaves correctly. The v2 id is intentionally distinct so operators opt into the new memory-slot behavior instead of silently changing an existing v1 install.
|
|
109
|
+
|
|
110
|
+
## Benchmark Posture
|
|
111
|
+
|
|
112
|
+
For behavioral benchmark work, leave `autoRecall=true` and `autoCapture=true`, keep recall under a tight timeout, and keep `includePending` / `includeRelated` off unless the specific benchmark requires them.
|
|
113
|
+
|
|
114
|
+
The expected turn shape is:
|
|
115
|
+
|
|
116
|
+
```text
|
|
117
|
+
OpenClaw turn
|
|
118
|
+
-> Persistio autoRecall, bounded and fail-open
|
|
119
|
+
-> model answers with a tiny memory block
|
|
120
|
+
-> Persistio autoCapture, async and non-blocking
|
|
121
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { IngestChunk } from './client.js';
|
|
2
|
+
import type { PersistioV2Config } from './config.js';
|
|
3
|
+
export interface PreparedCapture {
|
|
4
|
+
chunks: IngestChunk[];
|
|
5
|
+
keys: string[];
|
|
6
|
+
items: CaptureItem[];
|
|
7
|
+
}
|
|
8
|
+
export interface CaptureItem {
|
|
9
|
+
key: string;
|
|
10
|
+
chunks: IngestChunk[];
|
|
11
|
+
}
|
|
12
|
+
export interface PrepareCaptureOptions {
|
|
13
|
+
shouldIncludeKey?: (key: string) => boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function prepareCapture(event: {
|
|
16
|
+
messages?: unknown[];
|
|
17
|
+
}, cfg: PersistioV2Config, options?: PrepareCaptureOptions): PreparedCapture;
|
package/dist/capture.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { extractTextFromMessage } from './memory-format.js';
|
|
2
|
+
export function prepareCapture(event, cfg, options = {}) {
|
|
3
|
+
if (!Array.isArray(event.messages))
|
|
4
|
+
return { chunks: [], keys: [], items: [] };
|
|
5
|
+
const chunks = [];
|
|
6
|
+
const keys = [];
|
|
7
|
+
const items = [];
|
|
8
|
+
let turnChars = 0;
|
|
9
|
+
for (const [index, message] of event.messages.entries()) {
|
|
10
|
+
const role = normalizeRole(message);
|
|
11
|
+
if (!role || !shouldCaptureRole(role, cfg))
|
|
12
|
+
continue;
|
|
13
|
+
const rawText = extractTextFromMessage(message);
|
|
14
|
+
const key = messageKey(message, role, rawText, index);
|
|
15
|
+
if (options.shouldIncludeKey && !options.shouldIncludeKey(key))
|
|
16
|
+
continue;
|
|
17
|
+
if (chunks.length >= cfg.capture.maxChunksPerTurn)
|
|
18
|
+
break;
|
|
19
|
+
const preparedText = prepareTextForRole(rawText, role, cfg);
|
|
20
|
+
if (!preparedText)
|
|
21
|
+
continue;
|
|
22
|
+
const remainingTurnChars = cfg.capture.maxCharsPerTurn - turnChars;
|
|
23
|
+
if (remainingTurnChars <= 0)
|
|
24
|
+
break;
|
|
25
|
+
const boundedText = truncate(preparedText, Math.min(cfg.capture.maxCharsPerMessage, remainingTurnChars));
|
|
26
|
+
const itemChunks = [];
|
|
27
|
+
for (const chunk of chunkText(boundedText, cfg.capture.maxChunkChars)) {
|
|
28
|
+
if (chunks.length >= cfg.capture.maxChunksPerTurn)
|
|
29
|
+
break;
|
|
30
|
+
const ingestChunk = {
|
|
31
|
+
role,
|
|
32
|
+
content: chunk,
|
|
33
|
+
timestamp: resolveTimestamp(message) ?? new Date().toISOString(),
|
|
34
|
+
};
|
|
35
|
+
chunks.push(ingestChunk);
|
|
36
|
+
itemChunks.push(ingestChunk);
|
|
37
|
+
turnChars += chunk.length;
|
|
38
|
+
}
|
|
39
|
+
if (itemChunks.length > 0) {
|
|
40
|
+
keys.push(key);
|
|
41
|
+
items.push({ key, chunks: itemChunks });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return { chunks, keys, items };
|
|
45
|
+
}
|
|
46
|
+
function normalizeRole(message) {
|
|
47
|
+
if (typeof message !== 'object' || message === null)
|
|
48
|
+
return null;
|
|
49
|
+
const role = message['role'];
|
|
50
|
+
return role === 'user' || role === 'assistant' || role === 'tool' ? role : null;
|
|
51
|
+
}
|
|
52
|
+
function shouldCaptureRole(role, cfg) {
|
|
53
|
+
if (role === 'user')
|
|
54
|
+
return cfg.capture.roles.user === 'enabled';
|
|
55
|
+
if (role === 'assistant')
|
|
56
|
+
return cfg.capture.roles.assistant !== 'disabled';
|
|
57
|
+
return cfg.capture.roles.tool === 'enabled';
|
|
58
|
+
}
|
|
59
|
+
function prepareTextForRole(text, role, cfg) {
|
|
60
|
+
const normalized = normalizeText(text);
|
|
61
|
+
if (!normalized)
|
|
62
|
+
return '';
|
|
63
|
+
if (role !== 'assistant' || cfg.capture.roles.assistant !== 'bounded') {
|
|
64
|
+
return normalized;
|
|
65
|
+
}
|
|
66
|
+
return normalized
|
|
67
|
+
.replace(/```[\s\S]*?```/g, '[Code block omitted from memory capture]')
|
|
68
|
+
.replace(/\n(?:[|].*[|]\n){8,}/g, '\n[Large table omitted from memory capture]\n')
|
|
69
|
+
.replace(/\n(?:[-+].*\n){20,}/g, '\n[Large diff/log omitted from memory capture]\n')
|
|
70
|
+
.trim();
|
|
71
|
+
}
|
|
72
|
+
function normalizeText(text) {
|
|
73
|
+
return text
|
|
74
|
+
.replace(/\r\n?/g, '\n')
|
|
75
|
+
.replace(/[ \t]+\n/g, '\n')
|
|
76
|
+
.replace(/\n{4,}/g, '\n\n\n')
|
|
77
|
+
.trim();
|
|
78
|
+
}
|
|
79
|
+
function chunkText(text, maxChars) {
|
|
80
|
+
if (text.length <= maxChars)
|
|
81
|
+
return [text];
|
|
82
|
+
const chunks = [];
|
|
83
|
+
for (let start = 0; start < text.length; start += maxChars) {
|
|
84
|
+
const chunk = text.slice(start, start + maxChars).trim();
|
|
85
|
+
if (chunk)
|
|
86
|
+
chunks.push(chunk);
|
|
87
|
+
}
|
|
88
|
+
return chunks;
|
|
89
|
+
}
|
|
90
|
+
function truncate(text, maxChars) {
|
|
91
|
+
if (text.length <= maxChars)
|
|
92
|
+
return text;
|
|
93
|
+
return `${text.slice(0, Math.max(0, maxChars - 3)).trimEnd()}...`;
|
|
94
|
+
}
|
|
95
|
+
function resolveTimestamp(message) {
|
|
96
|
+
if (typeof message !== 'object' || message === null)
|
|
97
|
+
return undefined;
|
|
98
|
+
const value = message['timestamp'];
|
|
99
|
+
if (typeof value === 'string')
|
|
100
|
+
return value;
|
|
101
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
102
|
+
return new Date(value).toISOString();
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
function messageKey(message, role, text, index) {
|
|
106
|
+
if (typeof message === 'object' && message !== null) {
|
|
107
|
+
const id = message['id'];
|
|
108
|
+
if (typeof id === 'string' && id)
|
|
109
|
+
return `${role}:${id}`;
|
|
110
|
+
}
|
|
111
|
+
return `${role}:${index}:${text.slice(0, 300)}`;
|
|
112
|
+
}
|
package/dist/client.d.ts
CHANGED
|
@@ -1,71 +1,55 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export interface PersistioConfig {
|
|
3
|
-
baseURL: string;
|
|
4
|
-
apiKey: string;
|
|
5
|
-
tokenBudget: number;
|
|
6
|
-
recallTopK: number;
|
|
7
|
-
recallMinSimilarity?: number;
|
|
8
|
-
recallTimeout: number;
|
|
9
|
-
ingest: PersistioIngestPolicy;
|
|
10
|
-
send: PersistioSendConfig;
|
|
11
|
-
}
|
|
12
|
-
export type PersistioSendRoleStatus = 'enabled' | 'disabled';
|
|
13
|
-
export interface PersistioSendConfig {
|
|
14
|
-
roles: {
|
|
15
|
-
user: PersistioSendRoleStatus;
|
|
16
|
-
agent: PersistioSendRoleStatus;
|
|
17
|
-
tool: PersistioSendRoleStatus;
|
|
18
|
-
};
|
|
19
|
-
}
|
|
1
|
+
import type { PersistioV2Config } from './config.js';
|
|
20
2
|
export interface PersistioMemory {
|
|
21
3
|
id: string;
|
|
22
4
|
data: string;
|
|
23
5
|
subject: string;
|
|
24
6
|
similarity?: number;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
includePending?: boolean;
|
|
7
|
+
confidence?: number;
|
|
8
|
+
categories?: string[];
|
|
9
|
+
source?: string;
|
|
10
|
+
edge_type?: string | null;
|
|
30
11
|
}
|
|
31
12
|
export interface RecallBundle {
|
|
32
13
|
global_user_rules?: string[];
|
|
33
|
-
user_rules
|
|
34
|
-
user_preferences
|
|
35
|
-
task_patterns
|
|
36
|
-
workflows
|
|
37
|
-
project
|
|
38
|
-
constraints
|
|
39
|
-
decisions
|
|
40
|
-
system_facts
|
|
41
|
-
domain_knowledge
|
|
14
|
+
user_rules?: string[];
|
|
15
|
+
user_preferences?: string[];
|
|
16
|
+
task_patterns?: string[];
|
|
17
|
+
workflows?: string[];
|
|
18
|
+
project?: string[];
|
|
19
|
+
constraints?: string[];
|
|
20
|
+
decisions?: string[];
|
|
21
|
+
system_facts?: string[];
|
|
22
|
+
domain_knowledge?: string[];
|
|
42
23
|
}
|
|
43
24
|
export interface RecallBundleResponse {
|
|
44
|
-
bundle
|
|
25
|
+
bundle?: RecallBundle;
|
|
45
26
|
related_bundle?: RecallBundle;
|
|
46
27
|
}
|
|
28
|
+
export interface RecallResult {
|
|
29
|
+
memories: PersistioMemory[];
|
|
30
|
+
relatedMemories: PersistioMemory[];
|
|
31
|
+
}
|
|
32
|
+
export interface IngestChunk {
|
|
33
|
+
role: string;
|
|
34
|
+
content: string;
|
|
35
|
+
timestamp: string;
|
|
36
|
+
}
|
|
47
37
|
export declare class PersistioTimeoutError extends Error {
|
|
48
38
|
constructor(operation: string, timeoutMs: number);
|
|
49
39
|
}
|
|
50
40
|
export declare class PersistioClient {
|
|
41
|
+
private readonly config;
|
|
51
42
|
private readonly baseURL;
|
|
52
43
|
private readonly apiKey;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
44
|
+
constructor(config: PersistioV2Config);
|
|
45
|
+
recall(query: string, options?: {
|
|
46
|
+
maxResults?: number;
|
|
47
|
+
}): Promise<RecallResult>;
|
|
48
|
+
recallBundle(query: string): Promise<RecallBundleResponse>;
|
|
49
|
+
ingest(sessionId: string, chunks: IngestChunk[]): Promise<void>;
|
|
50
|
+
storeMemory(data: string, subject: string): Promise<PersistioMemory>;
|
|
51
|
+
forgetMemory(id: string): Promise<void>;
|
|
52
|
+
private buildRecallBody;
|
|
59
53
|
private headers;
|
|
60
|
-
recall(query: string): Promise<PersistioMemory[]>;
|
|
61
|
-
recallBundle(query: string, topK?: number): Promise<RecallBundleResponse>;
|
|
62
|
-
ingest(sessionId: string, chunks: Array<{
|
|
63
|
-
role: string;
|
|
64
|
-
content: string;
|
|
65
|
-
timestamp: string;
|
|
66
|
-
}>): Promise<void>;
|
|
67
|
-
addMemory(data: string, subject: string): Promise<void>;
|
|
68
|
-
deleteMemory(id: string): Promise<void>;
|
|
69
|
-
getMemory(id: string, options?: GetMemoryOptions): Promise<PersistioMemory | null>;
|
|
70
|
-
listMemories(): Promise<PersistioMemory[]>;
|
|
71
54
|
}
|
|
55
|
+
export declare function withRequestDeadline<T>(operation: string, timeoutMs: number, run: (signal: AbortSignal) => Promise<T>): Promise<T>;
|
package/dist/client.js
CHANGED
|
@@ -5,34 +5,17 @@ export class PersistioTimeoutError extends Error {
|
|
|
5
5
|
}
|
|
6
6
|
}
|
|
7
7
|
export class PersistioClient {
|
|
8
|
+
config;
|
|
8
9
|
baseURL;
|
|
9
10
|
apiKey;
|
|
10
|
-
recallTopK;
|
|
11
|
-
recallMinSimilarity;
|
|
12
|
-
recallTimeout;
|
|
13
|
-
ingestTimeout;
|
|
14
|
-
writeTimeout;
|
|
15
11
|
constructor(config) {
|
|
12
|
+
this.config = config;
|
|
16
13
|
this.baseURL = config.baseURL.replace(/\/$/, '');
|
|
17
14
|
this.apiKey = config.apiKey;
|
|
18
|
-
this.recallTopK = config.recallTopK;
|
|
19
|
-
this.recallMinSimilarity = config.recallMinSimilarity;
|
|
20
|
-
this.recallTimeout = config.recallTimeout;
|
|
21
|
-
this.ingestTimeout = config.ingest.timeoutMs;
|
|
22
|
-
this.writeTimeout = config.ingest.timeoutMs;
|
|
23
15
|
}
|
|
24
|
-
|
|
25
|
-
return {
|
|
26
|
-
|
|
27
|
-
'Authorization': `Bearer ${this.apiKey}`,
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
async recall(query) {
|
|
31
|
-
return withRequestDeadline('recall', this.recallTimeout, async (signal) => {
|
|
32
|
-
const body = { query, top_k: this.recallTopK, include_pending: true };
|
|
33
|
-
if (typeof this.recallMinSimilarity === 'number') {
|
|
34
|
-
body.min_similarity = this.recallMinSimilarity;
|
|
35
|
-
}
|
|
16
|
+
async recall(query, options = {}) {
|
|
17
|
+
return withRequestDeadline('recall', this.config.recall.timeoutMs, async (signal) => {
|
|
18
|
+
const body = this.buildRecallBody(query, options.maxResults);
|
|
36
19
|
const res = await fetch(`${this.baseURL}/v1/recall`, {
|
|
37
20
|
method: 'POST',
|
|
38
21
|
headers: this.headers(),
|
|
@@ -40,17 +23,19 @@ export class PersistioClient {
|
|
|
40
23
|
signal,
|
|
41
24
|
});
|
|
42
25
|
if (!res.ok)
|
|
43
|
-
throw new Error(
|
|
26
|
+
throw new Error(await formatHttpError('recall', res));
|
|
44
27
|
const data = await res.json();
|
|
45
|
-
return
|
|
28
|
+
return {
|
|
29
|
+
memories: Array.isArray(data.memories) ? data.memories : [],
|
|
30
|
+
relatedMemories: Array.isArray(data.related_memories) ? data.related_memories : [],
|
|
31
|
+
};
|
|
46
32
|
});
|
|
47
33
|
}
|
|
48
|
-
async recallBundle(query
|
|
49
|
-
return withRequestDeadline('recallBundle', this.
|
|
50
|
-
const body = {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
34
|
+
async recallBundle(query) {
|
|
35
|
+
return withRequestDeadline('recallBundle', this.config.recall.timeoutMs, async (signal) => {
|
|
36
|
+
const body = {
|
|
37
|
+
...this.buildRecallBody(query, this.config.recall.maxResults),
|
|
38
|
+
};
|
|
54
39
|
const res = await fetch(`${this.baseURL}/v1/recall?format=bundle`, {
|
|
55
40
|
method: 'POST',
|
|
56
41
|
headers: this.headers(),
|
|
@@ -58,15 +43,14 @@ export class PersistioClient {
|
|
|
58
43
|
signal,
|
|
59
44
|
});
|
|
60
45
|
if (!res.ok)
|
|
61
|
-
throw new Error(
|
|
62
|
-
|
|
63
|
-
return data;
|
|
46
|
+
throw new Error(await formatHttpError('recallBundle', res));
|
|
47
|
+
return await res.json();
|
|
64
48
|
});
|
|
65
49
|
}
|
|
66
50
|
async ingest(sessionId, chunks) {
|
|
67
51
|
if (chunks.length === 0)
|
|
68
52
|
return;
|
|
69
|
-
await withRequestDeadline('ingest', this.
|
|
53
|
+
await withRequestDeadline('ingest', this.config.capture.timeoutMs, async (signal) => {
|
|
70
54
|
const res = await fetch(`${this.baseURL}/v1/ingest`, {
|
|
71
55
|
method: 'POST',
|
|
72
56
|
headers: this.headers(),
|
|
@@ -77,8 +61,8 @@ export class PersistioClient {
|
|
|
77
61
|
throw new Error(await formatHttpError('ingest', res));
|
|
78
62
|
});
|
|
79
63
|
}
|
|
80
|
-
async
|
|
81
|
-
|
|
64
|
+
async storeMemory(data, subject) {
|
|
65
|
+
return withRequestDeadline('memory_store', this.config.capture.timeoutMs, async (signal) => {
|
|
82
66
|
const res = await fetch(`${this.baseURL}/v1/memories`, {
|
|
83
67
|
method: 'POST',
|
|
84
68
|
headers: this.headers(),
|
|
@@ -86,48 +70,41 @@ export class PersistioClient {
|
|
|
86
70
|
signal,
|
|
87
71
|
});
|
|
88
72
|
if (!res.ok)
|
|
89
|
-
throw new Error(
|
|
73
|
+
throw new Error(await formatHttpError('memory_store', res));
|
|
74
|
+
return await res.json();
|
|
90
75
|
});
|
|
91
76
|
}
|
|
92
|
-
async
|
|
93
|
-
await withRequestDeadline('
|
|
94
|
-
const res = await fetch(`${this.baseURL}/v1/memories/${id}`, {
|
|
77
|
+
async forgetMemory(id) {
|
|
78
|
+
await withRequestDeadline('memory_forget', this.config.capture.timeoutMs, async (signal) => {
|
|
79
|
+
const res = await fetch(`${this.baseURL}/v1/memories/${encodeURIComponent(id)}`, {
|
|
95
80
|
method: 'DELETE',
|
|
96
81
|
headers: this.headers(),
|
|
97
82
|
signal,
|
|
98
83
|
});
|
|
99
84
|
if (!res.ok)
|
|
100
|
-
throw new Error(
|
|
85
|
+
throw new Error(await formatHttpError('memory_forget', res));
|
|
101
86
|
});
|
|
102
87
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
return await res.json();
|
|
115
|
-
});
|
|
88
|
+
buildRecallBody(query, maxResults = this.config.recall.maxResults) {
|
|
89
|
+
const body = {
|
|
90
|
+
query,
|
|
91
|
+
top_k: maxResults,
|
|
92
|
+
include_pending: this.config.recall.includePending,
|
|
93
|
+
include_related: this.config.recall.includeRelated,
|
|
94
|
+
};
|
|
95
|
+
if (typeof this.config.recall.minSimilarity === 'number') {
|
|
96
|
+
body.min_similarity = this.config.recall.minSimilarity;
|
|
97
|
+
}
|
|
98
|
+
return body;
|
|
116
99
|
}
|
|
117
|
-
|
|
118
|
-
return
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
});
|
|
123
|
-
if (!res.ok)
|
|
124
|
-
throw new Error(`Persistio listMemories failed: ${res.status}`);
|
|
125
|
-
const data = await res.json();
|
|
126
|
-
return data.items ?? [];
|
|
127
|
-
});
|
|
100
|
+
headers() {
|
|
101
|
+
return {
|
|
102
|
+
'Content-Type': 'application/json',
|
|
103
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
104
|
+
};
|
|
128
105
|
}
|
|
129
106
|
}
|
|
130
|
-
async function withRequestDeadline(operation, timeoutMs, run) {
|
|
107
|
+
export async function withRequestDeadline(operation, timeoutMs, run) {
|
|
131
108
|
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
132
109
|
return run(new AbortController().signal);
|
|
133
110
|
}
|
|
@@ -154,9 +131,7 @@ async function withRequestDeadline(operation, timeoutMs, run) {
|
|
|
154
131
|
}
|
|
155
132
|
}
|
|
156
133
|
function isAbortLikeError(err) {
|
|
157
|
-
|
|
158
|
-
return false;
|
|
159
|
-
return err.name === 'AbortError' || err.name === 'TimeoutError';
|
|
134
|
+
return err instanceof Error && (err.name === 'AbortError' || err.name === 'TimeoutError');
|
|
160
135
|
}
|
|
161
136
|
async function formatHttpError(operation, res) {
|
|
162
137
|
let detail = '';
|
|
@@ -164,7 +139,7 @@ async function formatHttpError(operation, res) {
|
|
|
164
139
|
detail = (await res.text()).trim().slice(0, 500);
|
|
165
140
|
}
|
|
166
141
|
catch {
|
|
167
|
-
//
|
|
142
|
+
// Status code is still useful if the body cannot be read.
|
|
168
143
|
}
|
|
169
144
|
return detail
|
|
170
145
|
? `Persistio ${operation} failed: ${res.status} ${detail}`
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type PersistioCaptureRoleStatus = 'enabled' | 'bounded' | 'disabled';
|
|
2
|
+
export interface PersistioV2Config {
|
|
3
|
+
baseURL: string;
|
|
4
|
+
apiKey: string;
|
|
5
|
+
autoRecall: boolean;
|
|
6
|
+
autoCapture: boolean;
|
|
7
|
+
recall: {
|
|
8
|
+
timeoutMs: number;
|
|
9
|
+
maxResults: number;
|
|
10
|
+
tokenBudget: number;
|
|
11
|
+
minSimilarity?: number;
|
|
12
|
+
includePending: boolean;
|
|
13
|
+
includeRelated: boolean;
|
|
14
|
+
queryMaxChars: number;
|
|
15
|
+
};
|
|
16
|
+
capture: {
|
|
17
|
+
timeoutMs: number;
|
|
18
|
+
maxCharsPerTurn: number;
|
|
19
|
+
maxCharsPerMessage: number;
|
|
20
|
+
maxChunksPerTurn: number;
|
|
21
|
+
maxChunkChars: number;
|
|
22
|
+
roles: {
|
|
23
|
+
user: 'enabled' | 'disabled';
|
|
24
|
+
assistant: PersistioCaptureRoleStatus;
|
|
25
|
+
tool: 'enabled' | 'disabled';
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export declare function resolveConfig(raw: unknown): PersistioV2Config;
|