@pyxmate/memory 0.0.1-beta
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/LICENSE +21 -0
- package/README.md +54 -0
- package/dist/chunk-EDIYGMT2.mjs +385 -0
- package/dist/chunk-Q4QIILKH.mjs +190 -0
- package/dist/dashboard.d.ts +216 -0
- package/dist/dashboard.mjs +29 -0
- package/dist/index.d.ts +270 -0
- package/dist/index.mjs +46 -0
- package/dist/react.d.ts +28 -0
- package/dist/react.mjs +154 -0
- package/package.json +59 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Pyxmate
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# @pyxmate/memory
|
|
2
|
+
|
|
3
|
+
SDK for **pyx-memory** — Memory as a Service for AI agents.
|
|
4
|
+
|
|
5
|
+
Provides an HTTP client, shared types, and optional dashboard + React hooks for interacting with a pyx-memory server.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @pyxmate/memory
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { MemoryClient } from '@pyxmate/memory';
|
|
17
|
+
import type { MemoryEntry } from '@pyxmate/memory';
|
|
18
|
+
|
|
19
|
+
const client = new MemoryClient('http://localhost:7822');
|
|
20
|
+
await client.initialize();
|
|
21
|
+
|
|
22
|
+
// Store a memory
|
|
23
|
+
await client.store({
|
|
24
|
+
content: 'The project deadline is March 15th',
|
|
25
|
+
agentId: 'my-agent',
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Search memories
|
|
29
|
+
const results = await client.search({ query: 'deadline', limit: 5 });
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Entry Points
|
|
33
|
+
|
|
34
|
+
| Import | Description |
|
|
35
|
+
|--------|-------------|
|
|
36
|
+
| `@pyxmate/memory` | Core SDK — `MemoryClient`, shared types, constants |
|
|
37
|
+
| `@pyxmate/memory/dashboard` | Headless dashboard utilities — `DashboardClient`, aggregations, graph transforms (no React) |
|
|
38
|
+
| `@pyxmate/memory/react` | React hooks — `useMemoryStats`, `useMemoryHealth`, `useKnowledgeGraph`, etc. (requires React >= 18) |
|
|
39
|
+
|
|
40
|
+
## Running the Server
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
docker pull ghcr.io/fysoul17/pyx-memory:latest
|
|
44
|
+
docker run -p 7822:7822 ghcr.io/fysoul17/pyx-memory:latest
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Requirements
|
|
48
|
+
|
|
49
|
+
- ESM only (`"type": "module"`)
|
|
50
|
+
- React >= 18 (optional, only for `@pyxmate/memory/react`)
|
|
51
|
+
|
|
52
|
+
## License
|
|
53
|
+
|
|
54
|
+
MIT
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MemoryClient
|
|
3
|
+
} from "./chunk-Q4QIILKH.mjs";
|
|
4
|
+
|
|
5
|
+
// ../dashboard/src/aggregations/consolidation-analytics.ts
|
|
6
|
+
function analyzeConsolidationLog(entries) {
|
|
7
|
+
const totalRuns = entries.length;
|
|
8
|
+
if (totalRuns === 0) {
|
|
9
|
+
return {
|
|
10
|
+
entries,
|
|
11
|
+
totalRuns: 0,
|
|
12
|
+
avgDurationMs: 0,
|
|
13
|
+
avgEntriesProcessed: 0,
|
|
14
|
+
avgEntriesMerged: 0,
|
|
15
|
+
avgEntriesArchived: 0,
|
|
16
|
+
lastRunAt: null,
|
|
17
|
+
consolidationTrend: "insufficient_data"
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const avgDurationMs = Math.round(entries.reduce((sum, e) => sum + e.durationMs, 0) / totalRuns);
|
|
21
|
+
const avgEntriesProcessed = Math.round(entries.reduce((sum, e) => sum + e.entriesProcessed, 0) / totalRuns * 100) / 100;
|
|
22
|
+
const avgEntriesMerged = Math.round(entries.reduce((sum, e) => sum + e.entriesMerged, 0) / totalRuns * 100) / 100;
|
|
23
|
+
const avgEntriesArchived = Math.round(entries.reduce((sum, e) => sum + e.entriesArchived, 0) / totalRuns * 100) / 100;
|
|
24
|
+
const lastRunAt = entries[0]?.runAt ?? null;
|
|
25
|
+
const consolidationTrend = computeTrend(entries);
|
|
26
|
+
return {
|
|
27
|
+
entries,
|
|
28
|
+
totalRuns,
|
|
29
|
+
avgDurationMs,
|
|
30
|
+
avgEntriesProcessed,
|
|
31
|
+
avgEntriesMerged,
|
|
32
|
+
avgEntriesArchived,
|
|
33
|
+
lastRunAt,
|
|
34
|
+
consolidationTrend
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function computeTrend(entries) {
|
|
38
|
+
if (entries.length < 3) return "insufficient_data";
|
|
39
|
+
const mid = Math.floor(entries.length / 2);
|
|
40
|
+
const recentHalf = entries.slice(0, mid);
|
|
41
|
+
const olderHalf = entries.slice(mid);
|
|
42
|
+
const recentAvg = recentHalf.reduce((sum, e) => sum + e.entriesMerged, 0) / recentHalf.length;
|
|
43
|
+
const olderAvg = olderHalf.reduce((sum, e) => sum + e.entriesMerged, 0) / olderHalf.length;
|
|
44
|
+
const diff = recentAvg - olderAvg;
|
|
45
|
+
const threshold = Math.max(olderAvg * 0.2, 1);
|
|
46
|
+
if (diff > threshold) return "increasing";
|
|
47
|
+
if (diff < -threshold) return "decreasing";
|
|
48
|
+
return "stable";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ../dashboard/src/aggregations/health-enrichment.ts
|
|
52
|
+
function formatUptime(seconds) {
|
|
53
|
+
const days = Math.floor(seconds / 86400);
|
|
54
|
+
const hours = Math.floor(seconds % 86400 / 3600);
|
|
55
|
+
const minutes = Math.floor(seconds % 3600 / 60);
|
|
56
|
+
const parts = [];
|
|
57
|
+
if (days > 0) parts.push(`${days}d`);
|
|
58
|
+
if (hours > 0) parts.push(`${hours}h`);
|
|
59
|
+
if (minutes > 0 || parts.length === 0) parts.push(`${minutes}m`);
|
|
60
|
+
return parts.join(" ");
|
|
61
|
+
}
|
|
62
|
+
function formatBytes(bytes) {
|
|
63
|
+
if (bytes === 0) return "0 B";
|
|
64
|
+
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
65
|
+
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
66
|
+
const value = bytes / 1024 ** i;
|
|
67
|
+
return `${Math.round(value * 100) / 100} ${units[i]}`;
|
|
68
|
+
}
|
|
69
|
+
function enrichHealth(raw) {
|
|
70
|
+
const storageMB = raw.stats ? Math.round(raw.stats.storageUsedBytes / (1024 * 1024) * 100) / 100 : 0;
|
|
71
|
+
return {
|
|
72
|
+
status: raw.status === "ok" ? "ok" : "degraded",
|
|
73
|
+
uptime: raw.uptime,
|
|
74
|
+
uptimeFormatted: formatUptime(raw.uptime),
|
|
75
|
+
storageMB,
|
|
76
|
+
isHealthy: raw.status === "ok",
|
|
77
|
+
embeddingProvider: raw.embeddingProvider,
|
|
78
|
+
stats: raw.stats
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function unreachableHealth(_error) {
|
|
82
|
+
return {
|
|
83
|
+
status: "unreachable",
|
|
84
|
+
uptime: 0,
|
|
85
|
+
uptimeFormatted: "0m",
|
|
86
|
+
storageMB: 0,
|
|
87
|
+
isHealthy: false,
|
|
88
|
+
embeddingProvider: "unknown",
|
|
89
|
+
stats: void 0
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ../dashboard/src/aggregations/metrics.ts
|
|
94
|
+
var STALE_DAYS = 30;
|
|
95
|
+
function computeMetrics(stats, entries) {
|
|
96
|
+
const storageMB = Math.round(stats.storageUsedBytes / (1024 * 1024) * 100) / 100;
|
|
97
|
+
let importanceSum = 0;
|
|
98
|
+
let importanceCount = 0;
|
|
99
|
+
let staleCandidates = 0;
|
|
100
|
+
const staleThreshold = /* @__PURE__ */ new Date();
|
|
101
|
+
staleThreshold.setDate(staleThreshold.getDate() - STALE_DAYS);
|
|
102
|
+
const staleThresholdStr = staleThreshold.toISOString();
|
|
103
|
+
for (const entry of entries) {
|
|
104
|
+
if (entry.importance != null) {
|
|
105
|
+
importanceSum += entry.importance;
|
|
106
|
+
importanceCount++;
|
|
107
|
+
}
|
|
108
|
+
const lastAccess = entry.lastAccessed ?? entry.createdAt;
|
|
109
|
+
if (lastAccess < staleThresholdStr && (entry.importance ?? 0.5) < 0.3) {
|
|
110
|
+
staleCandidates++;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const avgImportance = importanceCount > 0 ? Math.round(importanceSum / importanceCount * 1e3) / 1e3 : 0;
|
|
114
|
+
return {
|
|
115
|
+
totalEntries: stats.totalEntries,
|
|
116
|
+
vectorCount: stats.vectorCount,
|
|
117
|
+
storageUsedBytes: stats.storageUsedBytes,
|
|
118
|
+
storageMB,
|
|
119
|
+
avgImportance,
|
|
120
|
+
staleCandidates,
|
|
121
|
+
graphNodeCount: stats.graphNodeCount ?? 0,
|
|
122
|
+
graphEdgeCount: stats.graphEdgeCount ?? 0
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ../dashboard/src/aggregations/type-distribution.ts
|
|
127
|
+
function computeTypeDistribution(entries) {
|
|
128
|
+
const counts = {};
|
|
129
|
+
for (const entry of entries) {
|
|
130
|
+
counts[entry.type] = (counts[entry.type] ?? 0) + 1;
|
|
131
|
+
}
|
|
132
|
+
const total = entries.length;
|
|
133
|
+
const percentages = {};
|
|
134
|
+
for (const [type, count] of Object.entries(counts)) {
|
|
135
|
+
percentages[type] = total > 0 ? Math.round(count / total * 1e4) / 100 : 0;
|
|
136
|
+
}
|
|
137
|
+
let dominant = "";
|
|
138
|
+
let maxCount = 0;
|
|
139
|
+
for (const [type, count] of Object.entries(counts)) {
|
|
140
|
+
if (count > maxCount) {
|
|
141
|
+
maxCount = count;
|
|
142
|
+
dominant = type;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return { counts, percentages, total, dominant };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ../dashboard/src/client/dashboard-client.ts
|
|
149
|
+
var DashboardClient = class extends MemoryClient {
|
|
150
|
+
async consolidationLog(limit = 10) {
|
|
151
|
+
return this.fetchApi(`/api/memory/consolidation-log?limit=${limit}`);
|
|
152
|
+
}
|
|
153
|
+
async listEntriesPaginated(filters = {}) {
|
|
154
|
+
const params = new URLSearchParams();
|
|
155
|
+
if (filters.page) params.set("page", String(filters.page));
|
|
156
|
+
if (filters.limit) params.set("limit", String(filters.limit));
|
|
157
|
+
if (filters.type) params.set("type", filters.type);
|
|
158
|
+
if (filters.agentId) params.set("agentId", filters.agentId);
|
|
159
|
+
if (filters.query) params.set("query", filters.query);
|
|
160
|
+
const qs = params.toString();
|
|
161
|
+
const raw = await this.fetchApi(`/api/memory/entries${qs ? `?${qs}` : ""}`);
|
|
162
|
+
return {
|
|
163
|
+
...raw,
|
|
164
|
+
totalPages: Math.ceil(raw.totalCount / raw.limit),
|
|
165
|
+
hasNextPage: raw.page * raw.limit < raw.totalCount,
|
|
166
|
+
hasPreviousPage: raw.page > 1
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
async graphRelationships(limit = 200) {
|
|
170
|
+
return this.fetchApi(
|
|
171
|
+
`/api/memory/graph/relationships?limit=${limit}`
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
async graphFull(limit = 50) {
|
|
175
|
+
const [nodesResponse, relsResponse] = await Promise.all([
|
|
176
|
+
this.fetchApi(
|
|
177
|
+
`/api/memory/graph/nodes?limit=${limit}`
|
|
178
|
+
),
|
|
179
|
+
this.graphRelationships(limit * 4).catch(() => ({
|
|
180
|
+
relationships: [],
|
|
181
|
+
totalCount: 0
|
|
182
|
+
}))
|
|
183
|
+
]);
|
|
184
|
+
return {
|
|
185
|
+
nodes: nodesResponse.nodes,
|
|
186
|
+
relationships: relsResponse.relationships
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
async fetchHealthRaw() {
|
|
190
|
+
return this.fetchApi("/health");
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// ../dashboard/src/graph/transform.ts
|
|
195
|
+
function transformGraphData(nodes, relationships, opts = {}) {
|
|
196
|
+
const { minWeight = 0, maxNodes } = opts;
|
|
197
|
+
const degreeMap = /* @__PURE__ */ new Map();
|
|
198
|
+
for (const rel of relationships) {
|
|
199
|
+
degreeMap.set(rel.sourceId, (degreeMap.get(rel.sourceId) ?? 0) + 1);
|
|
200
|
+
degreeMap.set(rel.targetId, (degreeMap.get(rel.targetId) ?? 0) + 1);
|
|
201
|
+
}
|
|
202
|
+
let vizNodes = nodes.map((node) => ({
|
|
203
|
+
id: node.id,
|
|
204
|
+
label: node.name,
|
|
205
|
+
type: node.type,
|
|
206
|
+
memoryCount: node.memoryEntryIds.length,
|
|
207
|
+
degree: degreeMap.get(node.id) ?? 0,
|
|
208
|
+
properties: node.properties
|
|
209
|
+
}));
|
|
210
|
+
if (maxNodes && vizNodes.length > maxNodes) {
|
|
211
|
+
vizNodes.sort((a, b) => b.degree - a.degree);
|
|
212
|
+
vizNodes = vizNodes.slice(0, maxNodes);
|
|
213
|
+
}
|
|
214
|
+
const nodeIdSet = new Set(vizNodes.map((n) => n.id));
|
|
215
|
+
const filteredRels = relationships.filter(
|
|
216
|
+
(rel) => nodeIdSet.has(rel.sourceId) && nodeIdSet.has(rel.targetId) && (rel.properties?.weight ?? 1) >= minWeight
|
|
217
|
+
);
|
|
218
|
+
const vizEdges = filteredRels.map((rel) => ({
|
|
219
|
+
id: rel.id,
|
|
220
|
+
source: rel.sourceId,
|
|
221
|
+
target: rel.targetId,
|
|
222
|
+
label: rel.type,
|
|
223
|
+
weight: rel.properties?.weight ?? 1
|
|
224
|
+
}));
|
|
225
|
+
const nodeTypes = {};
|
|
226
|
+
for (const node of vizNodes) {
|
|
227
|
+
nodeTypes[node.type] = (nodeTypes[node.type] ?? 0) + 1;
|
|
228
|
+
}
|
|
229
|
+
const edgeTypes = {};
|
|
230
|
+
for (const edge of vizEdges) {
|
|
231
|
+
edgeTypes[edge.label] = (edgeTypes[edge.label] ?? 0) + 1;
|
|
232
|
+
}
|
|
233
|
+
return {
|
|
234
|
+
nodes: vizNodes,
|
|
235
|
+
edges: vizEdges,
|
|
236
|
+
nodeCount: vizNodes.length,
|
|
237
|
+
edgeCount: vizEdges.length,
|
|
238
|
+
nodeTypes,
|
|
239
|
+
edgeTypes
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ../dashboard/src/graph/types.ts
|
|
244
|
+
function toGraphologyFormat(data) {
|
|
245
|
+
const nodes = data.nodes.map((node) => ({
|
|
246
|
+
key: node.id,
|
|
247
|
+
attributes: {
|
|
248
|
+
label: node.label,
|
|
249
|
+
type: node.type,
|
|
250
|
+
memoryCount: node.memoryCount,
|
|
251
|
+
degree: node.degree,
|
|
252
|
+
...node.properties
|
|
253
|
+
}
|
|
254
|
+
}));
|
|
255
|
+
const edges = data.edges.map((edge) => ({
|
|
256
|
+
key: edge.id,
|
|
257
|
+
source: edge.source,
|
|
258
|
+
target: edge.target,
|
|
259
|
+
attributes: {
|
|
260
|
+
label: edge.label,
|
|
261
|
+
weight: edge.weight
|
|
262
|
+
}
|
|
263
|
+
}));
|
|
264
|
+
return { nodes, edges };
|
|
265
|
+
}
|
|
266
|
+
function toD3ForceFormat(data) {
|
|
267
|
+
const nodes = data.nodes.map((node) => ({
|
|
268
|
+
id: node.id,
|
|
269
|
+
label: node.label,
|
|
270
|
+
type: node.type,
|
|
271
|
+
memoryCount: node.memoryCount,
|
|
272
|
+
degree: node.degree
|
|
273
|
+
}));
|
|
274
|
+
const links = data.edges.map((edge) => ({
|
|
275
|
+
source: edge.source,
|
|
276
|
+
target: edge.target,
|
|
277
|
+
label: edge.label,
|
|
278
|
+
weight: edge.weight
|
|
279
|
+
}));
|
|
280
|
+
return { nodes, links };
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ../dashboard/src/polling/poller.ts
|
|
284
|
+
var Poller = class {
|
|
285
|
+
fetcher;
|
|
286
|
+
options;
|
|
287
|
+
timer = null;
|
|
288
|
+
state = {
|
|
289
|
+
data: null,
|
|
290
|
+
error: null,
|
|
291
|
+
isLoading: false,
|
|
292
|
+
lastUpdated: null
|
|
293
|
+
};
|
|
294
|
+
updateListeners = [];
|
|
295
|
+
errorListeners = [];
|
|
296
|
+
constructor(fetcher, options) {
|
|
297
|
+
this.fetcher = fetcher;
|
|
298
|
+
this.options = {
|
|
299
|
+
intervalMs: options.intervalMs,
|
|
300
|
+
immediate: options.immediate ?? true
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
on(event, listener) {
|
|
304
|
+
if (event === "update") {
|
|
305
|
+
this.updateListeners.push(listener);
|
|
306
|
+
} else if (event === "error") {
|
|
307
|
+
this.errorListeners.push(listener);
|
|
308
|
+
}
|
|
309
|
+
return this;
|
|
310
|
+
}
|
|
311
|
+
off(event, listener) {
|
|
312
|
+
if (event === "update") {
|
|
313
|
+
this.updateListeners = this.updateListeners.filter((l) => l !== listener);
|
|
314
|
+
} else if (event === "error") {
|
|
315
|
+
this.errorListeners = this.errorListeners.filter((l) => l !== listener);
|
|
316
|
+
}
|
|
317
|
+
return this;
|
|
318
|
+
}
|
|
319
|
+
async start() {
|
|
320
|
+
if (this.timer) return;
|
|
321
|
+
if (this.options.immediate) {
|
|
322
|
+
await this.poll();
|
|
323
|
+
}
|
|
324
|
+
this.timer = setInterval(() => {
|
|
325
|
+
void this.poll();
|
|
326
|
+
}, this.options.intervalMs);
|
|
327
|
+
}
|
|
328
|
+
stop() {
|
|
329
|
+
if (this.timer) {
|
|
330
|
+
clearInterval(this.timer);
|
|
331
|
+
this.timer = null;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
getState() {
|
|
335
|
+
return { ...this.state };
|
|
336
|
+
}
|
|
337
|
+
isRunning() {
|
|
338
|
+
return this.timer !== null;
|
|
339
|
+
}
|
|
340
|
+
async poll() {
|
|
341
|
+
this.state = { ...this.state, isLoading: true };
|
|
342
|
+
this.emit("update", this.state);
|
|
343
|
+
try {
|
|
344
|
+
const data = await this.fetcher();
|
|
345
|
+
this.state = {
|
|
346
|
+
data,
|
|
347
|
+
error: null,
|
|
348
|
+
isLoading: false,
|
|
349
|
+
lastUpdated: /* @__PURE__ */ new Date()
|
|
350
|
+
};
|
|
351
|
+
this.emit("update", this.state);
|
|
352
|
+
} catch (err) {
|
|
353
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
354
|
+
this.state = { ...this.state, error, isLoading: false };
|
|
355
|
+
this.emit("update", this.state);
|
|
356
|
+
this.emit("error", error);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
emit(event, payload) {
|
|
360
|
+
if (event === "update") {
|
|
361
|
+
for (const listener of this.updateListeners) {
|
|
362
|
+
listener(payload);
|
|
363
|
+
}
|
|
364
|
+
} else if (event === "error") {
|
|
365
|
+
for (const listener of this.errorListeners) {
|
|
366
|
+
listener(payload);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
export {
|
|
373
|
+
analyzeConsolidationLog,
|
|
374
|
+
formatUptime,
|
|
375
|
+
formatBytes,
|
|
376
|
+
enrichHealth,
|
|
377
|
+
unreachableHealth,
|
|
378
|
+
computeMetrics,
|
|
379
|
+
computeTypeDistribution,
|
|
380
|
+
DashboardClient,
|
|
381
|
+
transformGraphData,
|
|
382
|
+
toGraphologyFormat,
|
|
383
|
+
toD3ForceFormat,
|
|
384
|
+
Poller
|
|
385
|
+
};
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// ../client/src/memory-client.ts
|
|
2
|
+
var MemoryServerError = class extends Error {
|
|
3
|
+
status;
|
|
4
|
+
constructor(message, status) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "MemoryServerError";
|
|
7
|
+
this.status = status;
|
|
8
|
+
}
|
|
9
|
+
/** True when the server returned HTTP 404 (not found). */
|
|
10
|
+
get isNotFound() {
|
|
11
|
+
return this.status === 404;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
var MemoryClient = class {
|
|
15
|
+
baseUrl;
|
|
16
|
+
constructor(memoryUrl) {
|
|
17
|
+
this.baseUrl = memoryUrl.replace(/\/$/, "");
|
|
18
|
+
}
|
|
19
|
+
/** Encode a path segment to prevent URL injection */
|
|
20
|
+
encodePathSegment(segment) {
|
|
21
|
+
return encodeURIComponent(segment);
|
|
22
|
+
}
|
|
23
|
+
async initialize() {
|
|
24
|
+
const response = await fetch(`${this.baseUrl}/health`);
|
|
25
|
+
if (!response.ok) {
|
|
26
|
+
throw new Error(`Memory server not reachable at ${this.baseUrl}: ${response.status}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
async store(entry) {
|
|
30
|
+
return this.fetchApi("/api/memory/ingest", {
|
|
31
|
+
method: "POST",
|
|
32
|
+
body: JSON.stringify({
|
|
33
|
+
content: entry.content,
|
|
34
|
+
type: entry.type,
|
|
35
|
+
metadata: entry.metadata,
|
|
36
|
+
agentId: entry.agentId,
|
|
37
|
+
sessionId: entry.sessionId
|
|
38
|
+
})
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
async search(params) {
|
|
42
|
+
const searchParams = new URLSearchParams({ query: params.query });
|
|
43
|
+
if (params.limit) searchParams.set("limit", String(params.limit));
|
|
44
|
+
if (params.type) searchParams.set("type", params.type);
|
|
45
|
+
if (params.agentId) searchParams.set("agentId", params.agentId);
|
|
46
|
+
if (params.strategy) searchParams.set("strategy", params.strategy);
|
|
47
|
+
return this.fetchApi(`/api/memory/search?${searchParams}`);
|
|
48
|
+
}
|
|
49
|
+
async get(id) {
|
|
50
|
+
try {
|
|
51
|
+
return await this.fetchApi(`/api/memory/entries/${this.encodePathSegment(id)}`);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
if (error instanceof MemoryServerError && error.isNotFound) return null;
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async delete(id) {
|
|
58
|
+
try {
|
|
59
|
+
await this.fetchApi(`/api/memory/entries/${this.encodePathSegment(id)}`, {
|
|
60
|
+
method: "DELETE"
|
|
61
|
+
});
|
|
62
|
+
return true;
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if (error instanceof MemoryServerError && error.isNotFound) return false;
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async clearSession(sessionId) {
|
|
69
|
+
const result = await this.fetchApi(
|
|
70
|
+
`/api/memory/sessions/${this.encodePathSegment(sessionId)}`,
|
|
71
|
+
{ method: "DELETE" }
|
|
72
|
+
);
|
|
73
|
+
return result.cleared;
|
|
74
|
+
}
|
|
75
|
+
async stats() {
|
|
76
|
+
const stats = await this.fetchApi("/api/memory/stats");
|
|
77
|
+
return { ...stats, connected: true };
|
|
78
|
+
}
|
|
79
|
+
async shutdown() {
|
|
80
|
+
}
|
|
81
|
+
async list(params = {}) {
|
|
82
|
+
const searchParams = new URLSearchParams();
|
|
83
|
+
if (params.page != null) searchParams.set("page", String(params.page));
|
|
84
|
+
if (params.limit != null) searchParams.set("limit", String(params.limit));
|
|
85
|
+
if (params.type) searchParams.set("type", params.type);
|
|
86
|
+
if (params.agentId) searchParams.set("agentId", params.agentId);
|
|
87
|
+
const qs = searchParams.toString();
|
|
88
|
+
return this.fetchApi(`/api/memory/entries${qs ? `?${qs}` : ""}`);
|
|
89
|
+
}
|
|
90
|
+
// --- Additional endpoints ---
|
|
91
|
+
async ingestFile(file) {
|
|
92
|
+
const formData = new FormData();
|
|
93
|
+
formData.append("file", file);
|
|
94
|
+
const res = await fetch(`${this.baseUrl}/api/memory/ingest/file`, {
|
|
95
|
+
method: "POST",
|
|
96
|
+
body: formData
|
|
97
|
+
});
|
|
98
|
+
const body = await res.json();
|
|
99
|
+
if (!body.success || body.data === void 0) {
|
|
100
|
+
throw new Error(body.error ?? `Memory server error: ${res.status}`);
|
|
101
|
+
}
|
|
102
|
+
return body.data;
|
|
103
|
+
}
|
|
104
|
+
/** @deprecated Use {@link list} instead. Kept for backwards compatibility. */
|
|
105
|
+
async listEntries(params) {
|
|
106
|
+
const result = await this.list(params);
|
|
107
|
+
return result.entries;
|
|
108
|
+
}
|
|
109
|
+
async graphNodes() {
|
|
110
|
+
const result = await this.fetchApi(
|
|
111
|
+
"/api/memory/graph/nodes"
|
|
112
|
+
);
|
|
113
|
+
return result.nodes;
|
|
114
|
+
}
|
|
115
|
+
async graphEdges() {
|
|
116
|
+
return this.fetchApi(
|
|
117
|
+
"/api/memory/graph/edges"
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
async graphQuery(query) {
|
|
121
|
+
return this.fetchApi("/api/memory/graph/query", {
|
|
122
|
+
method: "POST",
|
|
123
|
+
body: JSON.stringify(query)
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
// --- ExtendedMemoryInterface methods ---
|
|
127
|
+
async consolidate() {
|
|
128
|
+
return this.fetchApi("/api/memory/consolidate", {
|
|
129
|
+
method: "POST"
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
async forget(id, reason) {
|
|
133
|
+
try {
|
|
134
|
+
await this.fetchApi(`/api/memory/forget/${this.encodePathSegment(id)}`, {
|
|
135
|
+
method: "POST",
|
|
136
|
+
body: JSON.stringify({ reason })
|
|
137
|
+
});
|
|
138
|
+
return true;
|
|
139
|
+
} catch (error) {
|
|
140
|
+
if (error instanceof MemoryServerError && error.isNotFound) return false;
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async summarizeSession(sessionId) {
|
|
145
|
+
try {
|
|
146
|
+
return await this.fetchApi(
|
|
147
|
+
`/api/memory/sessions/${this.encodePathSegment(sessionId)}/summarize`,
|
|
148
|
+
{ method: "POST" }
|
|
149
|
+
);
|
|
150
|
+
} catch (error) {
|
|
151
|
+
if (error instanceof MemoryServerError && error.isNotFound) return null;
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
async runDecay() {
|
|
156
|
+
const result = await this.fetchApi("/api/memory/decay", {
|
|
157
|
+
method: "POST"
|
|
158
|
+
});
|
|
159
|
+
return result.archived;
|
|
160
|
+
}
|
|
161
|
+
async reindex() {
|
|
162
|
+
await this.fetchApi("/api/memory/reindex", { method: "POST" });
|
|
163
|
+
}
|
|
164
|
+
async deleteBySource(source) {
|
|
165
|
+
const result = await this.fetchApi(
|
|
166
|
+
`/api/memory/source/${this.encodePathSegment(source)}`,
|
|
167
|
+
{ method: "DELETE" }
|
|
168
|
+
);
|
|
169
|
+
return result.deleted;
|
|
170
|
+
}
|
|
171
|
+
async fetchApi(path, options) {
|
|
172
|
+
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
173
|
+
...options,
|
|
174
|
+
headers: {
|
|
175
|
+
"Content-Type": "application/json",
|
|
176
|
+
...options?.headers
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
const body = await res.json();
|
|
180
|
+
if (!body.success || body.data === void 0) {
|
|
181
|
+
throw new MemoryServerError(body.error ?? `Memory server error: ${res.status}`, res.status);
|
|
182
|
+
}
|
|
183
|
+
return body.data;
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
export {
|
|
188
|
+
MemoryServerError,
|
|
189
|
+
MemoryClient
|
|
190
|
+
};
|