@melandlabs/memory-store 0.2.0 → 0.2.2

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.
@@ -1,12 +1,22 @@
1
1
  #!/usr/bin/env node
2
- import { serve } from '@hono/node-server';
3
- import { Hono } from 'hono';
4
2
  import { buildMemoryRecordEmbeddingDocument } from '@melandlabs/ai/memory';
5
3
  import { ChromaVectorStore } from '@melandlabs/rag';
6
4
  import { mkdirSync } from 'fs';
7
5
  import { homedir } from 'os';
8
- import { join, dirname } from 'path';
6
+ import { dirname, join } from 'path';
9
7
  import { SQLiteRawMessageManager } from '@melandlabs/sqlite';
8
+ import { serve } from '@hono/node-server';
9
+ import { Hono } from 'hono';
10
+
11
+ var __defProp = Object.defineProperty;
12
+ var __getOwnPropNames = Object.getOwnPropertyNames;
13
+ var __esm = (fn, res) => function __init() {
14
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
15
+ };
16
+ var __export = (target, all) => {
17
+ for (var name in all)
18
+ __defProp(target, name, { get: all[name], enumerable: true });
19
+ };
10
20
 
11
21
  // src/contracts.ts
12
22
  function normalizeTimestampToMs(value) {
@@ -30,14 +40,10 @@ function rawMessageToMemoryRecord(message) {
30
40
  embeddingContentHash: message.embeddingContentHash
31
41
  };
32
42
  }
33
-
34
- // src/storage/chroma-memory-index.ts
35
- var DEFAULT_RAW_MESSAGES_COLLECTION = "opencontext_raw_messages";
36
- var RAW_VECTOR_BACKEND_ENV_KEYS = [
37
- "RAW_MESSAGE_VECTOR_STORE_BACKEND",
38
- "MEMORY_VECTOR_STORE_BACKEND",
39
- "VECTOR_STORE_BACKEND"
40
- ];
43
+ var init_contracts = __esm({
44
+ "src/contracts.ts"() {
45
+ }
46
+ });
41
47
  function getBackend(keys) {
42
48
  for (const key of keys) {
43
49
  const value = process.env[key]?.trim().toLowerCase();
@@ -140,21 +146,51 @@ async function searchRawMessagesWithChroma(input) {
140
146
  metadata: result.metadata ?? {}
141
147
  })).slice(0, input.limit);
142
148
  }
149
+ var DEFAULT_RAW_MESSAGES_COLLECTION, RAW_VECTOR_BACKEND_ENV_KEYS;
150
+ var init_chroma_memory_index = __esm({
151
+ "src/storage/chroma-memory-index.ts"() {
152
+ init_contracts();
153
+ DEFAULT_RAW_MESSAGES_COLLECTION = "opencontext_raw_messages";
154
+ RAW_VECTOR_BACKEND_ENV_KEYS = [
155
+ "RAW_MESSAGE_VECTOR_STORE_BACKEND",
156
+ "MEMORY_VECTOR_STORE_BACKEND",
157
+ "VECTOR_STORE_BACKEND"
158
+ ];
159
+ }
160
+ });
143
161
 
144
162
  // src/storage/postgres-raw-message-factory.ts
145
- var factory = null;
146
163
  async function resolvePostgresFactory(env) {
147
- return null;
164
+ if (!factory) return null;
165
+ return await factory(env);
148
166
  }
149
167
  function hasPostgresFactory() {
150
168
  return factory !== null;
151
169
  }
152
- var manager = null;
153
- var DEFAULT_SQLITE_PATH = join(homedir(), ".opencontext", "memory", "store.db");
170
+ var factory;
171
+ var init_postgres_raw_message_factory = __esm({
172
+ "src/storage/postgres-raw-message-factory.ts"() {
173
+ factory = null;
174
+ }
175
+ });
176
+
177
+ // src/storage/sqlite-raw-message-store.ts
178
+ var sqlite_raw_message_store_exports = {};
179
+ __export(sqlite_raw_message_store_exports, {
180
+ __resetSQLiteRawMessageManagerForTests: () => __resetSQLiteRawMessageManagerForTests,
181
+ closeSQLiteRawMessageManager: () => closeSQLiteRawMessageManager,
182
+ getSQLiteRawMessageManager: () => getSQLiteRawMessageManager,
183
+ isSQLiteRawMessageStorageAvailable: () => isSQLiteRawMessageStorageAvailable,
184
+ lexicalSearchRawMessages: () => lexicalSearchRawMessages,
185
+ resolveSQLiteRawMessageDbPath: () => resolveSQLiteRawMessageDbPath
186
+ });
154
187
  function resolveSQLiteRawMessageDbPath() {
155
188
  const fromEnv = process.env.MEMORY_STORE_DB_PATH?.trim();
156
189
  return fromEnv && fromEnv.length > 0 ? fromEnv : DEFAULT_SQLITE_PATH;
157
190
  }
191
+ function isSQLiteRawMessageStorageAvailable(_env) {
192
+ return true;
193
+ }
158
194
  async function getSQLiteRawMessageManager(_env) {
159
195
  if (!manager) {
160
196
  const dbPath = resolveSQLiteRawMessageDbPath();
@@ -174,8 +210,35 @@ async function closeSQLiteRawMessageManager() {
174
210
  await manager.close();
175
211
  manager = null;
176
212
  }
213
+ function __resetSQLiteRawMessageManagerForTests() {
214
+ manager = null;
215
+ }
216
+ async function lexicalSearchRawMessages(input) {
217
+ const mgr = await getSQLiteRawMessageManager();
218
+ if (typeof mgr.lexicalSearchMessages !== "function") {
219
+ return [];
220
+ }
221
+ return mgr.lexicalSearchMessages(input);
222
+ }
223
+ var manager, DEFAULT_SQLITE_PATH;
224
+ var init_sqlite_raw_message_store = __esm({
225
+ "src/storage/sqlite-raw-message-store.ts"() {
226
+ init_chroma_memory_index();
227
+ manager = null;
228
+ DEFAULT_SQLITE_PATH = join(homedir(), ".opencontext", "memory", "store.db");
229
+ }
230
+ });
177
231
 
178
232
  // src/storage/raw-message-store.ts
233
+ var raw_message_store_exports = {};
234
+ __export(raw_message_store_exports, {
235
+ closeRawMessageStore: () => closeRawMessageStore,
236
+ configureRawMessageStore: () => configureRawMessageStore,
237
+ createRawMessageStore: () => createRawMessageStore,
238
+ getRawMessageManager: () => getRawMessageManager,
239
+ getRawMessageStorageBackend: () => getRawMessageStorageBackend,
240
+ isRawMessageStorageAvailable: () => isRawMessageStorageAvailable
241
+ });
179
242
  function resolveBackend(env) {
180
243
  const explicit = process.env.OPENCONTEXT_MEMORY_STORE_BACKEND?.trim().toLowerCase();
181
244
  if (explicit === "postgres") return "postgres";
@@ -185,7 +248,7 @@ function resolveEnv(_env) {
185
248
  return {};
186
249
  }
187
250
  function createRawMessageStore(options = {}) {
188
- resolveEnv(options.env);
251
+ const env = resolveEnv(options.env);
189
252
  return {
190
253
  getBackend() {
191
254
  return resolveBackend();
@@ -195,7 +258,7 @@ function createRawMessageStore(options = {}) {
195
258
  },
196
259
  async getManager() {
197
260
  if (resolveBackend() === "postgres") {
198
- const pg = await resolvePostgresFactory();
261
+ const pg = await resolvePostgresFactory(env);
199
262
  if (!pg) {
200
263
  throw new Error(
201
264
  "OPENCONTEXT_MEMORY_STORE_BACKEND=postgres but no Postgres factory is registered. Call registerPostgresFactory() at startup, or unset the env var to use the default SQLite backend."
@@ -210,14 +273,47 @@ function createRawMessageStore(options = {}) {
210
273
  }
211
274
  };
212
275
  }
213
- var moduleStore = null;
214
- var moduleConfig = null;
276
+ function configureRawMessageStore(config) {
277
+ moduleConfig = config;
278
+ moduleStore = createRawMessageStore({ env: config.env });
279
+ return moduleStore;
280
+ }
281
+ function getRawMessageStorageBackend() {
282
+ if (!moduleStore) {
283
+ moduleStore = createRawMessageStore({ env: moduleConfig?.env });
284
+ }
285
+ return moduleStore.getBackend();
286
+ }
215
287
  function isRawMessageStorageAvailable() {
216
288
  if (!moduleStore) {
217
289
  moduleStore = createRawMessageStore({ env: moduleConfig?.env });
218
290
  }
219
291
  return moduleStore.isAvailable();
220
292
  }
293
+ async function getRawMessageManager() {
294
+ if (!moduleStore) {
295
+ moduleStore = createRawMessageStore({ env: moduleConfig?.env });
296
+ }
297
+ return moduleStore.getManager();
298
+ }
299
+ async function closeRawMessageStore() {
300
+ if (!moduleStore) return;
301
+ await moduleStore.close();
302
+ moduleStore = null;
303
+ }
304
+ var moduleStore, moduleConfig;
305
+ var init_raw_message_store = __esm({
306
+ "src/storage/raw-message-store.ts"() {
307
+ init_postgres_raw_message_factory();
308
+ init_sqlite_raw_message_store();
309
+ moduleStore = null;
310
+ moduleConfig = null;
311
+ }
312
+ });
313
+
314
+ // src/search/unified-search.ts
315
+ init_chroma_memory_index();
316
+ init_raw_message_store();
221
317
 
222
318
  // src/search/utilities.ts
223
319
  var DEFAULT_LIMIT = 10;
@@ -361,7 +457,7 @@ function createUnifiedSearch(deps = {}) {
361
457
  warnings.push({
362
458
  source: "memory",
363
459
  code: "memory_search_failed",
364
- message: error.message ?? "memory_search_failed"
460
+ message: `Memory search failed: ${error.message ?? "Unknown error"}. Using keyword search fallback.`
365
461
  });
366
462
  }
367
463
  } else {
@@ -401,13 +497,7 @@ function createUnifiedSearch(deps = {}) {
401
497
  message: error.message ?? "insights_search_failed"
402
498
  });
403
499
  }
404
- } else if (sources.includes("insights")) {
405
- warnings.push({
406
- source: "insights",
407
- code: "insights_search_not_configured",
408
- message: "Insights search is not configured for this host."
409
- });
410
- }
500
+ } else if (sources.includes("insights")) ;
411
501
  const knowledgeHits = [];
412
502
  if (sources.includes("knowledge") && typeof deps.searchKnowledge === "function") {
413
503
  try {
@@ -432,13 +522,7 @@ function createUnifiedSearch(deps = {}) {
432
522
  message: error.message ?? "knowledge_search_failed"
433
523
  });
434
524
  }
435
- } else if (sources.includes("knowledge")) {
436
- warnings.push({
437
- source: "knowledge",
438
- code: "knowledge_search_not_configured",
439
- message: "Knowledge search is not configured for this host."
440
- });
441
- }
525
+ } else if (sources.includes("knowledge")) ;
442
526
  const merged = mergeAcrossSources({
443
527
  memorySubs,
444
528
  insightHits,
@@ -456,7 +540,33 @@ function createUnifiedSearch(deps = {}) {
456
540
  }
457
541
  async function runMemorySource(input, limit, threshold, warnings) {
458
542
  if (typeof deps.embedQuery !== "function") {
459
- throw new Error("embedQuery is not configured");
543
+ warnings.push({
544
+ source: "memory",
545
+ code: "memory_lexical_search_fallback",
546
+ message: "Semantic search not configured, using keyword search as fallback"
547
+ });
548
+ const { lexicalSearchRawMessages: lexicalSearchRawMessages2 } = await Promise.resolve().then(() => (init_sqlite_raw_message_store(), sqlite_raw_message_store_exports));
549
+ const keywords = deriveLexicalKeywords(input.query);
550
+ let lexical2 = [];
551
+ if (keywords.length > 0) {
552
+ try {
553
+ const filters2 = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
554
+ const results = await Promise.all(
555
+ filters2.map(
556
+ (botId) => lexicalSearchRawMessages2({
557
+ userId: input.userId,
558
+ keywords,
559
+ limit: Math.ceil(limit / filters2.length),
560
+ botId
561
+ })
562
+ )
563
+ );
564
+ lexical2 = results.flat().filter(Boolean).map(toMemoryResult);
565
+ } catch (error) {
566
+ logger.warn?.("[memory-store] Default lexical search failed:", error);
567
+ }
568
+ }
569
+ return { semantic: [], lexical: lexical2 };
460
570
  }
461
571
  const queryEmbedding = await deps.embedQuery({
462
572
  userId: input.userId,
@@ -503,6 +613,28 @@ function createUnifiedSearch(deps = {}) {
503
613
  )
504
614
  )).flat().filter(isRawMemorySemanticResult).map(toMemoryResult);
505
615
  }
616
+ if (semantic.length === 0) {
617
+ try {
618
+ const { getRawMessageManager: getRawMessageManager2 } = await Promise.resolve().then(() => (init_raw_message_store(), raw_message_store_exports));
619
+ const manager2 = await getRawMessageManager2();
620
+ if (typeof manager2.searchMessagesSemantically === "function") {
621
+ const results = await manager2.searchMessagesSemantically({
622
+ userId: input.userId,
623
+ queryEmbedding,
624
+ limit
625
+ });
626
+ semantic = results.map((r) => ({
627
+ type: "memory",
628
+ id: r.id,
629
+ content: r.content,
630
+ similarity: r.similarity,
631
+ metadata: r.metadata ?? {}
632
+ }));
633
+ }
634
+ } catch (error) {
635
+ logger.warn?.("[memory-store] SQLite semantic search failed:", error);
636
+ }
637
+ }
506
638
  let lexical = [];
507
639
  if (typeof deps.searchRawMessagesLexical === "function") {
508
640
  const keywords = deriveLexicalKeywords(input.query);
@@ -585,6 +717,8 @@ function mergeAcrossSources(input) {
585
717
  }
586
718
 
587
719
  // src/http.ts
720
+ init_chroma_memory_index();
721
+ init_raw_message_store();
588
722
  async function embedMissingMessages(messages, deps) {
589
723
  if (typeof deps.embedQuery !== "function") {
590
724
  return messages;
@@ -619,7 +753,23 @@ async function startHttpServer(options = {}) {
619
753
  const rawStore = createRawMessageStore({
620
754
  env: options.env
621
755
  });
622
- const search = createUnifiedSearch(options.unified);
756
+ const manager2 = await rawStore.getManager();
757
+ const search = createUnifiedSearch({
758
+ ...options.unified,
759
+ searchRawMessagesLexical: async (input) => {
760
+ if (typeof manager2.lexicalSearchMessages === "function") {
761
+ const results = await manager2.lexicalSearchMessages(input);
762
+ return results.filter(Boolean).map((r) => ({
763
+ type: "memory",
764
+ id: r.id,
765
+ content: r.content,
766
+ similarity: r.similarity,
767
+ metadata: r.metadata ?? {}
768
+ }));
769
+ }
770
+ return [];
771
+ }
772
+ });
623
773
  const app = new Hono();
624
774
  app.get("/health", (c) => c.json({ ok: true, store: "memory", ts: Date.now() }));
625
775
  app.post("/v1/search", async (c) => {
@@ -647,20 +797,20 @@ async function startHttpServer(options = {}) {
647
797
  if (!Array.isArray(body.messages) || !body.userId) {
648
798
  return c.json({ error: "userId and messages[] required" }, 400);
649
799
  }
650
- const manager2 = await rawStore.getManager();
800
+ const manager3 = await rawStore.getManager();
651
801
  const incoming = body.messages;
652
802
  const messages = body.embedOnInsert === true ? await embedMissingMessages(
653
803
  incoming.map((m) => ({ ...m, userId: m.userId ?? body.userId })),
654
804
  options.unified ?? {}
655
805
  ) : incoming.map((m) => ({ ...m, userId: m.userId ?? body.userId }));
656
806
  let result;
657
- if (typeof manager2.upsertRawMessages === "function") {
658
- result = await manager2.upsertRawMessages({
807
+ if (typeof manager3.upsertRawMessages === "function") {
808
+ result = await manager3.upsertRawMessages({
659
809
  userId: body.userId,
660
810
  messages
661
811
  });
662
- } else if (typeof manager2.storeMessages === "function") {
663
- const ids = await manager2.storeMessages(messages);
812
+ } else if (typeof manager3.storeMessages === "function") {
813
+ const ids = await manager3.storeMessages(messages);
664
814
  result = { inserted: ids.length, ids };
665
815
  } else {
666
816
  return c.json(
@@ -675,14 +825,33 @@ async function startHttpServer(options = {}) {
675
825
  } catch (error) {
676
826
  console.warn("[memory-store/http] chroma upsert failed:", error);
677
827
  }
828
+ try {
829
+ const messagesWithEmbeddings = messages.filter(
830
+ (m) => Array.isArray(m.embedding) && m.embedding.length > 0
831
+ );
832
+ if (messagesWithEmbeddings.length > 0) {
833
+ for (const message of messagesWithEmbeddings) {
834
+ if (typeof manager3.upsertVectorForMessage === "function") {
835
+ manager3.upsertVectorForMessage(message.messageId, message.embedding);
836
+ }
837
+ }
838
+ console.log(
839
+ "[memory-store/http] Updated sqlite-vec vector table for",
840
+ messagesWithEmbeddings.length,
841
+ "message(s)"
842
+ );
843
+ }
844
+ } catch (error) {
845
+ console.warn("[memory-store/http] sqlite-vec vector update failed:", error);
846
+ }
678
847
  return c.json({ ok: true, count: messages.length, result });
679
848
  });
680
849
  app.get("/v1/raw-messages/:id", async (c) => {
681
850
  const id = c.req.param("id");
682
851
  const userId = c.req.query("userId");
683
852
  if (!userId) return c.json({ error: "userId query param required" }, 400);
684
- const manager2 = await rawStore.getManager();
685
- const row = await manager2.getMessageById?.(id);
853
+ const manager3 = await rawStore.getManager();
854
+ const row = await manager3.getMessageById?.(id);
686
855
  if (!row) return c.json({ error: "not found" }, 404);
687
856
  return c.json({ message: row });
688
857
  });
@@ -697,7 +866,11 @@ async function startHttpServer(options = {}) {
697
866
  };
698
867
  }
699
868
 
869
+ // src/server/cli-shared.ts
870
+ init_raw_message_store();
871
+
700
872
  // src/storage/sqlite-vector-index.ts
873
+ init_sqlite_raw_message_store();
701
874
  function getMemoryVectorStoreBackend() {
702
875
  const backend = (process.env.INSIGHT_VECTOR_STORE_BACKEND || process.env.MEMORY_VECTOR_STORE_BACKEND || process.env.VECTOR_STORE_BACKEND || "sqlite-vec").toLowerCase();
703
876
  return backend === "chroma" ? "chroma" : "sqlite-vec";
@@ -811,11 +984,26 @@ async function buildUnified(args) {
811
984
  const wantsAiRag = args.embeddingProvider === "local" || args.memoryBackend === "chroma" || args.insightsBackend === "chroma" || args.knowledgeBackend === "chroma";
812
985
  const aiRag = wantsAiRag ? await loadAiRag() : null;
813
986
  if (args.embeddingProvider === "local") {
814
- const provider = new aiRag.LocalTransformersEmbeddingProvider({ modelName: args.embeddingModel });
815
- unified.embedQuery = async ({ query }) => provider.embedQuery(query);
816
- log(
817
- `embedQuery wired via LocalTransformersEmbeddingProvider (model=${args.embeddingModel ?? "Xenova/all-MiniLM-L6-v2"})`
818
- );
987
+ try {
988
+ if (!aiRag) {
989
+ throw new Error("ai-rag modules not loaded");
990
+ }
991
+ const provider = new aiRag.LocalTransformersEmbeddingProvider({ modelName: args.embeddingModel });
992
+ unified.embedQuery = async ({ query }) => provider.embedQuery(query);
993
+ log(
994
+ `embedQuery wired via LocalTransformersEmbeddingProvider (model=${args.embeddingModel ?? "Xenova/all-MiniLM-L6-v2"})`
995
+ );
996
+ } catch (error) {
997
+ log(
998
+ `Warning: Failed to initialize LocalTransformersEmbeddingProvider: ${error.message}`
999
+ );
1000
+ log(
1001
+ "Semantic search will be disabled. The server will continue with keyword-only search."
1002
+ );
1003
+ log(
1004
+ "To fix: Ensure the model is downloaded or check your network connection to huggingface.co"
1005
+ );
1006
+ }
819
1007
  } else if (args.embeddingProvider === "openrouter") {
820
1008
  const apiKey = process.env.OPENROUTER_API_KEY;
821
1009
  if (!apiKey) {