@cogitator-ai/memory 0.10.0 → 0.11.1

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.
Files changed (65) hide show
  1. package/README.md +212 -89
  2. package/dist/adapters/index.d.ts +10 -2
  3. package/dist/adapters/index.d.ts.map +1 -1
  4. package/dist/adapters/index.js.map +1 -1
  5. package/dist/adapters/mongodb.d.ts +6 -0
  6. package/dist/adapters/mongodb.d.ts.map +1 -1
  7. package/dist/adapters/mongodb.js +60 -37
  8. package/dist/adapters/mongodb.js.map +1 -1
  9. package/dist/adapters/pgvector.d.ts +46 -0
  10. package/dist/adapters/pgvector.d.ts.map +1 -0
  11. package/dist/adapters/pgvector.js +55 -0
  12. package/dist/adapters/pgvector.js.map +1 -0
  13. package/dist/adapters/postgres.d.ts +4 -0
  14. package/dist/adapters/postgres.d.ts.map +1 -1
  15. package/dist/adapters/postgres.js +149 -68
  16. package/dist/adapters/postgres.js.map +1 -1
  17. package/dist/adapters/qdrant.d.ts +1 -0
  18. package/dist/adapters/qdrant.d.ts.map +1 -1
  19. package/dist/adapters/qdrant.js +49 -42
  20. package/dist/adapters/qdrant.js.map +1 -1
  21. package/dist/adapters/sqlite-driver.d.ts +11 -0
  22. package/dist/adapters/sqlite-driver.d.ts.map +1 -0
  23. package/dist/adapters/sqlite-driver.js +2 -0
  24. package/dist/adapters/sqlite-driver.js.map +1 -0
  25. package/dist/adapters/sqlite.d.ts +1 -0
  26. package/dist/adapters/sqlite.d.ts.map +1 -1
  27. package/dist/adapters/sqlite.js +18 -18
  28. package/dist/adapters/sqlite.js.map +1 -1
  29. package/dist/compaction.d.ts +14 -1
  30. package/dist/compaction.d.ts.map +1 -1
  31. package/dist/compaction.js +4 -1
  32. package/dist/compaction.js.map +1 -1
  33. package/dist/context-builder.d.ts +5 -0
  34. package/dist/context-builder.d.ts.map +1 -1
  35. package/dist/context-builder.js +10 -11
  36. package/dist/context-builder.js.map +1 -1
  37. package/dist/core-facts.d.ts +5 -0
  38. package/dist/core-facts.d.ts.map +1 -1
  39. package/dist/core-facts.js +1 -2
  40. package/dist/core-facts.js.map +1 -1
  41. package/dist/embedding/openai.d.ts +5 -0
  42. package/dist/embedding/openai.d.ts.map +1 -1
  43. package/dist/embedding/openai.js +24 -4
  44. package/dist/embedding/openai.js.map +1 -1
  45. package/dist/index.d.ts +2 -2
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +1 -1
  48. package/dist/index.js.map +1 -1
  49. package/dist/knowledge-graph/entity-extractor.d.ts +24 -11
  50. package/dist/knowledge-graph/entity-extractor.d.ts.map +1 -1
  51. package/dist/knowledge-graph/entity-extractor.js +15 -3
  52. package/dist/knowledge-graph/entity-extractor.js.map +1 -1
  53. package/dist/knowledge-graph/graph-adapter.d.ts +1 -0
  54. package/dist/knowledge-graph/graph-adapter.d.ts.map +1 -1
  55. package/dist/knowledge-graph/graph-adapter.js +477 -453
  56. package/dist/knowledge-graph/graph-adapter.js.map +1 -1
  57. package/dist/knowledge-graph/sqlite-graph-adapter.d.ts +1 -0
  58. package/dist/knowledge-graph/sqlite-graph-adapter.d.ts.map +1 -1
  59. package/dist/knowledge-graph/sqlite-graph-adapter.js +461 -439
  60. package/dist/knowledge-graph/sqlite-graph-adapter.js.map +1 -1
  61. package/dist/schema.d.ts +4 -0
  62. package/dist/schema.d.ts.map +1 -1
  63. package/dist/schema.js +2 -0
  64. package/dist/schema.js.map +1 -1
  65. package/package.json +3 -3
@@ -1,4 +1,6 @@
1
1
  import { nanoid } from 'nanoid';
2
+ import { unwrap } from '../result.js';
3
+ import { ensureHnswCosineIndex } from '../adapters/pgvector.js';
2
4
  export class PostgresGraphAdapter {
3
5
  pool;
4
6
  schema;
@@ -95,11 +97,12 @@ export class PostgresGraphAdapter {
95
97
  ON ${this.schema}.graph_edges(agent_id, type)
96
98
  `);
97
99
  try {
98
- await this.pool.query(`
99
- CREATE INDEX IF NOT EXISTS idx_graph_nodes_embedding
100
- ON ${this.schema}.graph_nodes
101
- USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100)
102
- `);
100
+ await ensureHnswCosineIndex(this.pool, {
101
+ schema: this.schema,
102
+ table: 'graph_nodes',
103
+ column: 'embedding',
104
+ index: 'idx_graph_nodes_embedding',
105
+ });
103
106
  }
104
107
  catch { }
105
108
  }
@@ -109,308 +112,326 @@ export class PostgresGraphAdapter {
109
112
  failure(error) {
110
113
  return { success: false, error };
111
114
  }
115
+ async run(operation) {
116
+ try {
117
+ await this.initialize();
118
+ return await operation();
119
+ }
120
+ catch (err) {
121
+ return this.failure(err instanceof Error ? err.message : String(err));
122
+ }
123
+ }
112
124
  generateId(prefix) {
113
125
  return `${prefix}_${nanoid(12)}`;
114
126
  }
115
127
  async addNode(node) {
116
- await this.initialize();
117
- const id = this.generateId('node');
118
- const now = new Date();
119
- const embeddingStr = node.embedding ? `[${node.embedding.join(',')}]` : null;
120
- await this.pool.query(`INSERT INTO ${this.schema}.graph_nodes
121
- (id, agent_id, type, name, aliases, description, properties, embedding, confidence, source, metadata, created_at, updated_at, last_accessed_at, access_count)
122
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $12, $12, 0)`, [
123
- id,
124
- node.agentId,
125
- node.type,
126
- node.name,
127
- node.aliases,
128
- node.description ?? null,
129
- node.properties,
130
- embeddingStr,
131
- node.confidence,
132
- node.source,
133
- node.metadata ?? {},
134
- now,
135
- ]);
136
- return this.success({
137
- ...node,
138
- id,
139
- createdAt: now,
140
- updatedAt: now,
141
- lastAccessedAt: now,
142
- accessCount: 0,
128
+ return this.run(async () => {
129
+ const id = this.generateId('node');
130
+ const now = new Date();
131
+ const embeddingStr = node.embedding ? `[${node.embedding.join(',')}]` : null;
132
+ await this.pool.query(`INSERT INTO ${this.schema}.graph_nodes
133
+ (id, agent_id, type, name, aliases, description, properties, embedding, confidence, source, metadata, created_at, updated_at, last_accessed_at, access_count)
134
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $12, $12, 0)`, [
135
+ id,
136
+ node.agentId,
137
+ node.type,
138
+ node.name,
139
+ node.aliases,
140
+ node.description ?? null,
141
+ node.properties,
142
+ embeddingStr,
143
+ node.confidence,
144
+ node.source,
145
+ node.metadata ?? {},
146
+ now,
147
+ ]);
148
+ return this.success({
149
+ ...node,
150
+ id,
151
+ createdAt: now,
152
+ updatedAt: now,
153
+ lastAccessedAt: now,
154
+ accessCount: 0,
155
+ });
143
156
  });
144
157
  }
145
158
  async getNode(nodeId) {
146
- await this.initialize();
147
- await this.pool.query(`UPDATE ${this.schema}.graph_nodes
148
- SET last_accessed_at = NOW(), access_count = access_count + 1
149
- WHERE id = $1`, [nodeId]);
150
- const result = await this.pool.query(`SELECT * FROM ${this.schema}.graph_nodes WHERE id = $1`, [
151
- nodeId,
152
- ]);
153
- if (result.rows.length === 0)
154
- return this.success(null);
155
- return this.success(this.rowToNode(result.rows[0]));
159
+ return this.run(async () => {
160
+ await this.pool.query(`UPDATE ${this.schema}.graph_nodes
161
+ SET last_accessed_at = NOW(), access_count = access_count + 1
162
+ WHERE id = $1`, [nodeId]);
163
+ const result = await this.pool.query(`SELECT * FROM ${this.schema}.graph_nodes WHERE id = $1`, [nodeId]);
164
+ if (result.rows.length === 0)
165
+ return this.success(null);
166
+ return this.success(this.rowToNode(result.rows[0]));
167
+ });
156
168
  }
157
169
  async getNodeByName(agentId, name) {
158
- await this.initialize();
159
- const result = await this.pool.query(`SELECT * FROM ${this.schema}.graph_nodes
160
- WHERE agent_id = $1 AND (name = $2 OR $2 = ANY(aliases))`, [agentId, name]);
161
- if (result.rows.length === 0)
162
- return this.success(null);
163
- return this.success(this.rowToNode(result.rows[0]));
170
+ return this.run(async () => {
171
+ const result = await this.pool.query(`SELECT * FROM ${this.schema}.graph_nodes
172
+ WHERE agent_id = $1 AND (name = $2 OR $2 = ANY(aliases))`, [agentId, name]);
173
+ if (result.rows.length === 0)
174
+ return this.success(null);
175
+ return this.success(this.rowToNode(result.rows[0]));
176
+ });
164
177
  }
165
178
  async updateNode(nodeId, updates) {
166
- await this.initialize();
167
- const setClauses = ['updated_at = NOW()'];
168
- const params = [];
169
- let paramIndex = 1;
170
- if (updates.name !== undefined) {
171
- setClauses.push(`name = $${paramIndex++}`);
172
- params.push(updates.name);
173
- }
174
- if (updates.aliases !== undefined) {
175
- setClauses.push(`aliases = $${paramIndex++}`);
176
- params.push(updates.aliases);
177
- }
178
- if (updates.description !== undefined) {
179
- setClauses.push(`description = $${paramIndex++}`);
180
- params.push(updates.description);
181
- }
182
- if (updates.properties !== undefined) {
183
- setClauses.push(`properties = $${paramIndex++}`);
184
- params.push(updates.properties);
185
- }
186
- if (updates.confidence !== undefined) {
187
- setClauses.push(`confidence = $${paramIndex++}`);
188
- params.push(updates.confidence);
189
- }
190
- if (updates.metadata !== undefined) {
191
- setClauses.push(`metadata = $${paramIndex++}`);
192
- params.push(updates.metadata);
193
- }
194
- if (updates.embedding !== undefined) {
195
- setClauses.push(`embedding = $${paramIndex++}`);
196
- params.push(`[${updates.embedding.join(',')}]`);
197
- }
198
- params.push(nodeId);
199
- const result = await this.pool.query(`UPDATE ${this.schema}.graph_nodes SET ${setClauses.join(', ')} WHERE id = $${paramIndex} RETURNING *`, params);
200
- if (result.rows.length === 0) {
201
- return this.failure(`Node not found: ${nodeId}`);
202
- }
203
- return this.success(this.rowToNode(result.rows[0]));
179
+ return this.run(async () => {
180
+ const setClauses = ['updated_at = NOW()'];
181
+ const params = [];
182
+ let paramIndex = 1;
183
+ if (updates.name !== undefined) {
184
+ setClauses.push(`name = $${paramIndex++}`);
185
+ params.push(updates.name);
186
+ }
187
+ if (updates.aliases !== undefined) {
188
+ setClauses.push(`aliases = $${paramIndex++}`);
189
+ params.push(updates.aliases);
190
+ }
191
+ if (updates.description !== undefined) {
192
+ setClauses.push(`description = $${paramIndex++}`);
193
+ params.push(updates.description);
194
+ }
195
+ if (updates.properties !== undefined) {
196
+ setClauses.push(`properties = $${paramIndex++}`);
197
+ params.push(updates.properties);
198
+ }
199
+ if (updates.confidence !== undefined) {
200
+ setClauses.push(`confidence = $${paramIndex++}`);
201
+ params.push(updates.confidence);
202
+ }
203
+ if (updates.metadata !== undefined) {
204
+ setClauses.push(`metadata = $${paramIndex++}`);
205
+ params.push(updates.metadata);
206
+ }
207
+ if (updates.embedding !== undefined) {
208
+ setClauses.push(`embedding = $${paramIndex++}`);
209
+ params.push(`[${updates.embedding.join(',')}]`);
210
+ }
211
+ params.push(nodeId);
212
+ const result = await this.pool.query(`UPDATE ${this.schema}.graph_nodes SET ${setClauses.join(', ')} WHERE id = $${paramIndex} RETURNING *`, params);
213
+ if (result.rows.length === 0) {
214
+ return this.failure(`Node not found: ${nodeId}`);
215
+ }
216
+ return this.success(this.rowToNode(result.rows[0]));
217
+ });
204
218
  }
205
219
  async deleteNode(nodeId) {
206
- await this.initialize();
207
- await this.pool.query(`DELETE FROM ${this.schema}.graph_nodes WHERE id = $1`, [nodeId]);
208
- return this.success(undefined);
220
+ return this.run(async () => {
221
+ await this.pool.query(`DELETE FROM ${this.schema}.graph_nodes WHERE id = $1`, [nodeId]);
222
+ return this.success(undefined);
223
+ });
209
224
  }
210
225
  async queryNodes(query) {
211
- await this.initialize();
212
- let sql = `SELECT * FROM ${this.schema}.graph_nodes WHERE agent_id = $1`;
213
- const params = [query.agentId];
214
- let paramIndex = 2;
215
- if (query.types && query.types.length > 0) {
216
- sql += ` AND type = ANY($${paramIndex++})`;
217
- params.push(query.types);
218
- }
219
- if (query.namePattern) {
220
- sql += ` AND name ILIKE $${paramIndex++}`;
221
- params.push(`%${query.namePattern.replace(/[%_\\]/g, '\\$&')}%`);
222
- }
223
- if (query.minConfidence !== undefined) {
224
- sql += ` AND confidence >= $${paramIndex++}`;
225
- params.push(query.minConfidence);
226
- }
227
- sql += ' ORDER BY access_count DESC, updated_at DESC';
228
- if (query.limit) {
229
- sql += ` LIMIT $${paramIndex}`;
230
- params.push(query.limit);
231
- }
232
- const result = await this.pool.query(sql, params);
233
- return this.success(result.rows.map((row) => this.rowToNode(row, query.includeEmbedding)));
226
+ return this.run(async () => {
227
+ let sql = `SELECT * FROM ${this.schema}.graph_nodes WHERE agent_id = $1`;
228
+ const params = [query.agentId];
229
+ let paramIndex = 2;
230
+ if (query.types && query.types.length > 0) {
231
+ sql += ` AND type = ANY($${paramIndex++})`;
232
+ params.push(query.types);
233
+ }
234
+ if (query.namePattern) {
235
+ sql += ` AND name ILIKE $${paramIndex++}`;
236
+ params.push(`%${query.namePattern.replace(/[%_\\]/g, '\\$&')}%`);
237
+ }
238
+ if (query.minConfidence !== undefined) {
239
+ sql += ` AND confidence >= $${paramIndex++}`;
240
+ params.push(query.minConfidence);
241
+ }
242
+ sql += ' ORDER BY access_count DESC, updated_at DESC';
243
+ if (query.limit) {
244
+ sql += ` LIMIT $${paramIndex}`;
245
+ params.push(query.limit);
246
+ }
247
+ const result = await this.pool.query(sql, params);
248
+ return this.success(result.rows.map((row) => this.rowToNode(row, query.includeEmbedding)));
249
+ });
234
250
  }
235
251
  async searchNodesSemantic(options) {
236
- await this.initialize();
237
- if (!options.vector) {
238
- return this.failure('searchNodesSemantic requires vector');
239
- }
240
- const vectorStr = `[${options.vector.join(',')}]`;
241
- const limit = options.limit ?? 10;
242
- const threshold = options.threshold ?? 0.7;
243
- let sql = `
244
- SELECT *, 1 - (embedding <=> $1) as score
245
- FROM ${this.schema}.graph_nodes
246
- WHERE agent_id = $2 AND embedding IS NOT NULL AND 1 - (embedding <=> $1) >= $3
247
- `;
248
- const params = [vectorStr, options.agentId, threshold];
249
- let paramIndex = 4;
250
- if (options.entityTypes && options.entityTypes.length > 0) {
251
- sql += ` AND type = ANY($${paramIndex++})`;
252
- params.push(options.entityTypes);
253
- }
254
- sql += ` ORDER BY embedding <=> $1 LIMIT $${paramIndex}`;
255
- params.push(limit);
256
- const result = await this.pool.query(sql, params);
257
- return this.success(result.rows.map((row) => ({
258
- ...this.rowToNode(row),
259
- score: row.score,
260
- })));
252
+ return this.run(async () => {
253
+ if (!options.vector) {
254
+ return this.failure('searchNodesSemantic requires vector');
255
+ }
256
+ const vectorStr = `[${options.vector.join(',')}]`;
257
+ const limit = options.limit ?? 10;
258
+ const threshold = options.threshold ?? 0.7;
259
+ let sql = `
260
+ SELECT *, 1 - (embedding <=> $1) as score
261
+ FROM ${this.schema}.graph_nodes
262
+ WHERE agent_id = $2 AND embedding IS NOT NULL AND 1 - (embedding <=> $1) >= $3
263
+ `;
264
+ const params = [vectorStr, options.agentId, threshold];
265
+ let paramIndex = 4;
266
+ if (options.entityTypes && options.entityTypes.length > 0) {
267
+ sql += ` AND type = ANY($${paramIndex++})`;
268
+ params.push(options.entityTypes);
269
+ }
270
+ sql += ` ORDER BY embedding <=> $1 LIMIT $${paramIndex}`;
271
+ params.push(limit);
272
+ const result = await this.pool.query(sql, params);
273
+ return this.success(result.rows.map((row) => ({
274
+ ...this.rowToNode(row),
275
+ score: row.score,
276
+ })));
277
+ });
261
278
  }
262
279
  async addEdge(edge) {
263
- await this.initialize();
264
- const id = this.generateId('edge');
265
- const now = new Date();
266
- await this.pool.query(`INSERT INTO ${this.schema}.graph_edges
267
- (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)
268
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $15)`, [
269
- id,
270
- edge.agentId,
271
- edge.sourceNodeId,
272
- edge.targetNodeId,
273
- edge.type,
274
- edge.label ?? null,
275
- edge.weight,
276
- edge.bidirectional,
277
- edge.properties,
278
- edge.confidence,
279
- edge.source,
280
- edge.validFrom ?? null,
281
- edge.validUntil ?? null,
282
- edge.metadata ?? {},
283
- now,
284
- ]);
285
- return this.success({
286
- ...edge,
287
- id,
288
- createdAt: now,
289
- updatedAt: now,
280
+ return this.run(async () => {
281
+ const id = this.generateId('edge');
282
+ const now = new Date();
283
+ await this.pool.query(`INSERT INTO ${this.schema}.graph_edges
284
+ (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)
285
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $15)`, [
286
+ id,
287
+ edge.agentId,
288
+ edge.sourceNodeId,
289
+ edge.targetNodeId,
290
+ edge.type,
291
+ edge.label ?? null,
292
+ edge.weight,
293
+ edge.bidirectional,
294
+ edge.properties,
295
+ edge.confidence,
296
+ edge.source,
297
+ edge.validFrom ?? null,
298
+ edge.validUntil ?? null,
299
+ edge.metadata ?? {},
300
+ now,
301
+ ]);
302
+ return this.success({
303
+ ...edge,
304
+ id,
305
+ createdAt: now,
306
+ updatedAt: now,
307
+ });
290
308
  });
291
309
  }
292
310
  async getEdge(edgeId) {
293
- await this.initialize();
294
- const result = await this.pool.query(`SELECT * FROM ${this.schema}.graph_edges WHERE id = $1`, [
295
- edgeId,
296
- ]);
297
- if (result.rows.length === 0)
298
- return this.success(null);
299
- return this.success(this.rowToEdge(result.rows[0]));
311
+ return this.run(async () => {
312
+ const result = await this.pool.query(`SELECT * FROM ${this.schema}.graph_edges WHERE id = $1`, [edgeId]);
313
+ if (result.rows.length === 0)
314
+ return this.success(null);
315
+ return this.success(this.rowToEdge(result.rows[0]));
316
+ });
300
317
  }
301
318
  async getEdgesBetween(sourceNodeId, targetNodeId) {
302
- await this.initialize();
303
- const result = await this.pool.query(`SELECT * FROM ${this.schema}.graph_edges
304
- WHERE (source_node_id = $1 AND target_node_id = $2)
305
- OR (bidirectional = TRUE AND source_node_id = $2 AND target_node_id = $1)`, [sourceNodeId, targetNodeId]);
306
- return this.success(result.rows.map((row) => this.rowToEdge(row)));
319
+ return this.run(async () => {
320
+ const result = await this.pool.query(`SELECT * FROM ${this.schema}.graph_edges
321
+ WHERE (source_node_id = $1 AND target_node_id = $2)
322
+ OR (bidirectional = TRUE AND source_node_id = $2 AND target_node_id = $1)`, [sourceNodeId, targetNodeId]);
323
+ return this.success(result.rows.map((row) => this.rowToEdge(row)));
324
+ });
307
325
  }
308
326
  async updateEdge(edgeId, updates) {
309
- await this.initialize();
310
- const setClauses = ['updated_at = NOW()'];
311
- const params = [];
312
- let paramIndex = 1;
313
- if (updates.weight !== undefined) {
314
- setClauses.push(`weight = $${paramIndex++}`);
315
- params.push(updates.weight);
316
- }
317
- if (updates.label !== undefined) {
318
- setClauses.push(`label = $${paramIndex++}`);
319
- params.push(updates.label);
320
- }
321
- if (updates.properties !== undefined) {
322
- setClauses.push(`properties = $${paramIndex++}`);
323
- params.push(updates.properties);
324
- }
325
- if (updates.confidence !== undefined) {
326
- setClauses.push(`confidence = $${paramIndex++}`);
327
- params.push(updates.confidence);
328
- }
329
- if (updates.validFrom !== undefined) {
330
- setClauses.push(`valid_from = $${paramIndex++}`);
331
- params.push(updates.validFrom);
332
- }
333
- if (updates.validUntil !== undefined) {
334
- setClauses.push(`valid_until = $${paramIndex++}`);
335
- params.push(updates.validUntil);
336
- }
337
- if (updates.metadata !== undefined) {
338
- setClauses.push(`metadata = $${paramIndex++}`);
339
- params.push(updates.metadata);
340
- }
341
- params.push(edgeId);
342
- const result = await this.pool.query(`UPDATE ${this.schema}.graph_edges SET ${setClauses.join(', ')} WHERE id = $${paramIndex} RETURNING *`, params);
343
- if (result.rows.length === 0) {
344
- return this.failure(`Edge not found: ${edgeId}`);
345
- }
346
- return this.success(this.rowToEdge(result.rows[0]));
327
+ return this.run(async () => {
328
+ const setClauses = ['updated_at = NOW()'];
329
+ const params = [];
330
+ let paramIndex = 1;
331
+ if (updates.weight !== undefined) {
332
+ setClauses.push(`weight = $${paramIndex++}`);
333
+ params.push(updates.weight);
334
+ }
335
+ if (updates.label !== undefined) {
336
+ setClauses.push(`label = $${paramIndex++}`);
337
+ params.push(updates.label);
338
+ }
339
+ if (updates.properties !== undefined) {
340
+ setClauses.push(`properties = $${paramIndex++}`);
341
+ params.push(updates.properties);
342
+ }
343
+ if (updates.confidence !== undefined) {
344
+ setClauses.push(`confidence = $${paramIndex++}`);
345
+ params.push(updates.confidence);
346
+ }
347
+ if (updates.validFrom !== undefined) {
348
+ setClauses.push(`valid_from = $${paramIndex++}`);
349
+ params.push(updates.validFrom);
350
+ }
351
+ if (updates.validUntil !== undefined) {
352
+ setClauses.push(`valid_until = $${paramIndex++}`);
353
+ params.push(updates.validUntil);
354
+ }
355
+ if (updates.metadata !== undefined) {
356
+ setClauses.push(`metadata = $${paramIndex++}`);
357
+ params.push(updates.metadata);
358
+ }
359
+ params.push(edgeId);
360
+ const result = await this.pool.query(`UPDATE ${this.schema}.graph_edges SET ${setClauses.join(', ')} WHERE id = $${paramIndex} RETURNING *`, params);
361
+ if (result.rows.length === 0) {
362
+ return this.failure(`Edge not found: ${edgeId}`);
363
+ }
364
+ return this.success(this.rowToEdge(result.rows[0]));
365
+ });
347
366
  }
348
367
  async deleteEdge(edgeId) {
349
- await this.initialize();
350
- await this.pool.query(`DELETE FROM ${this.schema}.graph_edges WHERE id = $1`, [edgeId]);
351
- return this.success(undefined);
368
+ return this.run(async () => {
369
+ await this.pool.query(`DELETE FROM ${this.schema}.graph_edges WHERE id = $1`, [edgeId]);
370
+ return this.success(undefined);
371
+ });
352
372
  }
353
373
  async queryEdges(query) {
354
- await this.initialize();
355
- let sql = `SELECT * FROM ${this.schema}.graph_edges WHERE agent_id = $1`;
356
- const params = [query.agentId];
357
- let paramIndex = 2;
358
- if (query.sourceNodeId) {
359
- sql += ` AND source_node_id = $${paramIndex++}`;
360
- params.push(query.sourceNodeId);
361
- }
362
- if (query.targetNodeId) {
363
- sql += ` AND target_node_id = $${paramIndex++}`;
364
- params.push(query.targetNodeId);
365
- }
366
- if (query.types && query.types.length > 0) {
367
- sql += ` AND type = ANY($${paramIndex++})`;
368
- params.push(query.types);
369
- }
370
- if (query.minWeight !== undefined) {
371
- sql += ` AND weight >= $${paramIndex++}`;
372
- params.push(query.minWeight);
373
- }
374
- if (query.minConfidence !== undefined) {
375
- sql += ` AND confidence >= $${paramIndex++}`;
376
- params.push(query.minConfidence);
377
- }
378
- if (query.bidirectionalOnly) {
379
- sql += ' AND bidirectional = TRUE';
380
- }
381
- sql += ' ORDER BY weight DESC, confidence DESC';
382
- if (query.limit) {
383
- sql += ` LIMIT $${paramIndex}`;
384
- params.push(query.limit);
385
- }
386
- const result = await this.pool.query(sql, params);
387
- return this.success(result.rows.map((row) => this.rowToEdge(row)));
374
+ return this.run(async () => {
375
+ let sql = `SELECT * FROM ${this.schema}.graph_edges WHERE agent_id = $1`;
376
+ const params = [query.agentId];
377
+ let paramIndex = 2;
378
+ if (query.sourceNodeId) {
379
+ sql += ` AND source_node_id = $${paramIndex++}`;
380
+ params.push(query.sourceNodeId);
381
+ }
382
+ if (query.targetNodeId) {
383
+ sql += ` AND target_node_id = $${paramIndex++}`;
384
+ params.push(query.targetNodeId);
385
+ }
386
+ if (query.types && query.types.length > 0) {
387
+ sql += ` AND type = ANY($${paramIndex++})`;
388
+ params.push(query.types);
389
+ }
390
+ if (query.minWeight !== undefined) {
391
+ sql += ` AND weight >= $${paramIndex++}`;
392
+ params.push(query.minWeight);
393
+ }
394
+ if (query.minConfidence !== undefined) {
395
+ sql += ` AND confidence >= $${paramIndex++}`;
396
+ params.push(query.minConfidence);
397
+ }
398
+ if (query.bidirectionalOnly) {
399
+ sql += ' AND bidirectional = TRUE';
400
+ }
401
+ sql += ' ORDER BY weight DESC, confidence DESC';
402
+ if (query.limit) {
403
+ sql += ` LIMIT $${paramIndex}`;
404
+ params.push(query.limit);
405
+ }
406
+ const result = await this.pool.query(sql, params);
407
+ return this.success(result.rows.map((row) => this.rowToEdge(row)));
408
+ });
388
409
  }
389
410
  async traverse(options) {
390
- await this.initialize();
391
- const visited = new Set();
392
- const visitedEdges = new Set();
393
- const paths = [];
394
- const allNodes = [];
395
- const allEdges = [];
396
- const startNodeResult = await this.getNode(options.startNodeId);
397
- if (!startNodeResult.success || !startNodeResult.data) {
398
- return this.failure(`Start node not found: ${options.startNodeId}`);
399
- }
400
- const startNode = startNodeResult.data;
401
- visited.add(startNode.id);
402
- allNodes.push(startNode);
403
- await this.traverseRecursive(startNode, [], [], 0, options, visited, visitedEdges, paths, allNodes, allEdges);
404
- let actualMaxDepth = 0;
405
- for (const p of paths) {
406
- if (p.length > actualMaxDepth)
407
- actualMaxDepth = p.length;
408
- }
409
- return this.success({
410
- paths,
411
- visitedNodes: allNodes,
412
- visitedEdges: allEdges,
413
- depth: actualMaxDepth,
411
+ return this.run(async () => {
412
+ const visited = new Set();
413
+ const visitedEdges = new Set();
414
+ const paths = [];
415
+ const allNodes = [];
416
+ const allEdges = [];
417
+ const startNode = unwrap(await this.getNode(options.startNodeId));
418
+ if (!startNode) {
419
+ return this.failure(`Start node not found: ${options.startNodeId}`);
420
+ }
421
+ visited.add(startNode.id);
422
+ allNodes.push(startNode);
423
+ await this.traverseRecursive(startNode, [], [], 0, options, visited, visitedEdges, paths, allNodes, allEdges);
424
+ let actualMaxDepth = 0;
425
+ for (const p of paths) {
426
+ if (p.length > actualMaxDepth)
427
+ actualMaxDepth = p.length;
428
+ }
429
+ return this.success({
430
+ paths,
431
+ visitedNodes: allNodes,
432
+ visitedEdges: allEdges,
433
+ depth: actualMaxDepth,
434
+ });
414
435
  });
415
436
  }
416
437
  async traverseRecursive(currentNode, pathNodes, pathEdges, currentDepth, options, visited, visitedEdges, paths, allNodes, allEdges) {
@@ -427,11 +448,9 @@ export class PostgresGraphAdapter {
427
448
  }
428
449
  if (options.limit && paths.length >= options.limit)
429
450
  return;
430
- const neighborsResult = await this.getNeighbors(currentNode.id, options.direction);
431
- if (!neighborsResult.success)
432
- return;
451
+ const neighbors = unwrap(await this.getNeighbors(currentNode.id, options.direction));
433
452
  let hasUnvisitedNeighbors = false;
434
- for (const { node, edge } of neighborsResult.data) {
453
+ for (const { node, edge } of neighbors) {
435
454
  if (options.edgeTypes && !options.edgeTypes.includes(edge.type))
436
455
  continue;
437
456
  if (options.minEdgeWeight !== undefined && edge.weight < options.minEdgeWeight)
@@ -459,196 +478,201 @@ export class PostgresGraphAdapter {
459
478
  }
460
479
  }
461
480
  async findShortestPath(agentId, startNodeId, endNodeId, maxDepth = 5) {
462
- await this.initialize();
463
- if (startNodeId === endNodeId) {
464
- const nodeResult = await this.getNode(startNodeId);
465
- if (!nodeResult.success || !nodeResult.data) {
466
- return this.failure(`Node not found: ${startNodeId}`);
481
+ return this.run(async () => {
482
+ if (startNodeId === endNodeId) {
483
+ const node = unwrap(await this.getNode(startNodeId));
484
+ if (!node) {
485
+ return this.failure(`Node not found: ${startNodeId}`);
486
+ }
487
+ return this.success({
488
+ nodes: [node],
489
+ edges: [],
490
+ totalWeight: 0,
491
+ length: 0,
492
+ });
467
493
  }
468
- return this.success({
469
- nodes: [nodeResult.data],
470
- edges: [],
471
- totalWeight: 0,
472
- length: 0,
473
- });
474
- }
475
- const result = await this.pool.query(`
476
- WITH RECURSIVE path_search AS (
477
- SELECT
478
- source_node_id as start_node,
479
- CASE
480
- WHEN source_node_id = $2 THEN target_node_id
481
- ELSE source_node_id
482
- END as end_node,
483
- ARRAY[$2, CASE WHEN source_node_id = $2 THEN target_node_id ELSE source_node_id END] as path,
484
- ARRAY[id] as edge_ids,
485
- weight as total_weight,
486
- 1 as depth
487
- FROM ${this.schema}.graph_edges
488
- WHERE agent_id = $1
489
- AND (source_node_id = $2 OR (target_node_id = $2 AND bidirectional = TRUE))
494
+ const result = await this.pool.query(`
495
+ WITH RECURSIVE path_search AS (
496
+ SELECT
497
+ source_node_id as start_node,
498
+ CASE
499
+ WHEN source_node_id = $2 THEN target_node_id
500
+ ELSE source_node_id
501
+ END as end_node,
502
+ ARRAY[$2, CASE WHEN source_node_id = $2 THEN target_node_id ELSE source_node_id END] as path,
503
+ ARRAY[id] as edge_ids,
504
+ weight as total_weight,
505
+ 1 as depth
506
+ FROM ${this.schema}.graph_edges
507
+ WHERE agent_id = $1
508
+ AND (source_node_id = $2 OR (target_node_id = $2 AND bidirectional = TRUE))
490
509
 
491
- UNION ALL
510
+ UNION ALL
492
511
 
493
- SELECT
494
- ps.start_node,
495
- CASE
496
- WHEN e.source_node_id = ps.end_node THEN e.target_node_id
497
- ELSE e.source_node_id
498
- END,
499
- ps.path || CASE WHEN e.source_node_id = ps.end_node THEN e.target_node_id ELSE e.source_node_id END,
500
- ps.edge_ids || e.id,
501
- ps.total_weight + e.weight,
502
- ps.depth + 1
503
- FROM path_search ps
504
- JOIN ${this.schema}.graph_edges e ON (
505
- e.source_node_id = ps.end_node
506
- OR (e.target_node_id = ps.end_node AND e.bidirectional = TRUE)
512
+ SELECT
513
+ ps.start_node,
514
+ CASE
515
+ WHEN e.source_node_id = ps.end_node THEN e.target_node_id
516
+ ELSE e.source_node_id
517
+ END,
518
+ ps.path || CASE WHEN e.source_node_id = ps.end_node THEN e.target_node_id ELSE e.source_node_id END,
519
+ ps.edge_ids || e.id,
520
+ ps.total_weight + e.weight,
521
+ ps.depth + 1
522
+ FROM path_search ps
523
+ JOIN ${this.schema}.graph_edges e ON (
524
+ e.source_node_id = ps.end_node
525
+ OR (e.target_node_id = ps.end_node AND e.bidirectional = TRUE)
526
+ )
527
+ WHERE e.agent_id = $1
528
+ AND NOT (CASE WHEN e.source_node_id = ps.end_node THEN e.target_node_id ELSE e.source_node_id END) = ANY(ps.path)
529
+ AND ps.depth < $4
507
530
  )
508
- WHERE e.agent_id = $1
509
- AND NOT (CASE WHEN e.source_node_id = ps.end_node THEN e.target_node_id ELSE e.source_node_id END) = ANY(ps.path)
510
- AND ps.depth < $4
511
- )
512
- SELECT path, edge_ids, total_weight
513
- FROM path_search
514
- WHERE end_node = $3
515
- ORDER BY depth, total_weight
516
- LIMIT 1
517
- `, [agentId, startNodeId, endNodeId, maxDepth]);
518
- if (result.rows.length === 0)
519
- return this.success(null);
520
- const row = result.rows[0];
521
- const nodeIds = row.path;
522
- const edgeIds = row.edge_ids;
523
- const nodes = [];
524
- const edges = [];
525
- for (const nodeId of nodeIds) {
526
- const nodeResult = await this.getNode(nodeId);
527
- if (nodeResult.success && nodeResult.data) {
528
- nodes.push(nodeResult.data);
531
+ SELECT path, edge_ids, total_weight
532
+ FROM path_search
533
+ WHERE end_node = $3
534
+ ORDER BY depth, total_weight
535
+ LIMIT 1
536
+ `, [agentId, startNodeId, endNodeId, maxDepth]);
537
+ if (result.rows.length === 0)
538
+ return this.success(null);
539
+ const row = result.rows[0];
540
+ const nodeIds = row.path;
541
+ const edgeIds = row.edge_ids;
542
+ const nodes = [];
543
+ const edges = [];
544
+ for (const nodeId of nodeIds) {
545
+ const node = unwrap(await this.getNode(nodeId));
546
+ if (node)
547
+ nodes.push(node);
529
548
  }
530
- }
531
- for (const edgeId of edgeIds) {
532
- const edgeResult = await this.getEdge(edgeId);
533
- if (edgeResult.success && edgeResult.data) {
534
- edges.push(edgeResult.data);
549
+ for (const edgeId of edgeIds) {
550
+ const edge = unwrap(await this.getEdge(edgeId));
551
+ if (edge)
552
+ edges.push(edge);
535
553
  }
536
- }
537
- return this.success({
538
- nodes,
539
- edges,
540
- totalWeight: row.total_weight,
541
- length: edges.length,
554
+ return this.success({
555
+ nodes,
556
+ edges,
557
+ totalWeight: row.total_weight,
558
+ length: edges.length,
559
+ });
542
560
  });
543
561
  }
544
562
  async getNeighbors(nodeId, direction = 'both') {
545
- await this.initialize();
546
- let sql;
547
- const params = [nodeId];
548
- const selectCols = `
549
- n.id, n.agent_id, n.type, n.name, n.aliases, n.description,
550
- n.properties, n.confidence, n.source, n.metadata,
551
- n.created_at, n.updated_at, n.last_accessed_at, n.access_count,
552
- e.id as edge_id, e.agent_id as edge_agent_id, e.type as edge_type,
553
- e.source_node_id, e.target_node_id, e.label, e.weight, e.bidirectional,
554
- e.properties as edge_properties, e.confidence as edge_confidence,
555
- e.source as edge_source, e.valid_from, e.valid_until,
556
- e.metadata as edge_metadata, e.created_at as edge_created_at, e.updated_at as edge_updated_at
557
- `;
558
- if (direction === 'outgoing') {
559
- sql = `
560
- SELECT ${selectCols}
561
- FROM ${this.schema}.graph_edges e
562
- JOIN ${this.schema}.graph_nodes n ON n.id = e.target_node_id
563
- WHERE e.source_node_id = $1
564
- UNION ALL
565
- SELECT ${selectCols}
566
- FROM ${this.schema}.graph_edges e
567
- JOIN ${this.schema}.graph_nodes n ON n.id = e.source_node_id
568
- WHERE e.target_node_id = $1 AND e.bidirectional = TRUE
563
+ return this.run(async () => {
564
+ let sql;
565
+ const params = [nodeId];
566
+ const selectCols = `
567
+ n.id, n.agent_id, n.type, n.name, n.aliases, n.description,
568
+ n.properties, n.confidence, n.source, n.metadata,
569
+ n.created_at, n.updated_at, n.last_accessed_at, n.access_count,
570
+ e.id as edge_id, e.agent_id as edge_agent_id, e.type as edge_type,
571
+ e.source_node_id, e.target_node_id, e.label, e.weight, e.bidirectional,
572
+ e.properties as edge_properties, e.confidence as edge_confidence,
573
+ e.source as edge_source, e.valid_from, e.valid_until,
574
+ e.metadata as edge_metadata, e.created_at as edge_created_at, e.updated_at as edge_updated_at
569
575
  `;
570
- }
571
- else if (direction === 'incoming') {
572
- sql = `
573
- SELECT ${selectCols}
574
- FROM ${this.schema}.graph_edges e
575
- JOIN ${this.schema}.graph_nodes n ON n.id = e.source_node_id
576
- WHERE e.target_node_id = $1
577
- UNION ALL
578
- SELECT ${selectCols}
579
- FROM ${this.schema}.graph_edges e
580
- JOIN ${this.schema}.graph_nodes n ON n.id = e.target_node_id
581
- WHERE e.source_node_id = $1 AND e.bidirectional = TRUE
582
- `;
583
- }
584
- else {
585
- sql = `
586
- SELECT ${selectCols}
587
- FROM ${this.schema}.graph_edges e
588
- JOIN ${this.schema}.graph_nodes n ON (
589
- (e.source_node_id = $1 AND n.id = e.target_node_id) OR
590
- (e.target_node_id = $1 AND n.id = e.source_node_id)
591
- )
592
- WHERE e.source_node_id = $1 OR e.target_node_id = $1
593
- `;
594
- }
595
- const result = await this.pool.query(sql, params);
596
- return this.success(result.rows.map((row) => ({
597
- node: this.rowToNode(row),
598
- edge: this.rowToEdgeFromJoin(row),
599
- })));
576
+ if (direction === 'outgoing') {
577
+ sql = `
578
+ SELECT ${selectCols}
579
+ FROM ${this.schema}.graph_edges e
580
+ JOIN ${this.schema}.graph_nodes n ON n.id = e.target_node_id
581
+ WHERE e.source_node_id = $1
582
+ UNION ALL
583
+ SELECT ${selectCols}
584
+ FROM ${this.schema}.graph_edges e
585
+ JOIN ${this.schema}.graph_nodes n ON n.id = e.source_node_id
586
+ WHERE e.target_node_id = $1 AND e.bidirectional = TRUE
587
+ `;
588
+ }
589
+ else if (direction === 'incoming') {
590
+ sql = `
591
+ SELECT ${selectCols}
592
+ FROM ${this.schema}.graph_edges e
593
+ JOIN ${this.schema}.graph_nodes n ON n.id = e.source_node_id
594
+ WHERE e.target_node_id = $1
595
+ UNION ALL
596
+ SELECT ${selectCols}
597
+ FROM ${this.schema}.graph_edges e
598
+ JOIN ${this.schema}.graph_nodes n ON n.id = e.target_node_id
599
+ WHERE e.source_node_id = $1 AND e.bidirectional = TRUE
600
+ `;
601
+ }
602
+ else {
603
+ sql = `
604
+ SELECT ${selectCols}
605
+ FROM ${this.schema}.graph_edges e
606
+ JOIN ${this.schema}.graph_nodes n ON (
607
+ (e.source_node_id = $1 AND n.id = e.target_node_id) OR
608
+ (e.target_node_id = $1 AND n.id = e.source_node_id)
609
+ )
610
+ WHERE e.source_node_id = $1 OR e.target_node_id = $1
611
+ `;
612
+ }
613
+ const result = await this.pool.query(sql, params);
614
+ return this.success(result.rows.map((row) => ({
615
+ node: this.rowToNode(row),
616
+ edge: this.rowToEdgeFromJoin(row),
617
+ })));
618
+ });
600
619
  }
601
620
  async mergeNodes(targetNodeId, sourceNodeIds) {
602
- await this.initialize();
603
- for (const sourceId of sourceNodeIds) {
604
- const sourceNode = await this.getNode(sourceId);
605
- await this.pool.query(`UPDATE ${this.schema}.graph_edges
606
- SET source_node_id = $1 WHERE source_node_id = $2`, [targetNodeId, sourceId]);
607
- await this.pool.query(`UPDATE ${this.schema}.graph_edges
608
- SET target_node_id = $1 WHERE target_node_id = $2`, [targetNodeId, sourceId]);
609
- await this.pool.query(`DELETE FROM ${this.schema}.graph_edges
610
- WHERE source_node_id = $1 AND target_node_id = $1`, [targetNodeId]);
611
- if (sourceNode.success && sourceNode.data) {
612
- await this.pool.query(`UPDATE ${this.schema}.graph_nodes
613
- SET aliases = array_cat(aliases, $1::TEXT[])
614
- WHERE id = $2`, [[sourceNode.data.name, ...sourceNode.data.aliases], targetNodeId]);
615
- }
616
- await this.deleteNode(sourceId);
617
- }
618
- const mergedNode = await this.getNode(targetNodeId);
619
- if (!mergedNode.success || !mergedNode.data) {
620
- return this.failure(`Target node not found: ${targetNodeId}`);
621
- }
622
- return this.success(mergedNode.data);
621
+ return this.run(async () => {
622
+ for (const sourceId of sourceNodeIds) {
623
+ const sourceNode = unwrap(await this.getNode(sourceId));
624
+ await this.pool.query(`UPDATE ${this.schema}.graph_edges
625
+ SET source_node_id = $1 WHERE source_node_id = $2`, [targetNodeId, sourceId]);
626
+ await this.pool.query(`UPDATE ${this.schema}.graph_edges
627
+ SET target_node_id = $1 WHERE target_node_id = $2`, [targetNodeId, sourceId]);
628
+ await this.pool.query(`DELETE FROM ${this.schema}.graph_edges
629
+ WHERE source_node_id = $1 AND target_node_id = $1`, [targetNodeId]);
630
+ if (sourceNode) {
631
+ await this.pool.query(`UPDATE ${this.schema}.graph_nodes
632
+ SET aliases = array_cat(aliases, $1::TEXT[])
633
+ WHERE id = $2`, [[sourceNode.name, ...sourceNode.aliases], targetNodeId]);
634
+ }
635
+ unwrap(await this.deleteNode(sourceId));
636
+ }
637
+ const mergedNode = unwrap(await this.getNode(targetNodeId));
638
+ if (!mergedNode) {
639
+ return this.failure(`Target node not found: ${targetNodeId}`);
640
+ }
641
+ return this.success(mergedNode);
642
+ });
623
643
  }
624
644
  async clearGraph(agentId) {
625
- await this.initialize();
626
- await this.pool.query(`DELETE FROM ${this.schema}.graph_nodes WHERE agent_id = $1`, [agentId]);
627
- return this.success(undefined);
645
+ return this.run(async () => {
646
+ await this.pool.query(`DELETE FROM ${this.schema}.graph_nodes WHERE agent_id = $1`, [
647
+ agentId,
648
+ ]);
649
+ return this.success(undefined);
650
+ });
628
651
  }
629
652
  async getGraphStats(agentId) {
630
- await this.initialize();
631
- const nodeCountResult = await this.pool.query(`SELECT COUNT(*) as count FROM ${this.schema}.graph_nodes WHERE agent_id = $1`, [agentId]);
632
- const edgeCountResult = await this.pool.query(`SELECT COUNT(*) as count FROM ${this.schema}.graph_edges WHERE agent_id = $1`, [agentId]);
633
- const nodesByTypeResult = await this.pool.query(`SELECT type, COUNT(*) as count FROM ${this.schema}.graph_nodes WHERE agent_id = $1 GROUP BY type`, [agentId]);
634
- const edgesByTypeResult = await this.pool.query(`SELECT type, COUNT(*) as count FROM ${this.schema}.graph_edges WHERE agent_id = $1 GROUP BY type`, [agentId]);
635
- const nodeCount = parseInt(nodeCountResult.rows[0]?.count ?? '0', 10);
636
- const edgeCount = parseInt(edgeCountResult.rows[0]?.count ?? '0', 10);
637
- const nodesByType = {};
638
- for (const row of nodesByTypeResult.rows) {
639
- nodesByType[row.type] = parseInt(row.count, 10);
640
- }
641
- const edgesByType = {};
642
- for (const row of edgesByTypeResult.rows) {
643
- edgesByType[row.type] = parseInt(row.count, 10);
644
- }
645
- return this.success({
646
- nodeCount,
647
- edgeCount,
648
- nodesByType,
649
- edgesByType,
650
- averageEdgesPerNode: nodeCount > 0 ? edgeCount / nodeCount : 0,
651
- maxDepth: 0,
653
+ return this.run(async () => {
654
+ const nodeCountResult = await this.pool.query(`SELECT COUNT(*) as count FROM ${this.schema}.graph_nodes WHERE agent_id = $1`, [agentId]);
655
+ const edgeCountResult = await this.pool.query(`SELECT COUNT(*) as count FROM ${this.schema}.graph_edges WHERE agent_id = $1`, [agentId]);
656
+ const nodesByTypeResult = await this.pool.query(`SELECT type, COUNT(*) as count FROM ${this.schema}.graph_nodes WHERE agent_id = $1 GROUP BY type`, [agentId]);
657
+ const edgesByTypeResult = await this.pool.query(`SELECT type, COUNT(*) as count FROM ${this.schema}.graph_edges WHERE agent_id = $1 GROUP BY type`, [agentId]);
658
+ const nodeCount = parseInt(nodeCountResult.rows[0]?.count ?? '0', 10);
659
+ const edgeCount = parseInt(edgeCountResult.rows[0]?.count ?? '0', 10);
660
+ const nodesByType = {};
661
+ for (const row of nodesByTypeResult.rows) {
662
+ nodesByType[row.type] = parseInt(row.count, 10);
663
+ }
664
+ const edgesByType = {};
665
+ for (const row of edgesByTypeResult.rows) {
666
+ edgesByType[row.type] = parseInt(row.count, 10);
667
+ }
668
+ return this.success({
669
+ nodeCount,
670
+ edgeCount,
671
+ nodesByType,
672
+ edgesByType,
673
+ averageEdgesPerNode: nodeCount > 0 ? edgeCount / nodeCount : 0,
674
+ maxDepth: 0,
675
+ });
652
676
  });
653
677
  }
654
678
  rowToNode(row, includeEmbedding = false) {