@amemhq/core 2.0.0 → 2.1.1

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.
@@ -43,7 +43,6 @@ var extractor = null;
43
43
  var loadedKey = null;
44
44
  var cachedDim = null;
45
45
  var DEFAULT_EMBEDDING_MODEL = "Xenova/bge-m3";
46
- var DEFAULT_MODEL_DTYPE = "fp16";
47
46
  var pinnedModel = null;
48
47
  function getEmbeddingModel() {
49
48
  return process.env.AMEM_EMBED_MODEL?.trim() || pinnedModel || DEFAULT_EMBEDDING_MODEL;
@@ -71,9 +70,28 @@ function getEmbeddingDevice() {
71
70
  return process.env.AMEM_EMBED_DEVICE?.trim() || void 0;
72
71
  }
73
72
  function getEmbeddingDtype() {
74
- const explicit = process.env.AMEM_EMBED_DTYPE?.trim();
75
- if (explicit) return explicit;
76
- return getEmbeddingModel() === DEFAULT_EMBEDDING_MODEL ? DEFAULT_MODEL_DTYPE : void 0;
73
+ return process.env.AMEM_EMBED_DTYPE?.trim() || void 0;
74
+ }
75
+ function applyModelPaths(env) {
76
+ const cache = process.env.AMEM_MODEL_CACHE?.trim();
77
+ if (cache) env.cacheDir = cache;
78
+ const local = process.env.AMEM_MODEL_DIR?.trim();
79
+ if (local) {
80
+ env.localModelPath = local;
81
+ env.allowLocalModels = true;
82
+ }
83
+ }
84
+ function makeProgressReporter() {
85
+ const lastPct = /* @__PURE__ */ new Map();
86
+ return (e) => {
87
+ if (e.status !== "progress" || !e.file || typeof e.progress !== "number") return;
88
+ if (!e.file.endsWith(".onnx") && !e.file.endsWith(".onnx_data")) return;
89
+ const pct = Math.floor(e.progress / 10) * 10;
90
+ if (lastPct.get(e.file) === pct) return;
91
+ lastPct.set(e.file, pct);
92
+ const size = e.total ? ` of ${(e.total / 1e9).toFixed(2)} GB` : "";
93
+ console.log(`[amem] downloading ${e.file}: ${pct}%${size}`);
94
+ };
77
95
  }
78
96
  function extractorKey() {
79
97
  return `${getEmbeddingModel()}|${getEmbeddingDevice() ?? ""}|${getEmbeddingDtype() ?? ""}`;
@@ -84,11 +102,13 @@ async function getExtractor() {
84
102
  if (!pipeline) {
85
103
  const mod = await import("@huggingface/transformers");
86
104
  pipeline = mod.pipeline;
105
+ applyModelPaths(mod.env);
87
106
  }
88
107
  const device = getEmbeddingDevice();
89
108
  const dtype = getEmbeddingDtype();
90
109
  extractor = await pipeline("feature-extraction", getEmbeddingModel(), {
91
110
  revision: "main",
111
+ progress_callback: makeProgressReporter(),
92
112
  // Omitted entirely when unset, so an unconfigured install gets exactly the
93
113
  // library defaults it got before these existed.
94
114
  ...device ? { device } : {},
@@ -224,6 +244,10 @@ async function scrollIdsRaw(collection, limit = 1e4) {
224
244
  async function deleteCollectionRaw(collection) {
225
245
  await qdrant("DELETE", `/collections/${collection}`);
226
246
  }
247
+ async function snapshotCollectionRaw(collection) {
248
+ const r = await qdrant("POST", `/collections/${collection}/snapshots`);
249
+ return { name: r.name, size: r.size ?? 0 };
250
+ }
227
251
  async function resolveAliasRaw(alias) {
228
252
  try {
229
253
  const res = await qdrant("GET", `/aliases`);
@@ -924,6 +948,7 @@ async function switchToMigrated(opts) {
924
948
  const { name, to } = opts;
925
949
  const log = opts.logger?.info ?? ((m) => console.log(m));
926
950
  if (name === to) throw new Error(`switch: "${name}" and "${to}" are the same collection`);
951
+ let snap;
927
952
  const already = await resolveAliasRaw(name);
928
953
  if (already === to) {
929
954
  log(`[switch] "${name}" already points at "${to}" \u2014 nothing to do`);
@@ -939,6 +964,10 @@ async function switchToMigrated(opts) {
939
964
  );
940
965
  }
941
966
  log(`[switch] verified ${targetCount} in "${to}" against ${sourceCount} in "${name}"`);
967
+ if (opts.snapshot !== false) {
968
+ snap = await snapshotCollectionRaw(name);
969
+ log(`[switch] snapshotted "${name}" \u2192 ${snap.name} (${(snap.size / 1e6).toFixed(0)} MB)`);
970
+ }
942
971
  await deleteCollectionRaw(name);
943
972
  log(`[switch] dropped "${name}"`);
944
973
  await createAliasRaw(name, to);
@@ -946,7 +975,7 @@ async function switchToMigrated(opts) {
946
975
  await setAliasRaw(name, to);
947
976
  }
948
977
  log(`[switch] "${name}" now resolves to "${to}"`);
949
- return { name, to, moved: targetCount };
978
+ return { name, to, moved: targetCount, snapshot: snap };
950
979
  }
951
980
 
952
981
  // src/cli-migrate.ts
@@ -963,10 +992,24 @@ Options
963
992
  them. Makes the run completely offline.
964
993
  -h, --help
965
994
 
995
+ AMEM_MODEL_CACHE=<dir> where model weights are cached. Set the same value
996
+ everywhere and the plugin and this command share one
997
+ copy instead of downloading it each.
998
+ AMEM_MODEL_DIR=<dir> read weights already on disk instead of downloading.
999
+
966
1000
  Migrates onto amem's current default unless AMEM_EMBED_MODEL says otherwise:
967
1001
 
968
1002
  AMEM_EMBED_MODEL=Alibaba-NLP/gte-multilingual-base amem-migrate --apply
969
1003
 
1004
+ Stop your agent before --apply, not before this. The bare run only downloads and
1005
+ reports, and the download is the long part \u2014 leaving the agent up through it costs
1006
+ nothing, while writing underneath a running agent means notes arrive after the
1007
+ rebuild has read them and you have to run --apply again.
1008
+
1009
+ Roughly: the download is your bandwidth (2.27 GB for the default), the rebuild is
1010
+ your CPU (about 3 notes a second on an M4), and notes missing keywords cost one
1011
+ LLM call each.
1012
+
970
1013
  Nothing before --switch touches the original. If a run looks wrong, delete the
971
1014
  target and start again.`;
972
1015
  function parseArgs(argv) {
@@ -975,7 +1018,9 @@ function parseArgs(argv) {
975
1018
  return i === -1 ? void 0 : argv[i + 1];
976
1019
  };
977
1020
  return {
978
- help: argv.includes("-h") || argv.includes("--help"),
1021
+ // `help` as a bare word too. Without it, typing what most CLIs accept starts
1022
+ // a multi-gigabyte model download instead of printing anything.
1023
+ help: argv.includes("-h") || argv.includes("--help") || argv[0] === "help",
979
1024
  apply: argv.includes("--apply"),
980
1025
  switchOver: argv.includes("--switch"),
981
1026
  from: value("--from-collection"),
@@ -1053,9 +1098,17 @@ All ${phase.notes} notes are in "${to}". "${from}" is untouched.`);
1053
1098
  console.log(`That drops "${from}" and cannot be undone.`);
1054
1099
  return;
1055
1100
  }
1056
- await switchToMigrated({ name: from, to });
1057
- console.log(`
1101
+ {
1102
+ const res = await switchToMigrated({ name: from, to });
1103
+ console.log(`
1058
1104
  Done. Nothing to change in your config \u2014 "${from}" now resolves to "${to}".`);
1105
+ if (res.snapshot) {
1106
+ console.log(`
1107
+ The old store is kept as a snapshot, ${(res.snapshot.size / 1e6).toFixed(0)} MB:`);
1108
+ console.log(` <qdrant storage>/snapshots/${from}/${res.snapshot.name}`);
1109
+ console.log(`Delete that file once the new store has proven itself. Nothing else will.`);
1110
+ }
1111
+ }
1059
1112
  return;
1060
1113
  }
1061
1114
  if (args.switchOver) {