Files
nexusAI/packages/memory-service/src/index.js

165 lines
5.3 KiB
JavaScript

require ('dotenv').config();
const express = require('express');
const {getEnv} = require('@nexusai/shared');
const { getDB } = require('./db');
const episodic = require('./episodic');
const semantic = require('./semantic');
const entities = require('./entities');
const app = express();
app.use(express.json());
const PORT = getEnv('PORT', '3002'); // Default to 3002 if PORT is not set
//initialize database on startup
const db = getDB();
semantic.initCollections()
.then(() => console.log(`QDrant collections ready`))
.catch(err => console.error(`QDrant initialization error:`, err.message));
// Health check endpoint
app.get('/health', (req, res) => {
res.json({ service: 'Memory Service', status: 'healthy' });
});
/************************************ */
/********** Session Routes ********** */
/************************************ */
// Creates a new session with an external ID and optional metadata
app.post('/sessions', (req, res) => {
const {externalId, metadata} = req.body;
if (!externalId) {
return res.status(400).json({ error: 'externalId is required' });
}
try {
const session = episodic.createSession(externalId, metadata);
res.status(201).json(session);
} catch (err) {
res.status(409).json({ error: 'Session already exists', detail: err.message });
}
});
// Retrieves a session by its internal ID
app.get('/sessions/:id', (req, res) => {
const session = episodic.getSession(req.params.id);
if (!session) return res.status(404).json({ error: 'Session not found' });
res.json(session);
});
// Retrieves a session by its external ID
app.get('/sessions/by-external/:externalId', (req, res) => {
const session = episodic.getSessionByExternalId(req.params.externalId);
if (!session) return res.status(404).json({ error: 'Session not found' });
res.json(session);
});
// Updates the session's updated_at timestamp to now
app.delete('/sessions/:id', (req, res) => {
episodic.deleteSession(req.params.id);
res.status(204).send();
});
/************************************* */
/********** Episodic Routes ********** */
/************************************* */
app.post('/episodes', async (req, res) => {
const { sessionId, userMessage, aiResponse, tokenCount, metadata } = req.body;
if (!sessionId || !userMessage || !aiResponse) {
return res.status(400).json({ error: 'sessionId, userMessage and aiResponse are required' });
}
const episode = await episodic.createEpisode(sessionId, userMessage, aiResponse, tokenCount, metadata);
res.status(201).json(episode);
});
// Search MUST come before /:id — otherwise 'search' gets captured as an id
app.get('/episodes/search', (req, res) => {
const { q, limit = 10 } = req.query;
if (!q) return res.status(400).json({ error: 'q (query) parameter is required' });
const results = episodic.searchEpisodes(q, Number(limit));
res.json(results);
});
app.get('/episodes/:id', (req, res) => {
const episode = episodic.getEpisode(req.params.id);
if (!episode) return res.status(404).json({ error: 'Episode not found' });
res.json(episode);
});
app.delete('/episodes/:id', (req, res) => {
episodic.deleteEpisode(req.params.id);
res.status(204).send();
});
/*********************************** */
/********** Entity Routes ********** */
/*********************************** */
//Upsert an entity, creates or updates if already exists
app.post('/entities', (req, res) => {
const {name, type, notes, metadata} = req.body;
if (!name || !type) {
return res.status(400).json({ error: 'name and type are required' });
}
const entity = entities.upsertEntity(name, type, notes, metadata);
res.status(201).json(entity);
});
// Get an entity by ID
app.get('/entities/:id', (req, res) => {
const entity = entities.getEntity(req.params.id);
if (!entity) return res.status(404).json({ error: 'Entity not found' });
res.json(entity);
});
// Get all entities of a given type
app.get('/entities/by-type/:type', (req, res) => {
res.json(entities.getEntitiesByType(req.params.type));
});
// Delete an entity by ID
app.delete('/entities/:id', (req, res) => {
entities.deleteEntity(req.params.id);
res.status(204).send();
});
/***************************************** */
/********** Relationship Routes ********** */
/***************************************** */
// Upsert a relationship between two entities
app.post('/relationships', (req, res) => {
const {fromId, toId, label, metadata } = req.body;
if (!fromId || !toId || !label) {
return res.status(400).json({ error: 'fromId, toId and label are required' });
}
const relationship = entities.upsertRelationship(fromId, toId, label, metadata);
res.status(201).json(relationship);
});
// Get all relationships for a given entity ID
app.get('/entities/:id/relationships', (req, res) => {
res.json(entities.getRelationshipsByEntity(req.params.id));
});
// Delete a specific relationship
app.delete('/relationships', (req, res) => {
const {fromId, toId, label} = req.body;
if (!fromId || !toId || !label) {
return res.status(400).json({ error: 'fromId, toId and label are required' });
}
entities.deleteRelationship(fromId, toId, label);
res.status(204).send();
})
/********************************** */
/********** Start Server ********** */
/********************************** */
app.listen(PORT, () => {
console.log(`Memory Service is running on port ${PORT}`);
});