memory-bridge/store.js
Zero cf36c06c31 Initial release: memory-bridge v1.0.0
Persistent semantic memory HTTP server for AI agents.
No cloud, no API keys. Local vector index with Ollama/TF-IDF embeddings.
Endpoints: /store, /recall, /forget, /list, /health
2026-03-31 06:36:11 +02:00

64 lines
1.8 KiB
JavaScript

'use strict';
const path = require('path');
const fs = require('fs');
const os = require('os');
const { randomUUID } = require('crypto');
const { LocalIndex } = require('vectra');
const STORE_DIR = path.join(os.homedir(), '.memory-bridge', 'index');
let index = null;
async function init() {
fs.mkdirSync(STORE_DIR, { recursive: true });
index = new LocalIndex(STORE_DIR);
if (!(await index.isIndexCreated())) {
await index.createIndex();
console.log(`Vector index : created at ${STORE_DIR}`);
} else {
console.log(`Vector index : loaded from ${STORE_DIR}`);
}
}
async function add({ text, tags = [], source = '', vector }) {
const id = randomUUID();
const createdAt = new Date().toISOString();
await index.insertItem({ id, vector, metadata: { id, text, tags, source, createdAt } });
return { id, createdAt };
}
async function query({ vector, limit = 10, tags = [] }) {
const topK = tags.length ? Math.min(limit * 3, 300) : limit;
const results = await index.queryItems(vector, topK);
let items = results;
if (tags.length) {
items = results.filter((r) =>
tags.some((t) => (r.item.metadata.tags || []).includes(t))
);
}
return items.slice(0, limit).map((r) => ({
...r.item.metadata,
score: r.score,
}));
}
async function remove(id) {
await index.deleteItem(id);
}
async function list({ page = 1, pageSize = 50 } = {}) {
const all = await index.listItems();
const total = all.length;
const start = (page - 1) * pageSize;
const data = all.slice(start, start + pageSize).map((item) => item.metadata);
return { data, total, page, pageSize, pages: Math.ceil(total / pageSize) };
}
async function count() {
const all = await index.listItems();
return all.length;
}
module.exports = { init, add, query, remove, list, count };