@cogitator-ai/memory 0.6.14 → 0.6.20
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/dist/adapters/index.d.ts +2 -2
- package/dist/adapters/index.js +8 -8
- package/dist/adapters/memory-embedding.js +1 -1
- package/dist/adapters/memory.d.ts +1 -1
- package/dist/adapters/memory.js +1 -1
- package/dist/adapters/mongodb.d.ts +1 -1
- package/dist/adapters/mongodb.js +1 -1
- package/dist/adapters/postgres.d.ts +1 -1
- package/dist/adapters/postgres.js +1 -1
- package/dist/adapters/qdrant.d.ts.map +1 -1
- package/dist/adapters/qdrant.js.map +1 -1
- package/dist/adapters/redis.d.ts +1 -1
- package/dist/adapters/redis.js +1 -1
- package/dist/adapters/sqlite.d.ts +1 -1
- package/dist/adapters/sqlite.js +1 -1
- package/dist/compaction.d.ts +16 -0
- package/dist/compaction.d.ts.map +1 -0
- package/dist/compaction.js +89 -0
- package/dist/compaction.js.map +1 -0
- package/dist/context-builder.d.ts +2 -0
- package/dist/context-builder.d.ts.map +1 -1
- package/dist/context-builder.js +40 -3
- package/dist/context-builder.js.map +1 -1
- package/dist/core-facts.d.ts +43 -0
- package/dist/core-facts.d.ts.map +1 -0
- package/dist/core-facts.js +99 -0
- package/dist/core-facts.js.map +1 -0
- package/dist/embedding/factory.js +3 -3
- package/dist/embedding/index.d.ts +4 -4
- package/dist/embedding/index.js +4 -4
- package/dist/index.d.ts +19 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -13
- package/dist/index.js.map +1 -1
- package/dist/knowledge-graph/entity-extractor.js +1 -1
- package/dist/knowledge-graph/graph-adapter.d.ts.map +1 -1
- package/dist/knowledge-graph/graph-adapter.js +1 -1
- package/dist/knowledge-graph/graph-adapter.js.map +1 -1
- package/dist/knowledge-graph/index.d.ts +10 -8
- package/dist/knowledge-graph/index.d.ts.map +1 -1
- package/dist/knowledge-graph/index.js +6 -5
- package/dist/knowledge-graph/index.js.map +1 -1
- package/dist/knowledge-graph/inference-engine.d.ts.map +1 -1
- package/dist/knowledge-graph/inference-engine.js +23 -6
- package/dist/knowledge-graph/inference-engine.js.map +1 -1
- package/dist/knowledge-graph/schema.d.ts +15 -15
- package/dist/knowledge-graph/sqlite-graph-adapter.d.ts +48 -0
- package/dist/knowledge-graph/sqlite-graph-adapter.d.ts.map +1 -0
- package/dist/knowledge-graph/sqlite-graph-adapter.js +694 -0
- package/dist/knowledge-graph/sqlite-graph-adapter.js.map +1 -0
- package/dist/schema.d.ts +4 -4
- package/dist/search/bm25.d.ts +1 -1
- package/dist/search/bm25.js +1 -1
- package/dist/search/hybrid-search.js +2 -2
- package/dist/search/index.d.ts +4 -4
- package/dist/search/index.js +4 -4
- package/dist/session-manager.d.ts +19 -0
- package/dist/session-manager.d.ts.map +1 -0
- package/dist/session-manager.js +167 -0
- package/dist/session-manager.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,694 @@
|
|
|
1
|
+
import { nanoid } from 'nanoid';
|
|
2
|
+
export class SQLiteGraphAdapter {
|
|
3
|
+
db = null;
|
|
4
|
+
path;
|
|
5
|
+
walMode;
|
|
6
|
+
initialized = false;
|
|
7
|
+
constructor(config) {
|
|
8
|
+
this.path = config.path;
|
|
9
|
+
this.walMode = config.walMode ?? true;
|
|
10
|
+
}
|
|
11
|
+
async initialize() {
|
|
12
|
+
if (this.initialized)
|
|
13
|
+
return;
|
|
14
|
+
if (!this.db) {
|
|
15
|
+
let DatabaseCtor;
|
|
16
|
+
const betterSqlite = await import('better-sqlite3');
|
|
17
|
+
DatabaseCtor = betterSqlite.default;
|
|
18
|
+
this.db = new DatabaseCtor(this.path);
|
|
19
|
+
this.db.pragma('foreign_keys = ON');
|
|
20
|
+
if (this.walMode && this.path !== ':memory:') {
|
|
21
|
+
this.db.pragma('journal_mode = WAL');
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
this.db.exec(`
|
|
25
|
+
CREATE TABLE IF NOT EXISTS graph_nodes (
|
|
26
|
+
id TEXT PRIMARY KEY,
|
|
27
|
+
agent_id TEXT NOT NULL,
|
|
28
|
+
name TEXT NOT NULL,
|
|
29
|
+
type TEXT NOT NULL,
|
|
30
|
+
aliases TEXT DEFAULT '[]',
|
|
31
|
+
description TEXT,
|
|
32
|
+
properties TEXT DEFAULT '{}',
|
|
33
|
+
embedding TEXT,
|
|
34
|
+
confidence REAL DEFAULT 1.0,
|
|
35
|
+
source TEXT NOT NULL,
|
|
36
|
+
metadata TEXT DEFAULT '{}',
|
|
37
|
+
access_count INTEGER DEFAULT 0,
|
|
38
|
+
created_at TEXT NOT NULL,
|
|
39
|
+
updated_at TEXT NOT NULL,
|
|
40
|
+
last_accessed_at TEXT NOT NULL
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
CREATE TABLE IF NOT EXISTS graph_edges (
|
|
44
|
+
id TEXT PRIMARY KEY,
|
|
45
|
+
source_node_id TEXT NOT NULL REFERENCES graph_nodes(id) ON DELETE CASCADE,
|
|
46
|
+
target_node_id TEXT NOT NULL REFERENCES graph_nodes(id) ON DELETE CASCADE,
|
|
47
|
+
type TEXT NOT NULL,
|
|
48
|
+
label TEXT,
|
|
49
|
+
weight REAL DEFAULT 1.0,
|
|
50
|
+
properties TEXT DEFAULT '{}',
|
|
51
|
+
confidence REAL DEFAULT 1.0,
|
|
52
|
+
source TEXT NOT NULL,
|
|
53
|
+
bidirectional INTEGER DEFAULT 0,
|
|
54
|
+
valid_from TEXT,
|
|
55
|
+
valid_until TEXT,
|
|
56
|
+
metadata TEXT DEFAULT '{}',
|
|
57
|
+
agent_id TEXT NOT NULL,
|
|
58
|
+
created_at TEXT NOT NULL,
|
|
59
|
+
updated_at TEXT NOT NULL
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
CREATE INDEX IF NOT EXISTS idx_graph_nodes_agent_id ON graph_nodes(agent_id);
|
|
63
|
+
CREATE INDEX IF NOT EXISTS idx_graph_nodes_type ON graph_nodes(agent_id, type);
|
|
64
|
+
CREATE INDEX IF NOT EXISTS idx_graph_nodes_name ON graph_nodes(agent_id, name);
|
|
65
|
+
CREATE INDEX IF NOT EXISTS idx_graph_edges_agent_id ON graph_edges(agent_id);
|
|
66
|
+
CREATE INDEX IF NOT EXISTS idx_graph_edges_source ON graph_edges(source_node_id);
|
|
67
|
+
CREATE INDEX IF NOT EXISTS idx_graph_edges_target ON graph_edges(target_node_id);
|
|
68
|
+
CREATE INDEX IF NOT EXISTS idx_graph_edges_type ON graph_edges(agent_id, type);
|
|
69
|
+
`);
|
|
70
|
+
this.initialized = true;
|
|
71
|
+
}
|
|
72
|
+
async close() {
|
|
73
|
+
if (this.db) {
|
|
74
|
+
this.db.close();
|
|
75
|
+
this.db = null;
|
|
76
|
+
this.initialized = false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
ensureDb() {
|
|
80
|
+
if (!this.db)
|
|
81
|
+
throw new Error('Not initialized');
|
|
82
|
+
return this.db;
|
|
83
|
+
}
|
|
84
|
+
success(data) {
|
|
85
|
+
return { success: true, data };
|
|
86
|
+
}
|
|
87
|
+
failure(error) {
|
|
88
|
+
return { success: false, error };
|
|
89
|
+
}
|
|
90
|
+
generateId(prefix) {
|
|
91
|
+
return `${prefix}_${nanoid(12)}`;
|
|
92
|
+
}
|
|
93
|
+
rowToNode(row, includeEmbedding = false) {
|
|
94
|
+
return {
|
|
95
|
+
id: row.id,
|
|
96
|
+
agentId: row.agent_id,
|
|
97
|
+
type: row.type,
|
|
98
|
+
name: row.name,
|
|
99
|
+
aliases: JSON.parse(row.aliases),
|
|
100
|
+
description: row.description ?? undefined,
|
|
101
|
+
properties: JSON.parse(row.properties),
|
|
102
|
+
embedding: includeEmbedding && row.embedding ? JSON.parse(row.embedding) : undefined,
|
|
103
|
+
confidence: row.confidence,
|
|
104
|
+
source: row.source,
|
|
105
|
+
createdAt: new Date(row.created_at),
|
|
106
|
+
updatedAt: new Date(row.updated_at),
|
|
107
|
+
lastAccessedAt: new Date(row.last_accessed_at),
|
|
108
|
+
accessCount: row.access_count,
|
|
109
|
+
metadata: JSON.parse(row.metadata),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
rowToEdge(row) {
|
|
113
|
+
return {
|
|
114
|
+
id: row.id,
|
|
115
|
+
agentId: row.agent_id,
|
|
116
|
+
sourceNodeId: row.source_node_id,
|
|
117
|
+
targetNodeId: row.target_node_id,
|
|
118
|
+
type: row.type,
|
|
119
|
+
label: row.label ?? undefined,
|
|
120
|
+
weight: row.weight,
|
|
121
|
+
bidirectional: row.bidirectional === 1,
|
|
122
|
+
properties: JSON.parse(row.properties),
|
|
123
|
+
confidence: row.confidence,
|
|
124
|
+
source: row.source,
|
|
125
|
+
createdAt: new Date(row.created_at),
|
|
126
|
+
updatedAt: new Date(row.updated_at),
|
|
127
|
+
validFrom: row.valid_from ? new Date(row.valid_from) : undefined,
|
|
128
|
+
validUntil: row.valid_until ? new Date(row.valid_until) : undefined,
|
|
129
|
+
metadata: JSON.parse(row.metadata),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
neighborRowToEdge(row) {
|
|
133
|
+
return {
|
|
134
|
+
id: row.edge_id,
|
|
135
|
+
agentId: row.edge_agent_id,
|
|
136
|
+
sourceNodeId: row.edge_source_node_id,
|
|
137
|
+
targetNodeId: row.edge_target_node_id,
|
|
138
|
+
type: row.edge_type,
|
|
139
|
+
label: row.edge_label ?? undefined,
|
|
140
|
+
weight: row.edge_weight,
|
|
141
|
+
bidirectional: row.edge_bidirectional === 1,
|
|
142
|
+
properties: JSON.parse(row.edge_properties),
|
|
143
|
+
confidence: row.edge_confidence,
|
|
144
|
+
source: row.edge_source,
|
|
145
|
+
createdAt: new Date(row.edge_created_at),
|
|
146
|
+
updatedAt: new Date(row.edge_updated_at),
|
|
147
|
+
validFrom: row.edge_valid_from ? new Date(row.edge_valid_from) : undefined,
|
|
148
|
+
validUntil: row.edge_valid_until ? new Date(row.edge_valid_until) : undefined,
|
|
149
|
+
metadata: JSON.parse(row.edge_metadata),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
cosineSimilarity(a, b) {
|
|
153
|
+
let dot = 0;
|
|
154
|
+
let normA = 0;
|
|
155
|
+
let normB = 0;
|
|
156
|
+
for (let i = 0; i < a.length; i++) {
|
|
157
|
+
dot += a[i] * b[i];
|
|
158
|
+
normA += a[i] * a[i];
|
|
159
|
+
normB += b[i] * b[i];
|
|
160
|
+
}
|
|
161
|
+
const denom = Math.sqrt(normA) * Math.sqrt(normB);
|
|
162
|
+
if (denom === 0)
|
|
163
|
+
return 0;
|
|
164
|
+
return dot / denom;
|
|
165
|
+
}
|
|
166
|
+
async addNode(node) {
|
|
167
|
+
await this.initialize();
|
|
168
|
+
const db = this.ensureDb();
|
|
169
|
+
const id = this.generateId('node');
|
|
170
|
+
const now = new Date();
|
|
171
|
+
const nowStr = now.toISOString();
|
|
172
|
+
db.prepare(`INSERT INTO graph_nodes
|
|
173
|
+
(id, agent_id, type, name, aliases, description, properties, embedding, confidence, source, metadata, access_count, created_at, updated_at, last_accessed_at)
|
|
174
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?, ?)`).run(id, node.agentId, node.type, node.name, JSON.stringify(node.aliases), node.description ?? null, JSON.stringify(node.properties), node.embedding ? JSON.stringify(node.embedding) : null, node.confidence, node.source, JSON.stringify(node.metadata ?? {}), nowStr, nowStr, nowStr);
|
|
175
|
+
return this.success({
|
|
176
|
+
...node,
|
|
177
|
+
id,
|
|
178
|
+
createdAt: now,
|
|
179
|
+
updatedAt: now,
|
|
180
|
+
lastAccessedAt: now,
|
|
181
|
+
accessCount: 0,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
async getNode(nodeId) {
|
|
185
|
+
await this.initialize();
|
|
186
|
+
const db = this.ensureDb();
|
|
187
|
+
db.prepare(`UPDATE graph_nodes SET last_accessed_at = ?, access_count = access_count + 1 WHERE id = ?`).run(new Date().toISOString(), nodeId);
|
|
188
|
+
const row = db.prepare(`SELECT * FROM graph_nodes WHERE id = ?`).get(nodeId);
|
|
189
|
+
if (!row)
|
|
190
|
+
return this.success(null);
|
|
191
|
+
return this.success(this.rowToNode(row));
|
|
192
|
+
}
|
|
193
|
+
async getNodeByName(agentId, name) {
|
|
194
|
+
await this.initialize();
|
|
195
|
+
const db = this.ensureDb();
|
|
196
|
+
const row = db
|
|
197
|
+
.prepare(`SELECT * FROM graph_nodes WHERE agent_id = ? AND name = ?`)
|
|
198
|
+
.get(agentId, name);
|
|
199
|
+
if (row)
|
|
200
|
+
return this.success(this.rowToNode(row));
|
|
201
|
+
const allRows = db
|
|
202
|
+
.prepare(`SELECT * FROM graph_nodes WHERE agent_id = ?`)
|
|
203
|
+
.all(agentId);
|
|
204
|
+
for (const r of allRows) {
|
|
205
|
+
const aliases = JSON.parse(r.aliases);
|
|
206
|
+
if (aliases.includes(name)) {
|
|
207
|
+
return this.success(this.rowToNode(r));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return this.success(null);
|
|
211
|
+
}
|
|
212
|
+
async updateNode(nodeId, updates) {
|
|
213
|
+
await this.initialize();
|
|
214
|
+
const db = this.ensureDb();
|
|
215
|
+
const setClauses = ['updated_at = ?'];
|
|
216
|
+
const params = [new Date().toISOString()];
|
|
217
|
+
if (updates.name !== undefined) {
|
|
218
|
+
setClauses.push('name = ?');
|
|
219
|
+
params.push(updates.name);
|
|
220
|
+
}
|
|
221
|
+
if (updates.aliases !== undefined) {
|
|
222
|
+
setClauses.push('aliases = ?');
|
|
223
|
+
params.push(JSON.stringify(updates.aliases));
|
|
224
|
+
}
|
|
225
|
+
if (updates.description !== undefined) {
|
|
226
|
+
setClauses.push('description = ?');
|
|
227
|
+
params.push(updates.description);
|
|
228
|
+
}
|
|
229
|
+
if (updates.properties !== undefined) {
|
|
230
|
+
setClauses.push('properties = ?');
|
|
231
|
+
params.push(JSON.stringify(updates.properties));
|
|
232
|
+
}
|
|
233
|
+
if (updates.confidence !== undefined) {
|
|
234
|
+
setClauses.push('confidence = ?');
|
|
235
|
+
params.push(updates.confidence);
|
|
236
|
+
}
|
|
237
|
+
if (updates.metadata !== undefined) {
|
|
238
|
+
setClauses.push('metadata = ?');
|
|
239
|
+
params.push(JSON.stringify(updates.metadata));
|
|
240
|
+
}
|
|
241
|
+
if (updates.embedding !== undefined) {
|
|
242
|
+
setClauses.push('embedding = ?');
|
|
243
|
+
params.push(JSON.stringify(updates.embedding));
|
|
244
|
+
}
|
|
245
|
+
params.push(nodeId);
|
|
246
|
+
const result = db
|
|
247
|
+
.prepare(`UPDATE graph_nodes SET ${setClauses.join(', ')} WHERE id = ?`)
|
|
248
|
+
.run(...params);
|
|
249
|
+
if (result.changes === 0) {
|
|
250
|
+
return this.failure(`Node not found: ${nodeId}`);
|
|
251
|
+
}
|
|
252
|
+
const row = db.prepare(`SELECT * FROM graph_nodes WHERE id = ?`).get(nodeId);
|
|
253
|
+
return this.success(this.rowToNode(row));
|
|
254
|
+
}
|
|
255
|
+
async deleteNode(nodeId) {
|
|
256
|
+
await this.initialize();
|
|
257
|
+
const db = this.ensureDb();
|
|
258
|
+
db.prepare(`DELETE FROM graph_nodes WHERE id = ?`).run(nodeId);
|
|
259
|
+
return this.success(undefined);
|
|
260
|
+
}
|
|
261
|
+
async queryNodes(query) {
|
|
262
|
+
await this.initialize();
|
|
263
|
+
const db = this.ensureDb();
|
|
264
|
+
let sql = `SELECT * FROM graph_nodes WHERE agent_id = ?`;
|
|
265
|
+
const params = [query.agentId];
|
|
266
|
+
if (query.types && query.types.length > 0) {
|
|
267
|
+
const placeholders = query.types.map(() => '?').join(', ');
|
|
268
|
+
sql += ` AND type IN (${placeholders})`;
|
|
269
|
+
params.push(...query.types);
|
|
270
|
+
}
|
|
271
|
+
if (query.namePattern) {
|
|
272
|
+
sql += ` AND name LIKE ?`;
|
|
273
|
+
params.push(`%${query.namePattern.replace(/[%_\\]/g, '\\$&')}%`);
|
|
274
|
+
}
|
|
275
|
+
if (query.minConfidence !== undefined) {
|
|
276
|
+
sql += ` AND confidence >= ?`;
|
|
277
|
+
params.push(query.minConfidence);
|
|
278
|
+
}
|
|
279
|
+
sql += ' ORDER BY access_count DESC, updated_at DESC';
|
|
280
|
+
if (query.limit) {
|
|
281
|
+
sql += ` LIMIT ?`;
|
|
282
|
+
params.push(query.limit);
|
|
283
|
+
}
|
|
284
|
+
const rows = db.prepare(sql).all(...params);
|
|
285
|
+
return this.success(rows.map((row) => this.rowToNode(row, query.includeEmbedding)));
|
|
286
|
+
}
|
|
287
|
+
async searchNodesSemantic(options) {
|
|
288
|
+
await this.initialize();
|
|
289
|
+
const db = this.ensureDb();
|
|
290
|
+
if (!options.vector) {
|
|
291
|
+
return this.failure('searchNodesSemantic requires vector');
|
|
292
|
+
}
|
|
293
|
+
const limit = options.limit ?? 10;
|
|
294
|
+
const threshold = options.threshold ?? 0.7;
|
|
295
|
+
let sql = `SELECT * FROM graph_nodes WHERE agent_id = ? AND embedding IS NOT NULL`;
|
|
296
|
+
const params = [options.agentId];
|
|
297
|
+
if (options.entityTypes && options.entityTypes.length > 0) {
|
|
298
|
+
const placeholders = options.entityTypes.map(() => '?').join(', ');
|
|
299
|
+
sql += ` AND type IN (${placeholders})`;
|
|
300
|
+
params.push(...options.entityTypes);
|
|
301
|
+
}
|
|
302
|
+
const rows = db.prepare(sql).all(...params);
|
|
303
|
+
const scored = [];
|
|
304
|
+
for (const row of rows) {
|
|
305
|
+
if (!row.embedding)
|
|
306
|
+
continue;
|
|
307
|
+
const emb = JSON.parse(row.embedding);
|
|
308
|
+
const score = this.cosineSimilarity(options.vector, emb);
|
|
309
|
+
if (score >= threshold) {
|
|
310
|
+
scored.push({ ...this.rowToNode(row), score });
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
scored.sort((a, b) => b.score - a.score);
|
|
314
|
+
return this.success(scored.slice(0, limit));
|
|
315
|
+
}
|
|
316
|
+
async addEdge(edge) {
|
|
317
|
+
await this.initialize();
|
|
318
|
+
const db = this.ensureDb();
|
|
319
|
+
const id = this.generateId('edge');
|
|
320
|
+
const now = new Date();
|
|
321
|
+
const nowStr = now.toISOString();
|
|
322
|
+
db.prepare(`INSERT INTO graph_edges
|
|
323
|
+
(id, agent_id, source_node_id, target_node_id, type, label, weight, bidirectional, properties, confidence, source, valid_from, valid_until, metadata, created_at, updated_at)
|
|
324
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, edge.agentId, edge.sourceNodeId, edge.targetNodeId, edge.type, edge.label ?? null, edge.weight, edge.bidirectional ? 1 : 0, JSON.stringify(edge.properties), edge.confidence, edge.source, edge.validFrom?.toISOString() ?? null, edge.validUntil?.toISOString() ?? null, JSON.stringify(edge.metadata ?? {}), nowStr, nowStr);
|
|
325
|
+
return this.success({
|
|
326
|
+
...edge,
|
|
327
|
+
id,
|
|
328
|
+
createdAt: now,
|
|
329
|
+
updatedAt: now,
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
async getEdge(edgeId) {
|
|
333
|
+
await this.initialize();
|
|
334
|
+
const db = this.ensureDb();
|
|
335
|
+
const row = db.prepare(`SELECT * FROM graph_edges WHERE id = ?`).get(edgeId);
|
|
336
|
+
if (!row)
|
|
337
|
+
return this.success(null);
|
|
338
|
+
return this.success(this.rowToEdge(row));
|
|
339
|
+
}
|
|
340
|
+
async getEdgesBetween(sourceNodeId, targetNodeId) {
|
|
341
|
+
await this.initialize();
|
|
342
|
+
const db = this.ensureDb();
|
|
343
|
+
const rows = db
|
|
344
|
+
.prepare(`SELECT * FROM graph_edges
|
|
345
|
+
WHERE (source_node_id = ? AND target_node_id = ?)
|
|
346
|
+
OR (bidirectional = 1 AND source_node_id = ? AND target_node_id = ?)`)
|
|
347
|
+
.all(sourceNodeId, targetNodeId, targetNodeId, sourceNodeId);
|
|
348
|
+
return this.success(rows.map((row) => this.rowToEdge(row)));
|
|
349
|
+
}
|
|
350
|
+
async updateEdge(edgeId, updates) {
|
|
351
|
+
await this.initialize();
|
|
352
|
+
const db = this.ensureDb();
|
|
353
|
+
const setClauses = ['updated_at = ?'];
|
|
354
|
+
const params = [new Date().toISOString()];
|
|
355
|
+
if (updates.weight !== undefined) {
|
|
356
|
+
setClauses.push('weight = ?');
|
|
357
|
+
params.push(updates.weight);
|
|
358
|
+
}
|
|
359
|
+
if (updates.label !== undefined) {
|
|
360
|
+
setClauses.push('label = ?');
|
|
361
|
+
params.push(updates.label);
|
|
362
|
+
}
|
|
363
|
+
if (updates.properties !== undefined) {
|
|
364
|
+
setClauses.push('properties = ?');
|
|
365
|
+
params.push(JSON.stringify(updates.properties));
|
|
366
|
+
}
|
|
367
|
+
if (updates.confidence !== undefined) {
|
|
368
|
+
setClauses.push('confidence = ?');
|
|
369
|
+
params.push(updates.confidence);
|
|
370
|
+
}
|
|
371
|
+
if (updates.validFrom !== undefined) {
|
|
372
|
+
setClauses.push('valid_from = ?');
|
|
373
|
+
params.push(updates.validFrom.toISOString());
|
|
374
|
+
}
|
|
375
|
+
if (updates.validUntil !== undefined) {
|
|
376
|
+
setClauses.push('valid_until = ?');
|
|
377
|
+
params.push(updates.validUntil.toISOString());
|
|
378
|
+
}
|
|
379
|
+
if (updates.metadata !== undefined) {
|
|
380
|
+
setClauses.push('metadata = ?');
|
|
381
|
+
params.push(JSON.stringify(updates.metadata));
|
|
382
|
+
}
|
|
383
|
+
params.push(edgeId);
|
|
384
|
+
const result = db
|
|
385
|
+
.prepare(`UPDATE graph_edges SET ${setClauses.join(', ')} WHERE id = ?`)
|
|
386
|
+
.run(...params);
|
|
387
|
+
if (result.changes === 0) {
|
|
388
|
+
return this.failure(`Edge not found: ${edgeId}`);
|
|
389
|
+
}
|
|
390
|
+
const row = db.prepare(`SELECT * FROM graph_edges WHERE id = ?`).get(edgeId);
|
|
391
|
+
return this.success(this.rowToEdge(row));
|
|
392
|
+
}
|
|
393
|
+
async deleteEdge(edgeId) {
|
|
394
|
+
await this.initialize();
|
|
395
|
+
const db = this.ensureDb();
|
|
396
|
+
db.prepare(`DELETE FROM graph_edges WHERE id = ?`).run(edgeId);
|
|
397
|
+
return this.success(undefined);
|
|
398
|
+
}
|
|
399
|
+
async queryEdges(query) {
|
|
400
|
+
await this.initialize();
|
|
401
|
+
const db = this.ensureDb();
|
|
402
|
+
let sql = `SELECT * FROM graph_edges WHERE agent_id = ?`;
|
|
403
|
+
const params = [query.agentId];
|
|
404
|
+
if (query.sourceNodeId) {
|
|
405
|
+
sql += ` AND source_node_id = ?`;
|
|
406
|
+
params.push(query.sourceNodeId);
|
|
407
|
+
}
|
|
408
|
+
if (query.targetNodeId) {
|
|
409
|
+
sql += ` AND target_node_id = ?`;
|
|
410
|
+
params.push(query.targetNodeId);
|
|
411
|
+
}
|
|
412
|
+
if (query.types && query.types.length > 0) {
|
|
413
|
+
const placeholders = query.types.map(() => '?').join(', ');
|
|
414
|
+
sql += ` AND type IN (${placeholders})`;
|
|
415
|
+
params.push(...query.types);
|
|
416
|
+
}
|
|
417
|
+
if (query.minWeight !== undefined) {
|
|
418
|
+
sql += ` AND weight >= ?`;
|
|
419
|
+
params.push(query.minWeight);
|
|
420
|
+
}
|
|
421
|
+
if (query.minConfidence !== undefined) {
|
|
422
|
+
sql += ` AND confidence >= ?`;
|
|
423
|
+
params.push(query.minConfidence);
|
|
424
|
+
}
|
|
425
|
+
if (query.bidirectionalOnly) {
|
|
426
|
+
sql += ' AND bidirectional = 1';
|
|
427
|
+
}
|
|
428
|
+
sql += ' ORDER BY weight DESC, confidence DESC';
|
|
429
|
+
if (query.limit) {
|
|
430
|
+
sql += ` LIMIT ?`;
|
|
431
|
+
params.push(query.limit);
|
|
432
|
+
}
|
|
433
|
+
const rows = db.prepare(sql).all(...params);
|
|
434
|
+
return this.success(rows.map((row) => this.rowToEdge(row)));
|
|
435
|
+
}
|
|
436
|
+
async traverse(options) {
|
|
437
|
+
await this.initialize();
|
|
438
|
+
const visited = new Set();
|
|
439
|
+
const visitedEdges = new Set();
|
|
440
|
+
const paths = [];
|
|
441
|
+
const allNodes = [];
|
|
442
|
+
const allEdges = [];
|
|
443
|
+
const startNodeResult = await this.getNode(options.startNodeId);
|
|
444
|
+
if (!startNodeResult.success || !startNodeResult.data) {
|
|
445
|
+
return this.failure(`Start node not found: ${options.startNodeId}`);
|
|
446
|
+
}
|
|
447
|
+
const startNode = startNodeResult.data;
|
|
448
|
+
visited.add(startNode.id);
|
|
449
|
+
allNodes.push(startNode);
|
|
450
|
+
await this.traverseRecursive(startNode, [], [], 0, options, visited, visitedEdges, paths, allNodes, allEdges);
|
|
451
|
+
let actualMaxDepth = 0;
|
|
452
|
+
for (const p of paths) {
|
|
453
|
+
if (p.length > actualMaxDepth)
|
|
454
|
+
actualMaxDepth = p.length;
|
|
455
|
+
}
|
|
456
|
+
return this.success({
|
|
457
|
+
paths,
|
|
458
|
+
visitedNodes: allNodes,
|
|
459
|
+
visitedEdges: allEdges,
|
|
460
|
+
depth: actualMaxDepth,
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
async traverseRecursive(currentNode, pathNodes, pathEdges, currentDepth, options, visited, visitedEdges, paths, allNodes, allEdges) {
|
|
464
|
+
if (currentDepth >= options.maxDepth) {
|
|
465
|
+
if (pathNodes.length > 0) {
|
|
466
|
+
paths.push({
|
|
467
|
+
nodes: [...pathNodes, currentNode],
|
|
468
|
+
edges: [...pathEdges],
|
|
469
|
+
totalWeight: pathEdges.reduce((sum, e) => sum + e.weight, 0),
|
|
470
|
+
length: pathEdges.length,
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
if (options.limit && paths.length >= options.limit)
|
|
476
|
+
return;
|
|
477
|
+
const neighborsResult = await this.getNeighbors(currentNode.id, options.direction);
|
|
478
|
+
if (!neighborsResult.success)
|
|
479
|
+
return;
|
|
480
|
+
let hasUnvisitedNeighbors = false;
|
|
481
|
+
for (const { node, edge } of neighborsResult.data) {
|
|
482
|
+
if (options.edgeTypes && !options.edgeTypes.includes(edge.type))
|
|
483
|
+
continue;
|
|
484
|
+
if (options.minEdgeWeight !== undefined && edge.weight < options.minEdgeWeight)
|
|
485
|
+
continue;
|
|
486
|
+
if (options.minConfidence !== undefined && edge.confidence < options.minConfidence)
|
|
487
|
+
continue;
|
|
488
|
+
if (!visitedEdges.has(edge.id)) {
|
|
489
|
+
visitedEdges.add(edge.id);
|
|
490
|
+
allEdges.push(edge);
|
|
491
|
+
}
|
|
492
|
+
if (!visited.has(node.id)) {
|
|
493
|
+
hasUnvisitedNeighbors = true;
|
|
494
|
+
visited.add(node.id);
|
|
495
|
+
allNodes.push(node);
|
|
496
|
+
await this.traverseRecursive(node, [...pathNodes, currentNode], [...pathEdges, edge], currentDepth + 1, options, visited, visitedEdges, paths, allNodes, allEdges);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
if (!hasUnvisitedNeighbors && pathNodes.length > 0) {
|
|
500
|
+
paths.push({
|
|
501
|
+
nodes: [...pathNodes, currentNode],
|
|
502
|
+
edges: [...pathEdges],
|
|
503
|
+
totalWeight: pathEdges.reduce((sum, e) => sum + e.weight, 0),
|
|
504
|
+
length: pathEdges.length,
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
async findShortestPath(_agentId, startNodeId, endNodeId, maxDepth = 5) {
|
|
509
|
+
await this.initialize();
|
|
510
|
+
if (startNodeId === endNodeId) {
|
|
511
|
+
const nodeResult = await this.getNode(startNodeId);
|
|
512
|
+
if (!nodeResult.success || !nodeResult.data)
|
|
513
|
+
return this.success(null);
|
|
514
|
+
return this.success({
|
|
515
|
+
nodes: [nodeResult.data],
|
|
516
|
+
edges: [],
|
|
517
|
+
totalWeight: 0,
|
|
518
|
+
length: 0,
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
const queue = [{ nodeId: startNodeId, pathNodeIds: [startNodeId], pathEdgeIds: [], totalWeight: 0 }];
|
|
522
|
+
const visited = new Set([startNodeId]);
|
|
523
|
+
while (queue.length > 0) {
|
|
524
|
+
const current = queue.shift();
|
|
525
|
+
if (current.pathEdgeIds.length >= maxDepth)
|
|
526
|
+
continue;
|
|
527
|
+
const neighborsResult = await this.getNeighbors(current.nodeId, 'both');
|
|
528
|
+
if (!neighborsResult.success)
|
|
529
|
+
continue;
|
|
530
|
+
for (const { node, edge } of neighborsResult.data) {
|
|
531
|
+
if (visited.has(node.id))
|
|
532
|
+
continue;
|
|
533
|
+
const newPath = {
|
|
534
|
+
nodeId: node.id,
|
|
535
|
+
pathNodeIds: [...current.pathNodeIds, node.id],
|
|
536
|
+
pathEdgeIds: [...current.pathEdgeIds, edge.id],
|
|
537
|
+
totalWeight: current.totalWeight + edge.weight,
|
|
538
|
+
};
|
|
539
|
+
if (node.id === endNodeId) {
|
|
540
|
+
const nodes = [];
|
|
541
|
+
for (const nid of newPath.pathNodeIds) {
|
|
542
|
+
const nr = await this.getNode(nid);
|
|
543
|
+
if (nr.success && nr.data)
|
|
544
|
+
nodes.push(nr.data);
|
|
545
|
+
}
|
|
546
|
+
const edges = [];
|
|
547
|
+
for (const eid of newPath.pathEdgeIds) {
|
|
548
|
+
const er = await this.getEdge(eid);
|
|
549
|
+
if (er.success && er.data)
|
|
550
|
+
edges.push(er.data);
|
|
551
|
+
}
|
|
552
|
+
return this.success({
|
|
553
|
+
nodes,
|
|
554
|
+
edges,
|
|
555
|
+
totalWeight: newPath.totalWeight,
|
|
556
|
+
length: edges.length,
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
visited.add(node.id);
|
|
560
|
+
queue.push(newPath);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
return this.success(null);
|
|
564
|
+
}
|
|
565
|
+
async getNeighbors(nodeId, direction = 'both') {
|
|
566
|
+
await this.initialize();
|
|
567
|
+
const db = this.ensureDb();
|
|
568
|
+
const selectCols = `
|
|
569
|
+
n.id, n.agent_id, n.type, n.name, n.aliases, n.description,
|
|
570
|
+
n.properties, n.embedding, n.confidence, n.source, n.metadata,
|
|
571
|
+
n.created_at, n.updated_at, n.last_accessed_at, n.access_count,
|
|
572
|
+
e.id as edge_id, e.agent_id as edge_agent_id, e.type as edge_type,
|
|
573
|
+
e.source_node_id as edge_source_node_id, e.target_node_id as edge_target_node_id,
|
|
574
|
+
e.label as edge_label, e.weight as edge_weight, e.bidirectional as edge_bidirectional,
|
|
575
|
+
e.properties as edge_properties, e.confidence as edge_confidence,
|
|
576
|
+
e.source as edge_source, e.valid_from as edge_valid_from, e.valid_until as edge_valid_until,
|
|
577
|
+
e.metadata as edge_metadata, e.created_at as edge_created_at, e.updated_at as edge_updated_at
|
|
578
|
+
`;
|
|
579
|
+
let sql;
|
|
580
|
+
if (direction === 'outgoing') {
|
|
581
|
+
sql = `
|
|
582
|
+
SELECT ${selectCols}
|
|
583
|
+
FROM graph_edges e
|
|
584
|
+
JOIN graph_nodes n ON n.id = e.target_node_id
|
|
585
|
+
WHERE e.source_node_id = ?
|
|
586
|
+
UNION ALL
|
|
587
|
+
SELECT ${selectCols}
|
|
588
|
+
FROM graph_edges e
|
|
589
|
+
JOIN graph_nodes n ON n.id = e.source_node_id
|
|
590
|
+
WHERE e.target_node_id = ? AND e.bidirectional = 1
|
|
591
|
+
`;
|
|
592
|
+
}
|
|
593
|
+
else if (direction === 'incoming') {
|
|
594
|
+
sql = `
|
|
595
|
+
SELECT ${selectCols}
|
|
596
|
+
FROM graph_edges e
|
|
597
|
+
JOIN graph_nodes n ON n.id = e.source_node_id
|
|
598
|
+
WHERE e.target_node_id = ?
|
|
599
|
+
UNION ALL
|
|
600
|
+
SELECT ${selectCols}
|
|
601
|
+
FROM graph_edges e
|
|
602
|
+
JOIN graph_nodes n ON n.id = e.target_node_id
|
|
603
|
+
WHERE e.source_node_id = ? AND e.bidirectional = 1
|
|
604
|
+
`;
|
|
605
|
+
}
|
|
606
|
+
else {
|
|
607
|
+
sql = `
|
|
608
|
+
SELECT ${selectCols}
|
|
609
|
+
FROM graph_edges e
|
|
610
|
+
JOIN graph_nodes n ON n.id = e.target_node_id
|
|
611
|
+
WHERE e.source_node_id = ?
|
|
612
|
+
UNION ALL
|
|
613
|
+
SELECT ${selectCols}
|
|
614
|
+
FROM graph_edges e
|
|
615
|
+
JOIN graph_nodes n ON n.id = e.source_node_id
|
|
616
|
+
WHERE e.target_node_id = ?
|
|
617
|
+
`;
|
|
618
|
+
}
|
|
619
|
+
const rows = db.prepare(sql).all(nodeId, nodeId);
|
|
620
|
+
return this.success(rows.map((row) => ({
|
|
621
|
+
node: this.rowToNode(row),
|
|
622
|
+
edge: this.neighborRowToEdge(row),
|
|
623
|
+
})));
|
|
624
|
+
}
|
|
625
|
+
async mergeNodes(targetNodeId, sourceNodeIds) {
|
|
626
|
+
await this.initialize();
|
|
627
|
+
const db = this.ensureDb();
|
|
628
|
+
for (const sourceId of sourceNodeIds) {
|
|
629
|
+
const sourceNode = await this.getNode(sourceId);
|
|
630
|
+
db.prepare(`UPDATE graph_edges SET source_node_id = ? WHERE source_node_id = ?`).run(targetNodeId, sourceId);
|
|
631
|
+
db.prepare(`UPDATE graph_edges SET target_node_id = ? WHERE target_node_id = ?`).run(targetNodeId, sourceId);
|
|
632
|
+
db.prepare(`DELETE FROM graph_edges WHERE source_node_id = target_node_id`).run();
|
|
633
|
+
if (sourceNode.success && sourceNode.data) {
|
|
634
|
+
const targetRow = db
|
|
635
|
+
.prepare(`SELECT aliases FROM graph_nodes WHERE id = ?`)
|
|
636
|
+
.get(targetNodeId);
|
|
637
|
+
if (targetRow) {
|
|
638
|
+
const currentAliases = JSON.parse(targetRow.aliases);
|
|
639
|
+
const newAliases = [...currentAliases, sourceNode.data.name, ...sourceNode.data.aliases];
|
|
640
|
+
const uniqueAliases = [...new Set(newAliases)];
|
|
641
|
+
db.prepare(`UPDATE graph_nodes SET aliases = ? WHERE id = ?`).run(JSON.stringify(uniqueAliases), targetNodeId);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
db.prepare(`DELETE FROM graph_nodes WHERE id = ?`).run(sourceId);
|
|
645
|
+
}
|
|
646
|
+
const mergedNode = await this.getNode(targetNodeId);
|
|
647
|
+
if (!mergedNode.success || !mergedNode.data) {
|
|
648
|
+
return this.failure(`Target node not found: ${targetNodeId}`);
|
|
649
|
+
}
|
|
650
|
+
return this.success(mergedNode.data);
|
|
651
|
+
}
|
|
652
|
+
async clearGraph(agentId) {
|
|
653
|
+
await this.initialize();
|
|
654
|
+
const db = this.ensureDb();
|
|
655
|
+
db.prepare(`DELETE FROM graph_edges WHERE agent_id = ?`).run(agentId);
|
|
656
|
+
db.prepare(`DELETE FROM graph_nodes WHERE agent_id = ?`).run(agentId);
|
|
657
|
+
return this.success(undefined);
|
|
658
|
+
}
|
|
659
|
+
async getGraphStats(agentId) {
|
|
660
|
+
await this.initialize();
|
|
661
|
+
const db = this.ensureDb();
|
|
662
|
+
const nodeCountRow = db
|
|
663
|
+
.prepare(`SELECT COUNT(*) as count FROM graph_nodes WHERE agent_id = ?`)
|
|
664
|
+
.get(agentId);
|
|
665
|
+
const edgeCountRow = db
|
|
666
|
+
.prepare(`SELECT COUNT(*) as count FROM graph_edges WHERE agent_id = ?`)
|
|
667
|
+
.get(agentId);
|
|
668
|
+
const nodesByTypeRows = db
|
|
669
|
+
.prepare(`SELECT type, COUNT(*) as count FROM graph_nodes WHERE agent_id = ? GROUP BY type`)
|
|
670
|
+
.all(agentId);
|
|
671
|
+
const edgesByTypeRows = db
|
|
672
|
+
.prepare(`SELECT type, COUNT(*) as count FROM graph_edges WHERE agent_id = ? GROUP BY type`)
|
|
673
|
+
.all(agentId);
|
|
674
|
+
const nodeCount = nodeCountRow.count;
|
|
675
|
+
const edgeCount = edgeCountRow.count;
|
|
676
|
+
const nodesByType = {};
|
|
677
|
+
for (const row of nodesByTypeRows) {
|
|
678
|
+
nodesByType[row.type] = row.count;
|
|
679
|
+
}
|
|
680
|
+
const edgesByType = {};
|
|
681
|
+
for (const row of edgesByTypeRows) {
|
|
682
|
+
edgesByType[row.type] = row.count;
|
|
683
|
+
}
|
|
684
|
+
return this.success({
|
|
685
|
+
nodeCount,
|
|
686
|
+
edgeCount,
|
|
687
|
+
nodesByType: nodesByType,
|
|
688
|
+
edgesByType: edgesByType,
|
|
689
|
+
averageEdgesPerNode: nodeCount > 0 ? edgeCount / nodeCount : 0,
|
|
690
|
+
maxDepth: 0,
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
//# sourceMappingURL=sqlite-graph-adapter.js.map
|