@prom.codes/memory-mcp 0.9.0 → 0.10.0

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.
Files changed (2) hide show
  1. package/dist/bin.js +195 -19
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -871,11 +871,166 @@ var VoyageRerankProvider = class {
871
871
  }
872
872
  };
873
873
 
874
- // ../rerank-openai-compat/dist/index.js
875
- var DEFAULT_MODEL2 = "bge-reranker-base";
874
+ // ../rerank-prometheus/dist/index.js
875
+ var DEFAULT_BASE2 = "https://api.prom.codes";
876
+ var DEFAULT_NAME = "prometheus";
877
+ var DEFAULT_MODEL2 = "prometheus";
876
878
  var DEFAULT_BATCH4 = 100;
877
- var DEFAULT_RETRIES4 = 6;
878
- var DEFAULT_BACKOFF4 = 2e3;
879
+ var DEFAULT_RETRIES4 = 4;
880
+ var DEFAULT_BACKOFF4 = 250;
881
+ function sleep4(ms, signal) {
882
+ return new Promise((resolve3, reject) => {
883
+ if (signal?.aborted === true) {
884
+ reject(new Error("aborted"));
885
+ return;
886
+ }
887
+ const timer = setTimeout(() => {
888
+ signal?.removeEventListener("abort", onAbort);
889
+ resolve3();
890
+ }, ms);
891
+ const onAbort = () => {
892
+ clearTimeout(timer);
893
+ reject(new Error("aborted"));
894
+ };
895
+ signal?.addEventListener("abort", onAbort, { once: true });
896
+ });
897
+ }
898
+ function nonRetryable4(message) {
899
+ const err = new Error(message);
900
+ err.nonRetryable = true;
901
+ return err;
902
+ }
903
+ var PrometheusRerankProvider = class {
904
+ name;
905
+ model;
906
+ region;
907
+ #apiKey;
908
+ #url;
909
+ #batchSize;
910
+ #maxRetries;
911
+ #retryBaseMs;
912
+ #fetch;
913
+ #creditsUsed = 0;
914
+ constructor(opts) {
915
+ if (typeof opts.apiKey !== "string" || opts.apiKey === "") {
916
+ throw new Error("PrometheusRerankProvider: apiKey is required");
917
+ }
918
+ if (opts.batchSize !== void 0 && (!Number.isInteger(opts.batchSize) || opts.batchSize <= 0 || opts.batchSize > 1e3)) {
919
+ throw new Error(`PrometheusRerankProvider: batchSize must be an integer in 1..1000, got ${opts.batchSize}`);
920
+ }
921
+ this.name = opts.name ?? DEFAULT_NAME;
922
+ this.model = opts.model ?? DEFAULT_MODEL2;
923
+ this.region = opts.region ?? "eu";
924
+ this.#apiKey = opts.apiKey;
925
+ this.#url = `${(opts.baseUrl ?? DEFAULT_BASE2).replace(/\/+$/, "")}/rerank`;
926
+ this.#batchSize = opts.batchSize ?? DEFAULT_BATCH4;
927
+ this.#maxRetries = opts.maxRetries ?? DEFAULT_RETRIES4;
928
+ this.#retryBaseMs = opts.retryBaseMs ?? DEFAULT_BACKOFF4;
929
+ this.#fetch = opts.fetch ?? fetch;
930
+ }
931
+ /** Cumulative credits charged across all rerank calls of this instance. */
932
+ get creditsUsed() {
933
+ return this.#creditsUsed;
934
+ }
935
+ async rerank(query, candidates, opts) {
936
+ if (candidates.length === 0)
937
+ return [];
938
+ const all = new Array(candidates.length);
939
+ let cursor = 0;
940
+ for (let start = 0; start < candidates.length; start += this.#batchSize) {
941
+ const slice = candidates.slice(start, start + this.#batchSize);
942
+ const scored = await this.#rerankBatch(query, slice, opts?.signal);
943
+ for (const hit of scored) {
944
+ const globalIndex = start + hit.localIndex;
945
+ const cand = candidates[globalIndex];
946
+ all[cursor++] = { id: cand.id, index: globalIndex, score: hit.score };
947
+ }
948
+ }
949
+ all.sort((a, b) => b.score - a.score);
950
+ if (opts?.topK !== void 0 && opts.topK >= 0 && opts.topK < all.length) {
951
+ return all.slice(0, opts.topK);
952
+ }
953
+ return all;
954
+ }
955
+ async #rerankBatch(query, batch, signal) {
956
+ const init = {
957
+ method: "POST",
958
+ headers: {
959
+ "content-type": "application/json",
960
+ authorization: `Bearer ${this.#apiKey}`
961
+ },
962
+ body: JSON.stringify({ query, documents: batch.map((c) => c.text) })
963
+ };
964
+ if (signal !== void 0)
965
+ init.signal = signal;
966
+ const payload = await this.#requestJson(init, signal);
967
+ if (payload?.ok !== true || !Array.isArray(payload.results)) {
968
+ throw nonRetryable4("prometheus-rerank: malformed rerank response");
969
+ }
970
+ if (payload.results.length !== batch.length) {
971
+ throw nonRetryable4(`prometheus-rerank: expected ${batch.length} rerank rows, got ${payload.results.length}`);
972
+ }
973
+ const credits = payload.usage?.credits;
974
+ if (typeof credits === "number" && Number.isFinite(credits)) {
975
+ this.#creditsUsed += credits;
976
+ }
977
+ return payload.results.map((row) => {
978
+ if (!Number.isInteger(row.index) || row.index < 0 || row.index >= batch.length) {
979
+ throw nonRetryable4(`prometheus-rerank: invalid index ${row.index} in rerank response`);
980
+ }
981
+ if (typeof row.relevanceScore !== "number" || !Number.isFinite(row.relevanceScore)) {
982
+ throw nonRetryable4(`prometheus-rerank: invalid relevanceScore ${row.relevanceScore} at index ${row.index}`);
983
+ }
984
+ return { localIndex: row.index, score: row.relevanceScore };
985
+ });
986
+ }
987
+ /**
988
+ * Fetch with retry. 5xx and network errors back off exponentially;
989
+ * everything else (401 invalid key, 413 oversized input, 429 monthly
990
+ * quota exhausted) fails fast with the proxy's error code in the
991
+ * message.
992
+ */
993
+ async #requestJson(init, signal) {
994
+ let attempt = 0;
995
+ let lastError = null;
996
+ while (attempt <= this.#maxRetries) {
997
+ try {
998
+ const res = await this.#fetch(this.#url, init);
999
+ if (res.status >= 500 && res.status < 600) {
1000
+ lastError = new Error(`prometheus-rerank: HTTP ${res.status}`);
1001
+ attempt += 1;
1002
+ if (attempt > this.#maxRetries)
1003
+ break;
1004
+ await sleep4(this.#retryBaseMs * 2 ** (attempt - 1), signal);
1005
+ continue;
1006
+ }
1007
+ if (!res.ok) {
1008
+ const body = await res.json().catch(() => null);
1009
+ const detail = typeof body?.code === "string" ? `${body.code}${typeof body.error === "string" ? ` \u2014 ${body.error}` : ""}` : res.statusText;
1010
+ throw nonRetryable4(`prometheus-rerank: HTTP ${res.status} ${detail}`);
1011
+ }
1012
+ return await res.json();
1013
+ } catch (err) {
1014
+ if (err?.name === "AbortError")
1015
+ throw err;
1016
+ if (err?.nonRetryable === true)
1017
+ throw err;
1018
+ if (attempt >= this.#maxRetries)
1019
+ throw err;
1020
+ lastError = err;
1021
+ attempt += 1;
1022
+ await sleep4(this.#retryBaseMs * 2 ** (attempt - 1), signal);
1023
+ }
1024
+ }
1025
+ throw lastError instanceof Error ? lastError : new Error(`prometheus-rerank: exhausted ${this.#maxRetries} retries`);
1026
+ }
1027
+ };
1028
+
1029
+ // ../rerank-openai-compat/dist/index.js
1030
+ var DEFAULT_MODEL3 = "bge-reranker-base";
1031
+ var DEFAULT_BATCH5 = 100;
1032
+ var DEFAULT_RETRIES5 = 6;
1033
+ var DEFAULT_BACKOFF5 = 2e3;
879
1034
  var DEFAULT_RETRY_MAX3 = 6e4;
880
1035
  var DEFAULT_TIMEOUT = 18e4;
881
1036
  function parseRetryAfterMs3(value, now = Date.now()) {
@@ -898,7 +1053,7 @@ function parseRetryAfterMs3(value, now = Date.now()) {
898
1053
  const delta = ts - now;
899
1054
  return delta > 0 ? delta : 0;
900
1055
  }
901
- function sleep4(ms, signal) {
1056
+ function sleep5(ms, signal) {
902
1057
  return new Promise((resolve3, reject) => {
903
1058
  if (signal?.aborted === true) {
904
1059
  reject(new Error("aborted"));
@@ -915,7 +1070,7 @@ function sleep4(ms, signal) {
915
1070
  signal?.addEventListener("abort", onAbort, { once: true });
916
1071
  });
917
1072
  }
918
- function nonRetryable4(message) {
1073
+ function nonRetryable5(message) {
919
1074
  const err = new Error(message);
920
1075
  err.nonRetryable = true;
921
1076
  return err;
@@ -942,14 +1097,14 @@ var OpenAICompatRerankProvider = class {
942
1097
  if (opts.timeoutMs !== void 0 && (!Number.isInteger(opts.timeoutMs) || opts.timeoutMs < 0)) {
943
1098
  throw new Error(`OpenAICompatRerankProvider: timeoutMs must be a non-negative integer (0 disables), got ${opts.timeoutMs}`);
944
1099
  }
945
- this.model = opts.model ?? DEFAULT_MODEL2;
1100
+ this.model = opts.model ?? DEFAULT_MODEL3;
946
1101
  this.name = opts.name ?? `openai-compat:${this.model}`;
947
1102
  this.region = opts.region ?? "self-hosted";
948
1103
  this.#baseUrl = opts.baseUrl.replace(/\/+$/, "");
949
1104
  this.#apiKey = opts.apiKey === void 0 || opts.apiKey === "" ? void 0 : opts.apiKey;
950
- this.#batchSize = opts.batchSize ?? DEFAULT_BATCH4;
951
- this.#maxRetries = opts.maxRetries ?? DEFAULT_RETRIES4;
952
- this.#retryBaseMs = opts.retryBaseMs ?? DEFAULT_BACKOFF4;
1105
+ this.#batchSize = opts.batchSize ?? DEFAULT_BATCH5;
1106
+ this.#maxRetries = opts.maxRetries ?? DEFAULT_RETRIES5;
1107
+ this.#retryBaseMs = opts.retryBaseMs ?? DEFAULT_BACKOFF5;
953
1108
  this.#retryMaxMs = opts.retryMaxMs ?? DEFAULT_RETRY_MAX3;
954
1109
  this.#timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT;
955
1110
  this.#fetch = opts.fetch ?? fetch;
@@ -1018,12 +1173,12 @@ var OpenAICompatRerankProvider = class {
1018
1173
  if (attempt > this.#maxRetries)
1019
1174
  break;
1020
1175
  const backoff = this.#computeBackoff(attempt, res.headers.get("retry-after"));
1021
- await sleep4(backoff, signal);
1176
+ await sleep5(backoff, signal);
1022
1177
  continue;
1023
1178
  }
1024
1179
  if (!res.ok) {
1025
1180
  const text = await res.text().catch(() => "");
1026
- throw nonRetryable4(`${this.name}: HTTP ${res.status} ${res.statusText}${text === "" ? "" : ` \u2014 ${text}`}`);
1181
+ throw nonRetryable5(`${this.name}: HTTP ${res.status} ${res.statusText}${text === "" ? "" : ` \u2014 ${text}`}`);
1027
1182
  }
1028
1183
  const payload = await res.json();
1029
1184
  return this.#decode(payload, batch.length);
@@ -1038,7 +1193,7 @@ var OpenAICompatRerankProvider = class {
1038
1193
  throw normalized;
1039
1194
  lastError = normalized;
1040
1195
  attempt += 1;
1041
- await sleep4(this.#computeBackoff(attempt, null), signal);
1196
+ await sleep5(this.#computeBackoff(attempt, null), signal);
1042
1197
  } finally {
1043
1198
  if (timer !== void 0)
1044
1199
  clearTimeout(timer);
@@ -1056,14 +1211,14 @@ var OpenAICompatRerankProvider = class {
1056
1211
  }
1057
1212
  #decode(payload, expected) {
1058
1213
  if (!Array.isArray(payload.results) || payload.results.length !== expected) {
1059
- throw nonRetryable4(`${this.name}: expected ${expected} rerank rows, got ${payload.results?.length ?? 0}`);
1214
+ throw nonRetryable5(`${this.name}: expected ${expected} rerank rows, got ${payload.results?.length ?? 0}`);
1060
1215
  }
1061
1216
  return payload.results.map((row) => {
1062
1217
  if (!Number.isInteger(row.index) || row.index < 0 || row.index >= expected) {
1063
- throw nonRetryable4(`${this.name}: invalid index ${row.index} in rerank response`);
1218
+ throw nonRetryable5(`${this.name}: invalid index ${row.index} in rerank response`);
1064
1219
  }
1065
1220
  if (typeof row.relevance_score !== "number" || !Number.isFinite(row.relevance_score)) {
1066
- throw nonRetryable4(`${this.name}: invalid relevance_score ${row.relevance_score} at index ${row.index}`);
1221
+ throw nonRetryable5(`${this.name}: invalid relevance_score ${row.relevance_score} at index ${row.index}`);
1067
1222
  }
1068
1223
  return { localIndex: row.index, score: row.relevance_score };
1069
1224
  });
@@ -1599,6 +1754,7 @@ var SqliteMemoryBackend = class {
1599
1754
  }
1600
1755
  this.db = new Database(dbPath);
1601
1756
  this.db.pragma("journal_mode = WAL");
1757
+ this.db.pragma("synchronous = NORMAL");
1602
1758
  this.db.exec(SCHEMA);
1603
1759
  this.db.exec(FTS_SCHEMA);
1604
1760
  this.db.exec(VEC_SCHEMA);
@@ -2033,6 +2189,10 @@ ${h.record.value}`
2033
2189
  if (this.closed)
2034
2190
  return;
2035
2191
  this.closed = true;
2192
+ try {
2193
+ this.db.pragma("wal_checkpoint(TRUNCATE)");
2194
+ } catch {
2195
+ }
2036
2196
  this.db.close();
2037
2197
  }
2038
2198
  };
@@ -2102,9 +2262,25 @@ function discoverMemoryEmbedder(env) {
2102
2262
  return { id: "none", embedder: void 0 };
2103
2263
  }
2104
2264
  function discoverMemoryReranker(env) {
2105
- const forced = (env.PROMETHEUS_MEMORY_RERANK_PROVIDER ?? "none").toLowerCase();
2106
- if (forced === "" || forced === "none")
2265
+ const explicit = env.PROMETHEUS_MEMORY_RERANK_PROVIDER?.toLowerCase();
2266
+ const hasKey = (env[API_KEY_ENV] ?? "").trim() !== "";
2267
+ const forced = explicit === void 0 || explicit === "" ? hasKey ? "prometheus" : "none" : explicit;
2268
+ if (forced === "none")
2107
2269
  return { id: "none", provider: null };
2270
+ if (forced === "prometheus") {
2271
+ const apiKey = requireApiKey(env);
2272
+ const baseUrl = env.PROMETHEUS_API_URL;
2273
+ const provider = new PrometheusRerankProvider({
2274
+ name: "prometheus-rerank",
2275
+ apiKey,
2276
+ region: "eu",
2277
+ ...baseUrl !== void 0 && baseUrl !== "" ? { baseUrl } : {},
2278
+ maxRetries: intEnv(env, "PROMETHEUS_MEMORY_RERANK_MAX_RETRIES", 4),
2279
+ retryBaseMs: intEnv(env, "PROMETHEUS_MEMORY_RERANK_RETRY_BASE_MS", 250),
2280
+ batchSize: intEnv(env, "PROMETHEUS_MEMORY_RERANK_BATCH", 100)
2281
+ });
2282
+ return { id: "prometheus", provider };
2283
+ }
2108
2284
  if (forced === "voyage") {
2109
2285
  const apiKey = env.VOYAGE_API_KEY;
2110
2286
  if (apiKey === void 0 || apiKey === "") {
@@ -2141,7 +2317,7 @@ function discoverMemoryReranker(env) {
2141
2317
  });
2142
2318
  return { id: "bge", provider };
2143
2319
  }
2144
- throw new Error(`unknown PROMETHEUS_MEMORY_RERANK_PROVIDER="${forced}" (expected "none", "voyage", or "bge")`);
2320
+ throw new Error(`unknown PROMETHEUS_MEMORY_RERANK_PROVIDER="${forced}" (expected "none", "prometheus", "voyage", or "bge")`);
2145
2321
  }
2146
2322
  function discoverMemoryExtractor(env) {
2147
2323
  const forced = (env.PROMETHEUS_MEMORY_EXTRACT_PROVIDER ?? "none").toLowerCase();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prom.codes/memory-mcp",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "prom.codes Memory — persistent, local-first agent memory as an MCP server.",
5
5
  "type": "module",
6
6
  "bin": {