const { getEnv, SERVICES, EPISODIC } = require('@nexusai/shared'); const BASE_URL = getEnv('MEMORY_SERVICE_URL', SERVICES.MEMORY_URL); //function to get session by external id, returns null if not found, throws error for other issues async function getSessionByExternalId(externalId) { const res = await fetch(`${BASE_URL}/sessions/by-external/${externalId}`); if (res.status === 404) return null; // Not found or bad request if (!res.ok) throw new Error(`Memory service error: ${res.status} ${res.statusText}`); // Other errors return res.json(); } // create a new session with an external ID, returns the created session async function createSession(externalId) { const res = await fetch(`${BASE_URL}/sessions`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ externalId }) }); if (!res.ok) throw new Error(`Failed to create sessions: ${res.status} ${res.statusText}`); return res.json(); } async function getRecentEpisodes(sessionId, limit = EPISODIC.DEFAULT_SESSIONS_LIMIT) { const res = await fetch(`${BASE_URL}/sessions/${sessionId}/episodes?limit=${limit}`); if (!res.ok) throw new Error(`Failed to fetch episodes: ${res.status} ${res.statusText}`); return res.json(); } async function createEpisode(sessionId, userMessage, aiResponse, tokenCount, projectId=null) { const res = await fetch(`${BASE_URL}/episodes`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId, userMessage, aiResponse, tokenCount, projectId }) }); if (!res.ok) throw new Error(`Failed to create episode: ${res.status} ${res.statusText}`); return res.json(); } async function getEpisodeById(episodeId) { const res = await fetch(`${BASE_URL}/episodes/${episodeId}`); if (res.status === 404) return null; if (!res.ok) throw new Error(`Failed to fetch episode: ${res.status}`); return res.json(); } async function getSessionHistory(sessionId, limit = EPISODIC.DEFAULT_SESSIONS_LIMIT, offset = EPISODIC.DEFAULT_OFFSET) { const res = await fetch( `${BASE_URL}/sessions/${sessionId}/episodes?limit=${limit}&offset=${offset}` ); if (res.status === 404 ) return null; if (!res.ok) throw new Error(`Failed to fetch history: ${res.status}`); return res.json(); } async function getSessions(limit = EPISODIC.DEFAULT_SESSIONS_LIMIT, offset = EPISODIC.DEFAULT_OFFSET, projectId = null) { const url = new URL(`${BASE_URL}/sessions`); url.searchParams.set('limit', limit); url.searchParams.set('offset', offset); if (projectId) url.searchParams.set('projectId', projectId); const res = await fetch(url.toString()); if (!res.ok) throw new Error(`Failed to fetch sessions: ${res.status}`); return res.json(); } async function updateSession(externalId, { name, projectId }) { const res = await fetch(`${BASE_URL}/sessions/by-external/${externalId}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, projectId }), }); if (!res.ok) throw new Error(`Failed to update session: ${res.status}`); return res.json(); } async function deleteSession(externalId) { const res = await fetch(`${BASE_URL}/sessions/by-external/${externalId}`, { method: 'DELETE', }); if (!res.ok) throw new Error(`Failed to delete session: ${res.status}`); } /******** PROJECTS ********* */ async function createProject({ name, description, colour, icon }) { const res = await fetch(`${BASE_URL}/projects`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, description, colour, icon }) }); if (!res.ok) throw new Error(`Failed to create project: ${res.status}`); return res.json(); } async function getProjects() { const res = await fetch(`${BASE_URL}/projects`); if (!res.ok) throw new Error(`Failed to fetch projects: ${res.status}`); return res.json(); } async function updateProject(id, fields = {}) { const res = await fetch(`${BASE_URL}/projects/${id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(fields) }); if (!res.ok) throw new Error(`Failed to update project: ${res.status}`); return res.json(); } async function deleteProject(id) { const res = await fetch(`${BASE_URL}/projects/${id}`, { method: 'DELETE' }); if (!res.ok) throw new Error(`Failed to delete project: ${res.status}`); } async function getProjectSessions(projectId) { const url = new URL(`${BASE_URL}/sessions`); url.searchParams.set('limit', 200); // generous upper bound url.searchParams.set('offset', 0); url.searchParams.set('projectId', projectId); const res = await fetch(url.toString()); if (!res.ok) throw new Error(`Failed to fetch project sessions: ${res.status}`); return res.json(); // returns array of session objects } async function getProject(id) { const res = await fetch(`${BASE_URL}/projects/${id}`); if (res.status === 404) return null; if (!res.ok) throw new Error(`Failed to fetch project: ${res.status}`); return res.json(); } async function getEpisodes({ limit = 50, offset = 0, sessionId, q } = {}) { const url = new URL(`${BASE_URL}/episodes`); url.searchParams.set('limit', limit); url.searchParams.set('offset', offset); if (sessionId) url.searchParams.set('sessionId', sessionId); if (q) url.searchParams.set('q', q); const res = await fetch(url.toString()); if (!res.ok) throw new Error(`Failed to fetch episodes: ${res.status}`); return res.json(); } async function deleteEpisode(id) { const res = await fetch(`${BASE_URL}/episodes/${id}`, { method: 'DELETE' }); if (!res.ok) throw new Error(`Failed to delete episode: ${res.status}`); } async function getSummariesBySession(sessionId) { const res = await fetch(`${BASE_URL}/sessions/${sessionId}/summaries`); if (!res.ok) throw new Error(`Failed to fetch summaries: ${res.status}`); return res.json(); } async function createSummary({ sessionId, projectId, content, tokenCount, episodeRange }) { const res = await fetch(`${BASE_URL}/summaries`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId, projectId, content, tokenCount, episodeRange }), }); if (!res.ok) throw new Error(`Failed to create summary: ${res.status}`); return res.json(); } async function updateSummary(id, { content, tokenCount, episodeRange }) { const res = await fetch(`${BASE_URL}/summaries/${id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ content, tokenCount, episodeRange }), }); if (!res.ok) throw new Error(`Failed to update summary: ${res.status}`); return res.json(); } async function getSummariesByProject(projectId) { const res = await fetch(`${BASE_URL}/projects/${projectId}/summaries`); if (!res.ok) throw new Error(`Failed to fetch summaries: ${res.status}`); return res.json(); } async function generateProjectSummary(projectId) { const res = await fetch(`${BASE_URL}/projects/${projectId}/summarize`, { method: 'POST', }); if (!res.ok) throw new Error(`Failed to generate project summary: ${res.status}`); return res.json(); } async function getProjectOverviewSummary(projectId) { const res = await fetch(`${BASE_URL}/projects/${projectId}/overview`); if (!res.ok) throw new Error(`Failed to fetch project overview: ${res.status}`); return res.json(); // null if none exists yet } async function searchEpisodes(query, { limit = 10, sessionIds = null } = {}) { const url = new URL(`${BASE_URL}/episodes/search`); url.searchParams.set('q', query); url.searchParams.set('limit', limit); if (sessionIds?.length) url.searchParams.set('sessionIds', sessionIds.join(',')); const res = await fetch(url.toString()); if (!res.ok) throw new Error(`FTS search error: ${res.status}`); return res.json(); } async function getEpisodesByEntities(entityIds) { const res = await fetch(`${BASE_URL}/episodes/by-entities`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ entityIds }), }); if (!res.ok) throw new Error(`Episodes-by-entities error: ${res.status}`); return res.json(); // { episodeIds: [...] } } async function getEntityIdsBySession(sessionId){ const res = await fetch(`${BASE_URL}/sessions/${sessionId}/entity-ids`); if (!res.ok) throw new Error(`Entity-ids-by-session error: ${res.status}`); const {entityIds} = await res.jaon(); return entityIds; } module.exports = { getSessionByExternalId, createSession, getRecentEpisodes, createEpisode, getEpisodeById, getSessionHistory, getSessions, updateSession, deleteSession, createProject, getProjects, updateProject, deleteProject, getProjectSessions, getProject, getEpisodes, deleteEpisode, getSummariesBySession, createSummary, updateSummary, getSummariesByProject, generateProjectSummary, getProjectOverviewSummary, searchEpisodes, getEpisodesByEntities, getEntityIdsBySession, }