@bcmyguest/openmem 0.0.0-stage → 0.6.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/LICENSE +190 -0
- package/README.md +136 -2
- package/bin/hindsight-project-index +530 -0
- package/cli/coding-agent/access-policy.mjs +38 -0
- package/cli/coding-agent/agents.mjs +34 -0
- package/cli/coding-agent/bank-from-cwd.mjs +140 -0
- package/cli/coding-agent/capabilities.mjs +23 -0
- package/cli/coding-agent/hook-harnesses.mjs +87 -0
- package/cli/coding-agent/hook-runner.mjs +260 -0
- package/cli/coding-agent/install-access.mjs +93 -0
- package/cli/coding-agent/install-shim.mjs +97 -0
- package/cli/coding-agent/opencode-config.mjs +91 -0
- package/cli/coding-agent/opencode-plugin.mjs +96 -0
- package/cli/coding-agent/runtime.mjs +39 -0
- package/cli/coding-agent/session-facade.mjs +62 -0
- package/cli/cognee-mcp.mjs +260 -0
- package/cli/feed-sync.mjs +11 -0
- package/cli/install-coding-agent.mjs +165 -0
- package/cli/install-memory-mcp.mjs +426 -0
- package/cli/is-main.mjs +18 -0
- package/cli/mcp/context.mjs +61 -0
- package/cli/mcp/export/cognee-dataset-name.mjs +12 -0
- package/cli/mcp/export/cognee.mjs +119 -0
- package/cli/mcp/export/hindsight.mjs +48 -0
- package/cli/mcp/export/index.mjs +26 -0
- package/cli/mcp/export/mem0.mjs +31 -0
- package/cli/mcp/llm-synthesize.mjs +50 -0
- package/cli/mcp/mem0-scope-id.mjs +22 -0
- package/cli/mcp/memory-facade.mjs +455 -0
- package/cli/mcp/memory-reflect-consolidate.mjs +120 -0
- package/cli/mcp/notebooklm-banks.mjs +21 -0
- package/cli/mcp/notebooklm-hindsight-rest.mjs +45 -0
- package/cli/mcp/notebooklm-provider.mjs +225 -0
- package/cli/mcp/pmm-records.mjs +23 -0
- package/cli/mcp/portability.mjs +158 -0
- package/cli/mcp/stdio.mjs +65 -0
- package/cli/mcp/unified-mcp-install.mjs +65 -0
- package/cli/mem0-mcp.mjs +108 -0
- package/cli/notebooklm-mcp.mjs +236 -0
- package/cli/openmem-mcp.mjs +35 -0
- package/cli/openmem.mjs +205 -0
- package/cli/roots.mjs +85 -0
- package/cli/stack.mjs +436 -0
- package/cli/switch-memory-provider.mjs +358 -0
- package/cli/wiring-report.mjs +125 -0
- package/compose.bench.yaml +12 -0
- package/compose.yaml +164 -0
- package/config/coding-agent.json +13 -0
- package/config/hindsight.env.example +24 -0
- package/config/index.yml +54 -0
- package/config/litellm.lemond.yaml +40 -0
- package/config/litellm.ollama.yaml +33 -0
- package/config/notebooklm.yml +33 -0
- package/config/openmem.toml.example +62 -0
- package/docs/OPENMEM.md +23 -0
- package/docs/architecture.md +219 -0
- package/docs/assets/README.md +10 -0
- package/docs/assets/setup-demo.gif +0 -0
- package/docs/coding-agent-hooks.md +51 -0
- package/docs/coding-agent-scopes.md +22 -0
- package/docs/coding-agents.md +51 -0
- package/docs/cognee.md +37 -0
- package/docs/concepts.md +112 -0
- package/docs/hindsight.md +74 -0
- package/docs/index-sources-roadmap.md +38 -0
- package/docs/index-sources.md +39 -0
- package/docs/indexer-sinks.md +122 -0
- package/docs/indexing.md +57 -0
- package/docs/manual-stack-setup.md +74 -0
- package/docs/mcp-install-guide.md +4 -0
- package/docs/mcp-setup.md +101 -0
- package/docs/mem0.md +103 -0
- package/docs/memory-portability.md +73 -0
- package/docs/memory-providers.md +246 -0
- package/docs/model-configuration.md +84 -0
- package/docs/notebooklm.md +83 -0
- package/docs/npx-install.md +170 -0
- package/docs/openmem-config.md +194 -0
- package/docs/provider-wizard-hooks.md +222 -0
- package/docs/quickstart.md +101 -0
- package/docs/setup-wizard.md +116 -0
- package/docs/sources-feeds.md +52 -0
- package/docs/sources-readwise.md +23 -0
- package/docs/sources-zotero.md +22 -0
- package/glama.json +4 -0
- package/indexer/Dockerfile +38 -0
- package/indexer/code_outline.py +146 -0
- package/indexer/feed_sync.py +107 -0
- package/indexer/index_config.py +290 -0
- package/indexer/pytest.ini +5 -0
- package/indexer/sinks/__init__.py +148 -0
- package/indexer/sinks/_http.py +44 -0
- package/indexer/sinks/cognee.py +329 -0
- package/indexer/sinks/hindsight.py +107 -0
- package/indexer/sinks/mem0.py +247 -0
- package/indexer/watch.py +240 -0
- package/package.json +95 -4
- package/providers/_template/compose.yaml +52 -0
- package/providers/cognee/OPERATOR.md +82 -0
- package/providers/cognee/compose.yaml +89 -0
- package/providers/cognee/lib/clack.mjs +157 -0
- package/providers/cognee/lib/coding-agent.mjs +26 -0
- package/providers/cognee/lib/cognee-mcp-install.mjs +153 -0
- package/providers/cognee/lib/config.mjs +98 -0
- package/providers/cognee/lib/env-file.mjs +194 -0
- package/providers/cognee/lib/env-setup.mjs +83 -0
- package/providers/cognee/lib/llm-env.mjs +382 -0
- package/providers/cognee/lib/ports.mjs +25 -0
- package/providers/cognee/lib/smoke.mjs +246 -0
- package/providers/cognee/lib/stack-env.mjs +134 -0
- package/providers/cognee/lib/state.mjs +101 -0
- package/providers/cognee/lib/summary.mjs +56 -0
- package/providers/cognee/lib/validate.mjs +97 -0
- package/providers/cognee/manifest.json +56 -0
- package/providers/cognee/wizard.mjs +134 -0
- package/providers/graphiti/manifest.json +15 -0
- package/providers/hindsight/lib/clack.mjs +245 -0
- package/providers/hindsight/lib/coding-agent.mjs +204 -0
- package/providers/hindsight/lib/config.mjs +73 -0
- package/providers/hindsight/lib/env-setup.mjs +71 -0
- package/providers/hindsight/lib/litellm-routes.mjs +187 -0
- package/providers/hindsight/lib/model-backend.mjs +214 -0
- package/providers/hindsight/lib/notebooklm-mcp-install.mjs +127 -0
- package/providers/hindsight/lib/ollama-compose.mjs +51 -0
- package/providers/hindsight/lib/ports.mjs +30 -0
- package/providers/hindsight/lib/probe.mjs +33 -0
- package/providers/hindsight/lib/stack-env.mjs +132 -0
- package/providers/hindsight/lib/state.mjs +75 -0
- package/providers/hindsight/lib/summary.mjs +43 -0
- package/providers/hindsight/lib/validate.mjs +75 -0
- package/providers/hindsight/manifest.json +58 -0
- package/providers/hindsight/wizard.mjs +149 -0
- package/providers/letta/manifest.json +29 -0
- package/providers/mem0/OPERATOR.md +124 -0
- package/providers/mem0/compose.yaml +57 -0
- package/providers/mem0/lib/coding-agent.mjs +58 -0
- package/providers/mem0/lib/config.mjs +45 -0
- package/providers/mem0/lib/configure.mjs +135 -0
- package/providers/mem0/lib/dotenv.mjs +71 -0
- package/providers/mem0/lib/env-file.mjs +110 -0
- package/providers/mem0/lib/http.mjs +69 -0
- package/providers/mem0/lib/mcp-install.mjs +165 -0
- package/providers/mem0/lib/secret.mjs +30 -0
- package/providers/mem0/lib/smoke.mjs +179 -0
- package/providers/mem0/lib/stack-env.mjs +216 -0
- package/providers/mem0/lib/state.mjs +67 -0
- package/providers/mem0/lib/summary.mjs +60 -0
- package/providers/mem0/lib/url.mjs +106 -0
- package/providers/mem0/lib/validate.mjs +158 -0
- package/providers/mem0/manifest.json +47 -0
- package/providers/mem0/wizard.mjs +133 -0
- package/providers/zep/manifest.json +29 -0
- package/scripts/check-pack.mjs +218 -0
- package/scripts/gen-masks.mjs +83 -0
- package/scripts/gen-masks.sh +4 -0
- package/scripts/promotion-provider-smoke.mjs +34 -0
- package/scripts/run-setup.sh +30 -0
- package/scripts/setup.mjs +94 -0
- package/scripts/setup.sh +5 -0
- package/scripts/spin-up.sh +45 -0
- package/scripts/wizard.sh +5 -0
- package/server.json +34 -0
- package/skills/openmem/SKILL.md +29 -0
- package/sync/Dockerfile +27 -0
- package/sync/memory_backend.py +87 -0
- package/sync/notebooklm_sync.py +664 -0
- package/wizard/bin/pm.mjs +97 -0
- package/wizard/bin/setup.mjs +33 -0
- package/wizard/lib/compose-version.mjs +32 -0
- package/wizard/lib/compose.mjs +70 -0
- package/wizard/lib/curl.mjs +103 -0
- package/wizard/lib/docker-build.mjs +148 -0
- package/wizard/lib/exec.mjs +41 -0
- package/wizard/lib/indexer.mjs +6 -0
- package/wizard/lib/load-machine-env.mjs +4 -0
- package/wizard/lib/log.mjs +32 -0
- package/wizard/lib/notebooklm-agent-access.mjs +92 -0
- package/wizard/lib/notebooklm.mjs +214 -0
- package/wizard/lib/parse-args.mjs +87 -0
- package/wizard/lib/paths.mjs +39 -0
- package/wizard/lib/pm-config.mjs +163 -0
- package/wizard/lib/ports.mjs +94 -0
- package/wizard/lib/prompts.mjs +57 -0
- package/wizard/lib/provider-wizard/helpers.mjs +98 -0
- package/wizard/lib/provider-wizard/run-steps.mjs +129 -0
- package/wizard/lib/providers/registry.mjs +262 -0
- package/wizard/lib/run-clack.mjs +108 -0
- package/wizard/lib/run-wizard.mjs +755 -0
- package/wizard/lib/validate-setup.mjs +62 -0
- package/wizard/lib/wizard-action.mjs +34 -0
- package/wizard/lib/wizard-conf.mjs +71 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { authHeaders } from "../../cognee-mcp.mjs";
|
|
2
|
+
import { cogneeDatasetName } from "./cognee-dataset-name.mjs";
|
|
3
|
+
|
|
4
|
+
const PAGE = 1000;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @param {unknown} value
|
|
8
|
+
* @returns {string}
|
|
9
|
+
*/
|
|
10
|
+
function trimString(value) {
|
|
11
|
+
return typeof value === "string" ? value.trim() : "";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {unknown} raw
|
|
16
|
+
* @returns {string}
|
|
17
|
+
*/
|
|
18
|
+
function textFromRawData(raw) {
|
|
19
|
+
if (typeof raw === "string") return raw.trim();
|
|
20
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
21
|
+
return textFromRecord(raw);
|
|
22
|
+
}
|
|
23
|
+
return "";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Read `text`, `content`, or nested `raw_data` / `rawData` from a plain object.
|
|
28
|
+
* @param {unknown} obj
|
|
29
|
+
* @returns {string}
|
|
30
|
+
*/
|
|
31
|
+
function textFromRecord(obj) {
|
|
32
|
+
if (!obj || typeof obj !== "object" || Array.isArray(obj)) return "";
|
|
33
|
+
const rec = /** @type {Record<string, unknown>} */ (obj);
|
|
34
|
+
for (const key of ["text", "content"]) {
|
|
35
|
+
const s = trimString(rec[key]);
|
|
36
|
+
if (s) return s;
|
|
37
|
+
}
|
|
38
|
+
const fromRaw = textFromRawData(rec.raw_data ?? rec.rawData);
|
|
39
|
+
if (fromRaw) return fromRaw;
|
|
40
|
+
return "";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Prefer full document bytes/text from a Cognee dataset data row over label-only fields.
|
|
45
|
+
* @param {Record<string, unknown>} item
|
|
46
|
+
* @returns {string}
|
|
47
|
+
*/
|
|
48
|
+
export function cogneeDataItemContent(item) {
|
|
49
|
+
const fromItem = textFromRecord(item);
|
|
50
|
+
if (fromItem) return fromItem;
|
|
51
|
+
const fromDoc = textFromRecord(item.document ?? item.doc);
|
|
52
|
+
if (fromDoc) return fromDoc;
|
|
53
|
+
return trimString(item.label) || trimString(item.name) || "";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {{ cognee?: { baseUrl: string, apiKey?: string } }} ctx
|
|
58
|
+
* @param {{ bank?: string }} args
|
|
59
|
+
* @param {typeof fetch} fetchFn
|
|
60
|
+
* @param {NodeJS.ProcessEnv} [env]
|
|
61
|
+
*/
|
|
62
|
+
export async function exportCognee(ctx, args, fetchFn, env = process.env) {
|
|
63
|
+
const base = ctx.cognee?.baseUrl?.replace(/\/+$/, "");
|
|
64
|
+
if (!base) throw new Error("Cognee API URL not configured");
|
|
65
|
+
const headers = { ...authHeaders(env) };
|
|
66
|
+
const bank = String(args.bank || "openmem-mcp").trim() || "openmem-mcp";
|
|
67
|
+
const datasetName = cogneeDatasetName(bank);
|
|
68
|
+
|
|
69
|
+
const listRes = await fetchFn(`${base}/api/v1/datasets`, { headers });
|
|
70
|
+
const listText = await listRes.text();
|
|
71
|
+
if (!listRes.ok) throw new Error(`GET /api/v1/datasets → ${listRes.status}: ${listText.slice(0, 400)}`);
|
|
72
|
+
let datasets;
|
|
73
|
+
try {
|
|
74
|
+
datasets = listText ? JSON.parse(listText) : [];
|
|
75
|
+
} catch {
|
|
76
|
+
throw new Error("GET /api/v1/datasets: invalid JSON");
|
|
77
|
+
}
|
|
78
|
+
const row = (Array.isArray(datasets) ? datasets : []).find(
|
|
79
|
+
(d) => d && typeof d === "object" && d.name === datasetName && d.id,
|
|
80
|
+
);
|
|
81
|
+
if (!row) {
|
|
82
|
+
return {
|
|
83
|
+
scope: { bank, dataset: datasetName },
|
|
84
|
+
records: [],
|
|
85
|
+
note: `No Cognee dataset named ${datasetName}; indexer may not have written to this bank yet.`,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
const datasetId = String(row.id);
|
|
89
|
+
/** @type {object[]} */
|
|
90
|
+
const records = [];
|
|
91
|
+
let offset = 0;
|
|
92
|
+
while (true) {
|
|
93
|
+
const url = `${base}/api/v1/datasets/${encodeURIComponent(datasetId)}/data?limit=${PAGE}&offset=${offset}`;
|
|
94
|
+
const res = await fetchFn(url, { headers });
|
|
95
|
+
const text = await res.text();
|
|
96
|
+
if (!res.ok) throw new Error(`GET dataset data → ${res.status}: ${text.slice(0, 400)}`);
|
|
97
|
+
let page;
|
|
98
|
+
try {
|
|
99
|
+
page = text ? JSON.parse(text) : [];
|
|
100
|
+
} catch {
|
|
101
|
+
throw new Error("GET dataset data: invalid JSON");
|
|
102
|
+
}
|
|
103
|
+
if (!Array.isArray(page)) break;
|
|
104
|
+
for (const item of page) {
|
|
105
|
+
if (!item || typeof item !== "object") continue;
|
|
106
|
+
const meta = item.externalMetadata ?? item.external_metadata ?? {};
|
|
107
|
+
const body = cogneeDataItemContent(item);
|
|
108
|
+
records.push({
|
|
109
|
+
id: item.id != null ? String(item.id) : null,
|
|
110
|
+
content: body || `[cognee:${item.name || item.id}]`,
|
|
111
|
+
metadata: { cognee: { name: item.name ?? null, externalMetadata: meta } },
|
|
112
|
+
scope_key: bank,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
if (page.length < PAGE) break;
|
|
116
|
+
offset += page.length;
|
|
117
|
+
}
|
|
118
|
+
return { scope: { bank, dataset: datasetName, dataset_id: datasetId }, records };
|
|
119
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { listBanks, listMemories } from "../../notebooklm-mcp.mjs";
|
|
2
|
+
|
|
3
|
+
const PAGE = 500;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {{ hindsightUrl?: string }} ctx
|
|
7
|
+
* @param {{ bank?: string }} args
|
|
8
|
+
* @param {typeof fetch} fetchFn
|
|
9
|
+
*/
|
|
10
|
+
export async function exportHindsight(ctx, args, fetchFn) {
|
|
11
|
+
const base = ctx.hindsightUrl;
|
|
12
|
+
if (!base) throw new Error("Hindsight API URL not configured");
|
|
13
|
+
const bankFilter = String(args.bank || "").trim();
|
|
14
|
+
const bankRows = await listBanks(base, fetchFn);
|
|
15
|
+
const bankIds = bankRows
|
|
16
|
+
.map((b) => b.bank_id || b.id || b.name || "")
|
|
17
|
+
.filter(Boolean);
|
|
18
|
+
const banks = bankFilter ? bankIds.filter((id) => id === bankFilter) : bankIds;
|
|
19
|
+
if (bankFilter && !banks.length) {
|
|
20
|
+
throw new Error(`bank not found: ${bankFilter}`);
|
|
21
|
+
}
|
|
22
|
+
/** @type {object[]} */
|
|
23
|
+
const records = [];
|
|
24
|
+
for (const bank of banks) {
|
|
25
|
+
let offset = 0;
|
|
26
|
+
while (true) {
|
|
27
|
+
const page = await listMemories(base, bank, "", { limit: PAGE, offset }, fetchFn);
|
|
28
|
+
const items = Array.isArray(page?.items) ? page.items : [];
|
|
29
|
+
for (const m of items) {
|
|
30
|
+
const content = String(m.text ?? m.content ?? "").trim();
|
|
31
|
+
if (!content) continue;
|
|
32
|
+
records.push({
|
|
33
|
+
id: m.id ?? m.memory_id ?? null,
|
|
34
|
+
content,
|
|
35
|
+
metadata: { type: m.type ?? null, ...(m.metadata && typeof m.metadata === "object" ? m.metadata : {}) },
|
|
36
|
+
scope_key: bank,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
const total = Number.isFinite(page?.total) ? page.total : items.length;
|
|
40
|
+
offset += items.length;
|
|
41
|
+
if (!items.length || items.length < PAGE || offset >= total) break;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
scope: bankFilter ? { bank: bankFilter } : { all_banks: true, bank_count: banks.length },
|
|
46
|
+
records,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Provider PMM export adapters (orchestrator: cli/mcp/portability.mjs). */
|
|
2
|
+
|
|
3
|
+
import { exportCognee } from "./cognee.mjs";
|
|
4
|
+
import { exportHindsight } from "./hindsight.mjs";
|
|
5
|
+
import { exportMem0 } from "./mem0.mjs";
|
|
6
|
+
|
|
7
|
+
export { exportHindsight } from "./hindsight.mjs";
|
|
8
|
+
export { exportMem0 } from "./mem0.mjs";
|
|
9
|
+
export { exportCognee } from "./cognee.mjs";
|
|
10
|
+
export { cogneeDatasetName } from "./cognee-dataset-name.mjs";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {string} provider
|
|
14
|
+
* @param {Record<string, unknown>} ctx
|
|
15
|
+
* @param {Record<string, unknown>} args
|
|
16
|
+
* @param {typeof fetch} fetchFn
|
|
17
|
+
*/
|
|
18
|
+
export async function runProviderExport(provider, ctx, args, fetchFn) {
|
|
19
|
+
if (provider === "mem0") {
|
|
20
|
+
const scopeBank = String(args.bank || "openmem-mcp").trim() || "openmem-mcp";
|
|
21
|
+
return exportMem0(ctx, { user_id: scopeBank }, fetchFn);
|
|
22
|
+
}
|
|
23
|
+
if (provider === "hindsight") return exportHindsight(ctx, args, fetchFn);
|
|
24
|
+
if (provider === "cognee") return exportCognee(ctx, args, fetchFn);
|
|
25
|
+
throw new Error(`export not supported for provider ${provider}`);
|
|
26
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {{ mem0?: { baseUrl: string, apiKey?: string } }} ctx
|
|
3
|
+
* @param {{ user_id: string }} scope
|
|
4
|
+
* @param {typeof fetch} fetchFn
|
|
5
|
+
*/
|
|
6
|
+
export async function exportMem0(ctx, scope, fetchFn) {
|
|
7
|
+
const base = ctx.mem0?.baseUrl;
|
|
8
|
+
const key = ctx.mem0?.apiKey;
|
|
9
|
+
if (!base) throw new Error("mem0 API URL not configured");
|
|
10
|
+
/** @type {Record<string, string>} */
|
|
11
|
+
const headers = {};
|
|
12
|
+
if (key) headers["X-API-Key"] = key;
|
|
13
|
+
const q = new URLSearchParams({ user_id: scope.user_id, top_k: "1000" });
|
|
14
|
+
const res = await fetchFn(`${base}/memories?${q}`, { headers });
|
|
15
|
+
const text = await res.text();
|
|
16
|
+
let json;
|
|
17
|
+
try {
|
|
18
|
+
json = text ? JSON.parse(text) : {};
|
|
19
|
+
} catch {
|
|
20
|
+
throw new Error(`GET /memories → ${res.status}: ${text.slice(0, 200)}`);
|
|
21
|
+
}
|
|
22
|
+
if (!res.ok) throw new Error(`GET /memories → ${res.status}: ${text.slice(0, 400)}`);
|
|
23
|
+
const raw = Array.isArray(json) ? json : json.results || json.memories || [];
|
|
24
|
+
const records = raw.map((row) => ({
|
|
25
|
+
id: row.id ?? row.memory_id ?? null,
|
|
26
|
+
content: row.memory ?? row.text ?? row.content ?? JSON.stringify(row),
|
|
27
|
+
metadata: row.metadata ?? {},
|
|
28
|
+
scope_key: scope.user_id,
|
|
29
|
+
}));
|
|
30
|
+
return { scope, records };
|
|
31
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { envValue } from "../../wizard/lib/paths.mjs";
|
|
3
|
+
|
|
4
|
+
/** OpenAI-compatible chat URL for cross-provider reflect synthesis (optional). */
|
|
5
|
+
export function resolveLlmChatUrl({ home, stackRoot, env = process.env }) {
|
|
6
|
+
const direct = String(env.PM_LLM_URL || env.OPENAI_API_BASE || "").trim();
|
|
7
|
+
if (direct) return direct.replace(/\/+$/, "").replace(/\/v1$/, "") + (direct.includes("/v1") ? "" : "/v1");
|
|
8
|
+
const fromEnv = envValue(path.join(stackRoot, ".env"), "LITELLM_URL");
|
|
9
|
+
if (fromEnv) return fromEnv.replace(/\/+$/, "");
|
|
10
|
+
const port = envValue(path.join(stackRoot, ".env"), "LITELLM_PORT") || "4000";
|
|
11
|
+
return `http://127.0.0.1:${String(port).trim()}/v1`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function resolveLlmModel(env = process.env) {
|
|
15
|
+
return String(env.PM_LLM_MODEL || env.PM_REFLECT_MODEL || "memory-reflect").trim() || "memory-reflect";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function resolveLlmApiKey(env = process.env) {
|
|
19
|
+
return String(env.PM_LLM_API_KEY || env.LITELLM_MASTER_KEY || env.OPENAI_API_KEY || "").trim();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {{ query: string, context: string, model?: string, baseUrl: string, apiKey?: string }} opts
|
|
24
|
+
* @param {typeof fetch} fetchFn
|
|
25
|
+
*/
|
|
26
|
+
export async function synthesizeReflectAnswer(opts, fetchFn = fetch) {
|
|
27
|
+
const base = opts.baseUrl.replace(/\/+$/, "");
|
|
28
|
+
const url = `${base}/chat/completions`;
|
|
29
|
+
const headers = { "content-type": "application/json" };
|
|
30
|
+
if (opts.apiKey) headers.authorization = `Bearer ${opts.apiKey}`;
|
|
31
|
+
const body = {
|
|
32
|
+
model: opts.model || resolveLlmModel(),
|
|
33
|
+
messages: [
|
|
34
|
+
{
|
|
35
|
+
role: "system",
|
|
36
|
+
content:
|
|
37
|
+
"Summarize the following memory search hits as concise session context for a coding agent. " +
|
|
38
|
+
"Do not invent facts not present in the hits.",
|
|
39
|
+
},
|
|
40
|
+
{ role: "user", content: `Question: ${opts.query}\n\nMemory hits:\n${opts.context}` },
|
|
41
|
+
],
|
|
42
|
+
max_tokens: 1024,
|
|
43
|
+
temperature: 0.2,
|
|
44
|
+
};
|
|
45
|
+
const res = await fetchFn(url, { method: "POST", headers, body: JSON.stringify(body) });
|
|
46
|
+
const text = await res.text();
|
|
47
|
+
if (!res.ok) throw new Error(`LLM synthesize → ${res.status}: ${text.slice(0, 300)}`);
|
|
48
|
+
const json = JSON.parse(text);
|
|
49
|
+
return json?.choices?.[0]?.message?.content?.trim() || text;
|
|
50
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Same mapping as indexer/sinks/mem0.py scope_id (user_id / run_id). */
|
|
2
|
+
import crypto from "node:crypto";
|
|
3
|
+
|
|
4
|
+
export function mem0ScopeId(value) {
|
|
5
|
+
const s = String(value ?? "");
|
|
6
|
+
if (!/\s/.test(s)) return s;
|
|
7
|
+
let out = "";
|
|
8
|
+
let inSpace = false;
|
|
9
|
+
for (const ch of s) {
|
|
10
|
+
if (ch.trim() === "") {
|
|
11
|
+
if (!inSpace) {
|
|
12
|
+
out += "_";
|
|
13
|
+
inSpace = true;
|
|
14
|
+
}
|
|
15
|
+
} else {
|
|
16
|
+
out += ch;
|
|
17
|
+
inSpace = false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const digest = crypto.createHash("sha256").update(s, "utf8").digest("hex").slice(0, 8);
|
|
21
|
+
return `${out}~${digest}`;
|
|
22
|
+
}
|