@melandlabs/memory-store 0.1.1 → 0.1.3
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.
- package/dist/{config-MlK9f9CM.d.ts → config-D6MzksfT.d.ts} +20 -14
- package/dist/http.d.ts +14 -2
- package/dist/http.js +80 -45
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +28 -39
- package/dist/index.js.map +1 -1
- package/dist/mcp.d.ts +2 -2
- package/dist/mcp.js +83 -45
- package/dist/mcp.js.map +1 -1
- package/dist/search/unified-search.d.ts +1 -1
- package/dist/search/unified-search.js +28 -39
- package/dist/search/unified-search.js.map +1 -1
- package/dist/server/cli-http.js +411 -57
- package/dist/server/cli-http.js.map +1 -1
- package/dist/server/cli-mcp.js +423 -46
- package/dist/server/cli-mcp.js.map +1 -1
- package/dist/server/cli-shared.d.ts +39 -0
- package/dist/server/cli-shared.js +373 -0
- package/dist/server/cli-shared.js.map +1 -0
- package/dist/storage/postgres-raw-message-factory.d.ts +1 -1
- package/dist/storage/raw-message-store.d.ts +12 -5
- package/dist/storage/raw-message-store.js +28 -39
- package/dist/storage/raw-message-store.js.map +1 -1
- package/dist/storage/sqlite-raw-message-store.d.ts +21 -6
- package/dist/storage/sqlite-raw-message-store.js +12 -18
- package/dist/storage/sqlite-raw-message-store.js.map +1 -1
- package/dist/storage/sqlite-vector-index.d.ts +4 -3
- package/dist/storage/sqlite-vector-index.js +23 -16
- package/dist/storage/sqlite-vector-index.js.map +1 -1
- package/package.json +15 -8
package/dist/server/cli-http.js
CHANGED
|
@@ -4,7 +4,8 @@ import { Hono } from 'hono';
|
|
|
4
4
|
import { buildMemoryRecordEmbeddingDocument } from '@melandlabs/ai/memory';
|
|
5
5
|
import { ChromaVectorStore } from '@melandlabs/rag';
|
|
6
6
|
import { mkdirSync } from 'fs';
|
|
7
|
-
import {
|
|
7
|
+
import { homedir } from 'os';
|
|
8
|
+
import { join, dirname } from 'path';
|
|
8
9
|
import { SQLiteRawMessageManager } from '@melandlabs/sqlite';
|
|
9
10
|
|
|
10
11
|
// src/contracts.ts
|
|
@@ -149,16 +150,14 @@ function hasPostgresFactory() {
|
|
|
149
150
|
return factory !== null;
|
|
150
151
|
}
|
|
151
152
|
var manager = null;
|
|
152
|
-
|
|
153
|
-
|
|
153
|
+
var DEFAULT_SQLITE_PATH = join(homedir(), ".opencontext", "memory", "store.db");
|
|
154
|
+
function resolveSQLiteRawMessageDbPath() {
|
|
155
|
+
const fromEnv = process.env.MEMORY_STORE_DB_PATH?.trim();
|
|
156
|
+
return fromEnv && fromEnv.length > 0 ? fromEnv : DEFAULT_SQLITE_PATH;
|
|
154
157
|
}
|
|
155
|
-
async function getSQLiteRawMessageManager(
|
|
156
|
-
const e = resolveEnv(env);
|
|
157
|
-
if (!e.isTauriMode()) {
|
|
158
|
-
throw new Error("SQLite raw message storage is only available in Tauri mode.");
|
|
159
|
-
}
|
|
158
|
+
async function getSQLiteRawMessageManager(_env) {
|
|
160
159
|
if (!manager) {
|
|
161
|
-
const dbPath =
|
|
160
|
+
const dbPath = resolveSQLiteRawMessageDbPath();
|
|
162
161
|
mkdirSync(dirname(dbPath), { recursive: true });
|
|
163
162
|
manager = new SQLiteRawMessageManager({
|
|
164
163
|
dbPath,
|
|
@@ -175,49 +174,39 @@ async function closeSQLiteRawMessageManager() {
|
|
|
175
174
|
await manager.close();
|
|
176
175
|
manager = null;
|
|
177
176
|
}
|
|
178
|
-
function resolveEnv(env) {
|
|
179
|
-
if (env) return env;
|
|
180
|
-
return {
|
|
181
|
-
isTauriMode: () => process.env.IS_TAURI === "true" || typeof process.env.TAURI_MODE === "string",
|
|
182
|
-
getTauriDbPath: () => process.env.TAURI_DB_PATH ?? "",
|
|
183
|
-
getTauriDataDir: () => process.env.TAURI_DATA_DIR ?? ""
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
177
|
|
|
187
178
|
// src/storage/raw-message-store.ts
|
|
188
|
-
function
|
|
189
|
-
|
|
190
|
-
return
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
};
|
|
179
|
+
function resolveBackend(env) {
|
|
180
|
+
const explicit = process.env.OPENCONTEXT_MEMORY_STORE_BACKEND?.trim().toLowerCase();
|
|
181
|
+
if (explicit === "postgres") return "postgres";
|
|
182
|
+
return "sqlite";
|
|
183
|
+
}
|
|
184
|
+
function resolveEnv(_env) {
|
|
185
|
+
return {};
|
|
195
186
|
}
|
|
196
187
|
function createRawMessageStore(options = {}) {
|
|
197
|
-
|
|
188
|
+
resolveEnv(options.env);
|
|
198
189
|
return {
|
|
199
190
|
getBackend() {
|
|
200
|
-
return
|
|
191
|
+
return resolveBackend();
|
|
201
192
|
},
|
|
202
193
|
isAvailable() {
|
|
203
|
-
return
|
|
194
|
+
return resolveBackend() === "sqlite" || hasPostgresFactory();
|
|
204
195
|
},
|
|
205
196
|
async getManager() {
|
|
206
|
-
if (
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
197
|
+
if (resolveBackend() === "postgres") {
|
|
198
|
+
const pg = await resolvePostgresFactory();
|
|
199
|
+
if (!pg) {
|
|
200
|
+
throw new Error(
|
|
201
|
+
"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."
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
return pg;
|
|
214
205
|
}
|
|
215
|
-
return
|
|
206
|
+
return await getSQLiteRawMessageManager();
|
|
216
207
|
},
|
|
217
208
|
async close() {
|
|
218
|
-
|
|
219
|
-
await closeSQLiteRawMessageManager();
|
|
220
|
-
}
|
|
209
|
+
await closeSQLiteRawMessageManager();
|
|
221
210
|
}
|
|
222
211
|
};
|
|
223
212
|
}
|
|
@@ -473,6 +462,34 @@ function createUnifiedSearch(deps = {}) {
|
|
|
473
462
|
}
|
|
474
463
|
|
|
475
464
|
// src/http.ts
|
|
465
|
+
async function embedMissingMessages(messages, deps) {
|
|
466
|
+
if (typeof deps.embedQuery !== "function") {
|
|
467
|
+
return messages;
|
|
468
|
+
}
|
|
469
|
+
const out = [];
|
|
470
|
+
for (const message of messages) {
|
|
471
|
+
if (Array.isArray(message.embedding) && message.embedding.length > 0) {
|
|
472
|
+
out.push(message);
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
if (typeof message.content !== "string" || message.content.length === 0) {
|
|
476
|
+
out.push(message);
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
const vector = await deps.embedQuery({
|
|
480
|
+
userId: message.userId,
|
|
481
|
+
query: message.content
|
|
482
|
+
});
|
|
483
|
+
out.push({
|
|
484
|
+
...message,
|
|
485
|
+
embedding: vector,
|
|
486
|
+
embeddingModel: message.embeddingModel ?? "server",
|
|
487
|
+
embeddingDimensions: vector.length,
|
|
488
|
+
embeddingUpdatedAt: Date.now()
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
return out;
|
|
492
|
+
}
|
|
476
493
|
async function startHttpServer(options = {}) {
|
|
477
494
|
const port = options.port ?? Number.parseInt(process.env.MEMORY_HTTP_PORT ?? "7421", 10);
|
|
478
495
|
const host = options.host ?? process.env.MEMORY_HTTP_HOST ?? "127.0.0.1";
|
|
@@ -508,16 +525,34 @@ async function startHttpServer(options = {}) {
|
|
|
508
525
|
return c.json({ error: "userId and messages[] required" }, 400);
|
|
509
526
|
}
|
|
510
527
|
const manager2 = await rawStore.getManager();
|
|
511
|
-
const
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
528
|
+
const incoming = body.messages;
|
|
529
|
+
const messages = body.embedOnInsert === true ? await embedMissingMessages(
|
|
530
|
+
incoming.map((m) => ({ ...m, userId: m.userId ?? body.userId })),
|
|
531
|
+
options.unified ?? {}
|
|
532
|
+
) : incoming.map((m) => ({ ...m, userId: m.userId ?? body.userId }));
|
|
533
|
+
let result;
|
|
534
|
+
if (typeof manager2.upsertRawMessages === "function") {
|
|
535
|
+
result = await manager2.upsertRawMessages({
|
|
536
|
+
userId: body.userId,
|
|
537
|
+
messages
|
|
538
|
+
});
|
|
539
|
+
} else if (typeof manager2.storeMessages === "function") {
|
|
540
|
+
const ids = await manager2.storeMessages(messages);
|
|
541
|
+
result = { inserted: ids.length, ids };
|
|
542
|
+
} else {
|
|
543
|
+
return c.json(
|
|
544
|
+
{
|
|
545
|
+
error: "active raw-message manager exposes neither upsertRawMessages nor storeMessages"
|
|
546
|
+
},
|
|
547
|
+
500
|
|
548
|
+
);
|
|
549
|
+
}
|
|
515
550
|
try {
|
|
516
|
-
await upsertRawMessagesToChroma(
|
|
551
|
+
await upsertRawMessagesToChroma(messages);
|
|
517
552
|
} catch (error) {
|
|
518
553
|
console.warn("[memory-store/http] chroma upsert failed:", error);
|
|
519
554
|
}
|
|
520
|
-
return c.json({ ok: true, result });
|
|
555
|
+
return c.json({ ok: true, count: messages.length, result });
|
|
521
556
|
});
|
|
522
557
|
app.get("/v1/raw-messages/:id", async (c) => {
|
|
523
558
|
const id = c.req.param("id");
|
|
@@ -539,33 +574,352 @@ async function startHttpServer(options = {}) {
|
|
|
539
574
|
};
|
|
540
575
|
}
|
|
541
576
|
|
|
577
|
+
// src/storage/sqlite-vector-index.ts
|
|
578
|
+
function getMemoryVectorStoreBackend() {
|
|
579
|
+
const backend = (process.env.INSIGHT_VECTOR_STORE_BACKEND || process.env.MEMORY_VECTOR_STORE_BACKEND || process.env.VECTOR_STORE_BACKEND || "sqlite-vec").toLowerCase();
|
|
580
|
+
return backend === "chroma" ? "chroma" : "sqlite-vec";
|
|
581
|
+
}
|
|
582
|
+
function isInsightSQLiteVecEnabled(_env) {
|
|
583
|
+
return getMemoryVectorStoreBackend() === "sqlite-vec";
|
|
584
|
+
}
|
|
585
|
+
async function getInsightSQLiteVecStore(_env) {
|
|
586
|
+
const dbPath = resolveSQLiteRawMessageDbPath();
|
|
587
|
+
const { getSQLiteVecStore } = await import('@melandlabs/rag/sqlite-vec-store');
|
|
588
|
+
return await getSQLiteVecStore(dbPath, void 0, {
|
|
589
|
+
collectionName: process.env.SQLITE_VEC_INSIGHTS_COLLECTION || "opencontext_insights"
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
async function searchInsightsWithSQLiteVec(input, env) {
|
|
593
|
+
const store = await getInsightSQLiteVecStore();
|
|
594
|
+
const overfetchLimit = Math.max(input.limit * 8, input.limit);
|
|
595
|
+
const results = await store.similaritySearchWithOptions(input.queryEmbedding, {
|
|
596
|
+
limit: overfetchLimit,
|
|
597
|
+
filter: { userId: input.userId }
|
|
598
|
+
});
|
|
599
|
+
return results.filter((result) => result.score >= input.threshold).filter((result) => {
|
|
600
|
+
const metadata = result.metadata ?? {};
|
|
601
|
+
if (input.botIds?.length && !input.botIds.includes(String(metadata.botId ?? ""))) {
|
|
602
|
+
return false;
|
|
603
|
+
}
|
|
604
|
+
return input.includeArchived || metadata.archived !== true;
|
|
605
|
+
}).slice(0, input.limit);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// src/server/cli-shared.ts
|
|
609
|
+
async function loadAiRag() {
|
|
610
|
+
try {
|
|
611
|
+
const [local, chroma] = await Promise.all([
|
|
612
|
+
import('@melandlabs/ai-rag/local-transformers-embedding-provider'),
|
|
613
|
+
import('@melandlabs/ai-rag/chroma-store')
|
|
614
|
+
]);
|
|
615
|
+
return {
|
|
616
|
+
LocalTransformersEmbeddingProvider: local.LocalTransformersEmbeddingProvider,
|
|
617
|
+
ChromaVectorStore: chroma.ChromaVectorStore
|
|
618
|
+
};
|
|
619
|
+
} catch (error) {
|
|
620
|
+
throw new Error(
|
|
621
|
+
`--embedding-provider local / --*-backend=chroma require @melandlabs/ai-rag to be installed. Run \`pnpm add @melandlabs/ai-rag\` and try again. (${error.message})`
|
|
622
|
+
);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
function parseUnifiedArgs(argv) {
|
|
626
|
+
const env = process.env;
|
|
627
|
+
const args = {
|
|
628
|
+
embeddingProvider: env.EMBEDDING_PROVIDER ?? "none",
|
|
629
|
+
embeddingModel: env.EMBEDDING_MODEL,
|
|
630
|
+
chromaUrl: env.CHROMA_URL,
|
|
631
|
+
memoryBackend: env.MEMORY_BACKEND ?? "none",
|
|
632
|
+
insightsBackend: env.INSIGHTS_BACKEND ?? "none",
|
|
633
|
+
insightsCollection: env.INSIGHTS_COLLECTION ?? "opencontext_insights",
|
|
634
|
+
knowledgeBackend: env.KNOWLEDGE_BACKEND ?? "none",
|
|
635
|
+
knowledgeCollection: env.KNOWLEDGE_COLLECTION ?? "opencontext_knowledge"
|
|
636
|
+
};
|
|
637
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
638
|
+
const arg = argv[i];
|
|
639
|
+
const next = argv[i + 1];
|
|
640
|
+
const takeValue = () => {
|
|
641
|
+
if (next === void 0) throw new Error(`[memory-store/http] ${arg} requires a value`);
|
|
642
|
+
i += 1;
|
|
643
|
+
return next;
|
|
644
|
+
};
|
|
645
|
+
switch (arg) {
|
|
646
|
+
case "--embedding-provider":
|
|
647
|
+
args.embeddingProvider = takeValue();
|
|
648
|
+
break;
|
|
649
|
+
case "--embedding-model":
|
|
650
|
+
args.embeddingModel = takeValue();
|
|
651
|
+
break;
|
|
652
|
+
case "--chroma-url":
|
|
653
|
+
args.chromaUrl = takeValue();
|
|
654
|
+
break;
|
|
655
|
+
case "--memory-backend":
|
|
656
|
+
args.memoryBackend = takeValue();
|
|
657
|
+
break;
|
|
658
|
+
case "--insights-backend":
|
|
659
|
+
args.insightsBackend = takeValue();
|
|
660
|
+
break;
|
|
661
|
+
case "--insights-collection":
|
|
662
|
+
args.insightsCollection = takeValue();
|
|
663
|
+
break;
|
|
664
|
+
case "--knowledge-backend":
|
|
665
|
+
args.knowledgeBackend = takeValue();
|
|
666
|
+
break;
|
|
667
|
+
case "--knowledge-collection":
|
|
668
|
+
args.knowledgeCollection = takeValue();
|
|
669
|
+
break;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
for (const [name, value] of [
|
|
673
|
+
["--embedding-provider", args.embeddingProvider],
|
|
674
|
+
["--memory-backend", args.memoryBackend],
|
|
675
|
+
["--insights-backend", args.insightsBackend],
|
|
676
|
+
["--knowledge-backend", args.knowledgeBackend]
|
|
677
|
+
]) {
|
|
678
|
+
const allowed = ["local", "openrouter", "none", "chroma", "sqlite-vec"];
|
|
679
|
+
if (!allowed.includes(value)) {
|
|
680
|
+
throw new Error(`[memory-store/cli] ${name} must be one of: ${allowed.join(", ")} (got "${value}")`);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
return args;
|
|
684
|
+
}
|
|
685
|
+
async function buildUnified(args) {
|
|
686
|
+
const unified = {};
|
|
687
|
+
const log = (msg) => console.warn(`[memory-store/cli] ${msg}`);
|
|
688
|
+
const wantsAiRag = args.embeddingProvider === "local" || args.memoryBackend === "chroma" || args.insightsBackend === "chroma" || args.knowledgeBackend === "chroma";
|
|
689
|
+
const aiRag = wantsAiRag ? await loadAiRag() : null;
|
|
690
|
+
if (args.embeddingProvider === "local") {
|
|
691
|
+
const provider = new aiRag.LocalTransformersEmbeddingProvider({ modelName: args.embeddingModel });
|
|
692
|
+
unified.embedQuery = async ({ query }) => provider.embedQuery(query);
|
|
693
|
+
log(
|
|
694
|
+
`embedQuery wired via LocalTransformersEmbeddingProvider (model=${args.embeddingModel ?? "Xenova/all-MiniLM-L6-v2"})`
|
|
695
|
+
);
|
|
696
|
+
} else if (args.embeddingProvider === "openrouter") {
|
|
697
|
+
const apiKey = process.env.OPENROUTER_API_KEY;
|
|
698
|
+
if (!apiKey) {
|
|
699
|
+
throw new Error("--embedding-provider openrouter requires OPENROUTER_API_KEY in the environment");
|
|
700
|
+
}
|
|
701
|
+
const model = args.embeddingModel ?? "text-embedding-3-small";
|
|
702
|
+
const baseURL = "https://openrouter.ai/api/v1";
|
|
703
|
+
unified.embedQuery = async ({ query }) => {
|
|
704
|
+
const res = await fetch(`${baseURL}/embeddings`, {
|
|
705
|
+
method: "POST",
|
|
706
|
+
headers: {
|
|
707
|
+
"content-type": "application/json",
|
|
708
|
+
authorization: `Bearer ${apiKey}`,
|
|
709
|
+
"HTTP-Referer": process.env.NEXT_PUBLIC_APP_URL ?? "https://opencontext.ai",
|
|
710
|
+
"X-Title": "opencontext AI"
|
|
711
|
+
},
|
|
712
|
+
body: JSON.stringify({ model, input: query })
|
|
713
|
+
});
|
|
714
|
+
if (!res.ok) {
|
|
715
|
+
throw new Error(`openrouter embeddings ${res.status}: ${await res.text()}`);
|
|
716
|
+
}
|
|
717
|
+
const body = await res.json();
|
|
718
|
+
const embedding = body.data?.[0]?.embedding;
|
|
719
|
+
if (!embedding) throw new Error("openrouter embeddings response missing data[0].embedding");
|
|
720
|
+
return embedding;
|
|
721
|
+
};
|
|
722
|
+
log(`embedQuery wired via openrouter (model=${model})`);
|
|
723
|
+
}
|
|
724
|
+
if (args.memoryBackend === "sqlite-vec") {
|
|
725
|
+
const store = createRawMessageStore({ env: void 0 });
|
|
726
|
+
const manager2 = await store.getManager();
|
|
727
|
+
if (typeof manager2.searchMessagesSemantically !== "function") {
|
|
728
|
+
throw new Error(
|
|
729
|
+
"the active raw-message manager does not implement searchMessagesSemantically; sqlite-vec ANN for the memory source is unavailable in this build"
|
|
730
|
+
);
|
|
731
|
+
}
|
|
732
|
+
unified.searchRawMessagesAnn = async ({ userId, queryEmbedding, limit, threshold, botId }) => {
|
|
733
|
+
const rows = await manager2.searchMessagesSemantically({
|
|
734
|
+
userId,
|
|
735
|
+
queryEmbedding,
|
|
736
|
+
limit,
|
|
737
|
+
threshold,
|
|
738
|
+
botId
|
|
739
|
+
});
|
|
740
|
+
return rows.map((r) => ({
|
|
741
|
+
id: r.id,
|
|
742
|
+
content: r.content,
|
|
743
|
+
similarity: r.similarity,
|
|
744
|
+
metadata: r.metadata ?? {}
|
|
745
|
+
}));
|
|
746
|
+
};
|
|
747
|
+
log("memory backend wired via sqlite-vec (uses the manager's searchMessagesSemantically)");
|
|
748
|
+
} else if (args.memoryBackend === "chroma") {
|
|
749
|
+
if (!args.chromaUrl)
|
|
750
|
+
throw new Error("--memory-backend=chroma requires --chroma-url <url> (or CHROMA_URL env)");
|
|
751
|
+
const store = new aiRag.ChromaVectorStore({
|
|
752
|
+
url: args.chromaUrl,
|
|
753
|
+
collectionName: "opencontext_raw_messages"
|
|
754
|
+
});
|
|
755
|
+
unified.searchRawMessagesAnn = async ({ queryEmbedding, limit, threshold }) => {
|
|
756
|
+
const results = await store.similaritySearchWithOptions(queryEmbedding, { limit: limit + 1 });
|
|
757
|
+
return results.filter((r) => r.score >= threshold).map((r) => ({ id: r.id, content: r.content, similarity: r.score, metadata: r.metadata ?? {} }));
|
|
758
|
+
};
|
|
759
|
+
log("memory backend wired via chroma (collection=opencontext_raw_messages)");
|
|
760
|
+
}
|
|
761
|
+
if (args.insightsBackend === "sqlite-vec" && isInsightSQLiteVecEnabled()) {
|
|
762
|
+
if (!unified.embedQuery) {
|
|
763
|
+
throw new Error("--insights-backend=sqlite-vec requires --embedding-provider local|openrouter");
|
|
764
|
+
}
|
|
765
|
+
const embedQuery = unified.embedQuery;
|
|
766
|
+
unified.searchInsights = async ({ userId, query, limit, threshold, botIds, includeArchived }) => {
|
|
767
|
+
const emb = await embedQuery({ userId, query });
|
|
768
|
+
const rows = await searchInsightsWithSQLiteVec({
|
|
769
|
+
userId,
|
|
770
|
+
queryEmbedding: emb,
|
|
771
|
+
limit,
|
|
772
|
+
threshold,
|
|
773
|
+
botIds,
|
|
774
|
+
includeArchived
|
|
775
|
+
});
|
|
776
|
+
return rows.map((r) => ({
|
|
777
|
+
id: r.id,
|
|
778
|
+
content: r.content,
|
|
779
|
+
similarity: r.score,
|
|
780
|
+
metadata: r.metadata ?? {}
|
|
781
|
+
}));
|
|
782
|
+
};
|
|
783
|
+
log("insights backend wired via sqlite-vec");
|
|
784
|
+
} else if (args.insightsBackend === "chroma") {
|
|
785
|
+
if (!args.chromaUrl)
|
|
786
|
+
throw new Error("--insights-backend=chroma requires --chroma-url <url> (or CHROMA_URL env)");
|
|
787
|
+
if (!unified.embedQuery)
|
|
788
|
+
throw new Error("--insights-backend=chroma requires --embedding-provider local|openrouter");
|
|
789
|
+
const store = new aiRag.ChromaVectorStore({
|
|
790
|
+
url: args.chromaUrl,
|
|
791
|
+
collectionName: args.insightsCollection
|
|
792
|
+
});
|
|
793
|
+
const embedQuery = unified.embedQuery;
|
|
794
|
+
unified.searchInsights = async ({ query, limit, threshold }) => {
|
|
795
|
+
const emb = await embedQuery({ userId: "", query });
|
|
796
|
+
const results = await store.similaritySearchWithOptions(emb, { limit: limit + 1 });
|
|
797
|
+
return results.filter((r) => r.score >= threshold).map((r) => ({ id: r.id, content: r.content, similarity: r.score, metadata: r.metadata ?? {} }));
|
|
798
|
+
};
|
|
799
|
+
log(`insights backend wired via chroma (collection=${args.insightsCollection})`);
|
|
800
|
+
}
|
|
801
|
+
if (args.knowledgeBackend === "chroma") {
|
|
802
|
+
if (!args.chromaUrl)
|
|
803
|
+
throw new Error("--knowledge-backend=chroma requires --chroma-url <url> (or CHROMA_URL env)");
|
|
804
|
+
if (!unified.embedQuery)
|
|
805
|
+
throw new Error("--knowledge-backend=chroma requires --embedding-provider local|openrouter");
|
|
806
|
+
const store = new aiRag.ChromaVectorStore({
|
|
807
|
+
url: args.chromaUrl,
|
|
808
|
+
collectionName: args.knowledgeCollection
|
|
809
|
+
});
|
|
810
|
+
const embedQuery = unified.embedQuery;
|
|
811
|
+
unified.searchKnowledge = async ({ query, options }) => {
|
|
812
|
+
const emb = await embedQuery({ userId: "", query });
|
|
813
|
+
const results = await store.similaritySearchWithOptions(emb, { limit: options.limit + 1 });
|
|
814
|
+
return results.filter((r) => r.score >= options.threshold).map((r, i) => ({
|
|
815
|
+
chunkId: r.id,
|
|
816
|
+
documentId: r.documentId || String(r.metadata?.documentId ?? r.id),
|
|
817
|
+
documentName: String(r.metadata?.documentName ?? ""),
|
|
818
|
+
content: r.content,
|
|
819
|
+
similarity: r.score,
|
|
820
|
+
chunkIndex: i
|
|
821
|
+
}));
|
|
822
|
+
};
|
|
823
|
+
log(`knowledge backend wired via chroma (collection=${args.knowledgeCollection})`);
|
|
824
|
+
}
|
|
825
|
+
return unified;
|
|
826
|
+
}
|
|
827
|
+
function printUnifiedHelp() {
|
|
828
|
+
console.log(`Embedding (wires unified.embedQuery):
|
|
829
|
+
--embedding-provider <name> local | openrouter | none
|
|
830
|
+
(env: EMBEDDING_PROVIDER, default: none)
|
|
831
|
+
--embedding-model <name> Model name
|
|
832
|
+
(env: EMBEDDING_MODEL; local \u2192 Xenova/all-MiniLM-L6-v2,
|
|
833
|
+
openrouter \u2192 text-embedding-3-small)
|
|
834
|
+
Note: "local" dynamically imports @melandlabs/ai-rag (a peer install).
|
|
835
|
+
|
|
836
|
+
Cross-source search (wires unified.searchKnowledge / searchInsights / searchRawMessagesAnn):
|
|
837
|
+
--chroma-url <url> Chroma server URL
|
|
838
|
+
(env: CHROMA_URL; required when any *-backend=chroma)
|
|
839
|
+
--memory-backend <name> sqlite-vec | chroma | none
|
|
840
|
+
(env: MEMORY_BACKEND, default: none)
|
|
841
|
+
--insights-backend <name> sqlite-vec | chroma | none
|
|
842
|
+
(env: INSIGHTS_BACKEND, default: none)
|
|
843
|
+
--insights-collection <name> Chroma collection (default: opencontext_insights)
|
|
844
|
+
--knowledge-backend <name> chroma | none
|
|
845
|
+
(env: KNOWLEDGE_BACKEND, default: none)
|
|
846
|
+
--knowledge-collection <name> Chroma collection (default: opencontext_knowledge)
|
|
847
|
+
Note: the chroma backends dynamically import @melandlabs/ai-rag.`);
|
|
848
|
+
}
|
|
849
|
+
|
|
542
850
|
// src/server/cli-http.ts
|
|
543
851
|
function parseArgs(argv) {
|
|
852
|
+
const env = process.env;
|
|
853
|
+
const unified = parseUnifiedArgs(argv);
|
|
544
854
|
const args = {
|
|
545
|
-
|
|
546
|
-
|
|
855
|
+
...unified,
|
|
856
|
+
port: Number.parseInt(env.MEMORY_HTTP_PORT ?? "7421", 10),
|
|
857
|
+
host: env.MEMORY_HTTP_HOST ?? "127.0.0.1"
|
|
547
858
|
};
|
|
548
859
|
for (let i = 0; i < argv.length; i += 1) {
|
|
549
860
|
const arg = argv[i];
|
|
550
861
|
const next = argv[i + 1];
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
i += 1;
|
|
554
|
-
} else if (arg === "--host" && next) {
|
|
555
|
-
args.host = next;
|
|
862
|
+
const takeValue = () => {
|
|
863
|
+
if (next === void 0) throw new Error(`[memory-store/http] ${arg} requires a value`);
|
|
556
864
|
i += 1;
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
865
|
+
return next;
|
|
866
|
+
};
|
|
867
|
+
switch (arg) {
|
|
868
|
+
case "--port":
|
|
869
|
+
args.port = Number.parseInt(takeValue(), 10);
|
|
870
|
+
break;
|
|
871
|
+
case "--host":
|
|
872
|
+
args.host = takeValue();
|
|
873
|
+
break;
|
|
874
|
+
case "--help":
|
|
875
|
+
case "-h":
|
|
876
|
+
printHelp();
|
|
877
|
+
process.exit(0);
|
|
560
878
|
}
|
|
561
879
|
}
|
|
880
|
+
if (!Number.isFinite(args.port) || args.port <= 0) {
|
|
881
|
+
throw new Error(`[memory-store/http] invalid --port: ${args.port}`);
|
|
882
|
+
}
|
|
562
883
|
return args;
|
|
563
884
|
}
|
|
885
|
+
function printHelp() {
|
|
886
|
+
console.log(`opencontext-memory-http \u2014 standalone memory-store HTTP daemon.
|
|
887
|
+
|
|
888
|
+
Usage:
|
|
889
|
+
opencontext-memory-http [options]
|
|
890
|
+
|
|
891
|
+
Server:
|
|
892
|
+
--port <port> Port to listen on (default: 7421, env: MEMORY_HTTP_PORT)
|
|
893
|
+
--host <host> Host to bind (default: 127.0.0.1, env: MEMORY_HTTP_HOST)
|
|
894
|
+
`);
|
|
895
|
+
printUnifiedHelp();
|
|
896
|
+
console.log(`
|
|
897
|
+
Examples:
|
|
898
|
+
# Minimal \u2014 same as before, all three *not_configured warnings remain
|
|
899
|
+
opencontext-memory-http
|
|
900
|
+
|
|
901
|
+
# Local ONNX embedder + sqlite-vec ANN for the memory source (no API key,
|
|
902
|
+
# no extra services). Removes embedQuery + memory warnings; the
|
|
903
|
+
# insights/knowledge sources still need a backend of their own.
|
|
904
|
+
opencontext-memory-http \\
|
|
905
|
+
--embedding-provider local \\
|
|
906
|
+
--memory-backend sqlite-vec
|
|
907
|
+
|
|
908
|
+
# Wire everything via a running Chroma server
|
|
909
|
+
opencontext-memory-http \\
|
|
910
|
+
--embedding-provider openrouter \\
|
|
911
|
+
--chroma-url http://127.0.0.1:8000 \\
|
|
912
|
+
--memory-backend chroma \\
|
|
913
|
+
--insights-backend chroma \\
|
|
914
|
+
--knowledge-backend chroma`);
|
|
915
|
+
}
|
|
564
916
|
async function main() {
|
|
565
917
|
const args = parseArgs(process.argv.slice(2));
|
|
566
|
-
const
|
|
918
|
+
const unified = await buildUnified(args);
|
|
919
|
+
const { url, stop } = await startHttpServer({
|
|
567
920
|
port: args.port,
|
|
568
|
-
host: args.host
|
|
921
|
+
host: args.host,
|
|
922
|
+
unified
|
|
569
923
|
});
|
|
570
924
|
console.log(`[memory-store/http] listening at ${url}`);
|
|
571
925
|
const shutdown = async (signal) => {
|