@prom.codes/memory-mcp 0.9.1 → 0.10.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.
Files changed (2) hide show
  1. package/dist/bin.js +216 -19
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -871,11 +871,192 @@ 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_BATCH_CHARS2 = 4e5;
880
+ var DEFAULT_RETRIES4 = 4;
881
+ var DEFAULT_BACKOFF4 = 250;
882
+ function sleep4(ms, signal) {
883
+ return new Promise((resolve3, reject) => {
884
+ if (signal?.aborted === true) {
885
+ reject(new Error("aborted"));
886
+ return;
887
+ }
888
+ const timer = setTimeout(() => {
889
+ signal?.removeEventListener("abort", onAbort);
890
+ resolve3();
891
+ }, ms);
892
+ const onAbort = () => {
893
+ clearTimeout(timer);
894
+ reject(new Error("aborted"));
895
+ };
896
+ signal?.addEventListener("abort", onAbort, { once: true });
897
+ });
898
+ }
899
+ function nonRetryable4(message) {
900
+ const err = new Error(message);
901
+ err.nonRetryable = true;
902
+ return err;
903
+ }
904
+ var PrometheusRerankProvider = class {
905
+ name;
906
+ model;
907
+ region;
908
+ #apiKey;
909
+ #url;
910
+ #batchSize;
911
+ #maxBatchChars;
912
+ #maxRetries;
913
+ #retryBaseMs;
914
+ #fetch;
915
+ #creditsUsed = 0;
916
+ constructor(opts) {
917
+ if (typeof opts.apiKey !== "string" || opts.apiKey === "") {
918
+ throw new Error("PrometheusRerankProvider: apiKey is required");
919
+ }
920
+ if (opts.batchSize !== void 0 && (!Number.isInteger(opts.batchSize) || opts.batchSize <= 0 || opts.batchSize > 1e3)) {
921
+ throw new Error(`PrometheusRerankProvider: batchSize must be an integer in 1..1000, got ${opts.batchSize}`);
922
+ }
923
+ this.name = opts.name ?? DEFAULT_NAME;
924
+ this.model = opts.model ?? DEFAULT_MODEL2;
925
+ this.region = opts.region ?? "eu";
926
+ this.#apiKey = opts.apiKey;
927
+ this.#url = `${(opts.baseUrl ?? DEFAULT_BASE2).replace(/\/+$/, "")}/rerank`;
928
+ this.#batchSize = opts.batchSize ?? DEFAULT_BATCH4;
929
+ this.#maxBatchChars = opts.maxBatchChars ?? DEFAULT_BATCH_CHARS2;
930
+ this.#maxRetries = opts.maxRetries ?? DEFAULT_RETRIES4;
931
+ this.#retryBaseMs = opts.retryBaseMs ?? DEFAULT_BACKOFF4;
932
+ this.#fetch = opts.fetch ?? fetch;
933
+ }
934
+ /** Cumulative credits charged across all rerank calls of this instance. */
935
+ get creditsUsed() {
936
+ return this.#creditsUsed;
937
+ }
938
+ async rerank(query, candidates, opts) {
939
+ if (candidates.length === 0)
940
+ return [];
941
+ const all = new Array(candidates.length);
942
+ let cursor = 0;
943
+ let start = 0;
944
+ while (start < candidates.length) {
945
+ const end = this.#batchEnd(query, candidates, start);
946
+ const slice = candidates.slice(start, end);
947
+ const scored = await this.#rerankBatch(query, slice, opts?.signal);
948
+ for (const hit of scored) {
949
+ const globalIndex = start + hit.localIndex;
950
+ const cand = candidates[globalIndex];
951
+ all[cursor++] = { id: cand.id, index: globalIndex, score: hit.score };
952
+ }
953
+ start = end;
954
+ }
955
+ all.length = cursor;
956
+ all.sort((a, b) => b.score - a.score);
957
+ if (opts?.topK !== void 0 && opts.topK >= 0 && opts.topK < all.length) {
958
+ return all.slice(0, opts.topK);
959
+ }
960
+ return all;
961
+ }
962
+ /**
963
+ * Greedy batch cut honouring both the document-count cap and the
964
+ * char budget (query + documents must stay under the proxy limit).
965
+ * Always advances by at least one candidate, even if a single
966
+ * document alone exceeds the budget (the proxy applies upstream
967
+ * truncation; refusing to send it would strand the rest).
968
+ */
969
+ #batchEnd(query, candidates, start) {
970
+ let chars = query.length;
971
+ let end = start;
972
+ while (end < candidates.length && end - start < this.#batchSize) {
973
+ const len = candidates[end].text.length;
974
+ if (end > start && chars + len > this.#maxBatchChars)
975
+ break;
976
+ chars += len;
977
+ end += 1;
978
+ }
979
+ return end;
980
+ }
981
+ async #rerankBatch(query, batch, signal) {
982
+ const init = {
983
+ method: "POST",
984
+ headers: {
985
+ "content-type": "application/json",
986
+ authorization: `Bearer ${this.#apiKey}`
987
+ },
988
+ body: JSON.stringify({ query, documents: batch.map((c) => c.text) })
989
+ };
990
+ if (signal !== void 0)
991
+ init.signal = signal;
992
+ const payload = await this.#requestJson(init, signal);
993
+ if (payload?.ok !== true || !Array.isArray(payload.results)) {
994
+ throw nonRetryable4("prometheus-rerank: malformed rerank response");
995
+ }
996
+ if (payload.results.length !== batch.length) {
997
+ throw nonRetryable4(`prometheus-rerank: expected ${batch.length} rerank rows, got ${payload.results.length}`);
998
+ }
999
+ const credits = payload.usage?.credits;
1000
+ if (typeof credits === "number" && Number.isFinite(credits)) {
1001
+ this.#creditsUsed += credits;
1002
+ }
1003
+ return payload.results.map((row) => {
1004
+ if (!Number.isInteger(row.index) || row.index < 0 || row.index >= batch.length) {
1005
+ throw nonRetryable4(`prometheus-rerank: invalid index ${row.index} in rerank response`);
1006
+ }
1007
+ if (typeof row.relevanceScore !== "number" || !Number.isFinite(row.relevanceScore)) {
1008
+ throw nonRetryable4(`prometheus-rerank: invalid relevanceScore ${row.relevanceScore} at index ${row.index}`);
1009
+ }
1010
+ return { localIndex: row.index, score: row.relevanceScore };
1011
+ });
1012
+ }
1013
+ /**
1014
+ * Fetch with retry. 5xx and network errors back off exponentially;
1015
+ * everything else (401 invalid key, 413 oversized input, 429 monthly
1016
+ * quota exhausted) fails fast with the proxy's error code in the
1017
+ * message.
1018
+ */
1019
+ async #requestJson(init, signal) {
1020
+ let attempt = 0;
1021
+ let lastError = null;
1022
+ while (attempt <= this.#maxRetries) {
1023
+ try {
1024
+ const res = await this.#fetch(this.#url, init);
1025
+ if (res.status >= 500 && res.status < 600) {
1026
+ lastError = new Error(`prometheus-rerank: HTTP ${res.status}`);
1027
+ attempt += 1;
1028
+ if (attempt > this.#maxRetries)
1029
+ break;
1030
+ await sleep4(this.#retryBaseMs * 2 ** (attempt - 1), signal);
1031
+ continue;
1032
+ }
1033
+ if (!res.ok) {
1034
+ const body = await res.json().catch(() => null);
1035
+ const detail = typeof body?.code === "string" ? `${body.code}${typeof body.error === "string" ? ` \u2014 ${body.error}` : ""}` : res.statusText;
1036
+ throw nonRetryable4(`prometheus-rerank: HTTP ${res.status} ${detail}`);
1037
+ }
1038
+ return await res.json();
1039
+ } catch (err) {
1040
+ if (err?.name === "AbortError")
1041
+ throw err;
1042
+ if (err?.nonRetryable === true)
1043
+ throw err;
1044
+ if (attempt >= this.#maxRetries)
1045
+ throw err;
1046
+ lastError = err;
1047
+ attempt += 1;
1048
+ await sleep4(this.#retryBaseMs * 2 ** (attempt - 1), signal);
1049
+ }
1050
+ }
1051
+ throw lastError instanceof Error ? lastError : new Error(`prometheus-rerank: exhausted ${this.#maxRetries} retries`);
1052
+ }
1053
+ };
1054
+
1055
+ // ../rerank-openai-compat/dist/index.js
1056
+ var DEFAULT_MODEL3 = "bge-reranker-base";
1057
+ var DEFAULT_BATCH5 = 100;
1058
+ var DEFAULT_RETRIES5 = 6;
1059
+ var DEFAULT_BACKOFF5 = 2e3;
879
1060
  var DEFAULT_RETRY_MAX3 = 6e4;
880
1061
  var DEFAULT_TIMEOUT = 18e4;
881
1062
  function parseRetryAfterMs3(value, now = Date.now()) {
@@ -898,7 +1079,7 @@ function parseRetryAfterMs3(value, now = Date.now()) {
898
1079
  const delta = ts - now;
899
1080
  return delta > 0 ? delta : 0;
900
1081
  }
901
- function sleep4(ms, signal) {
1082
+ function sleep5(ms, signal) {
902
1083
  return new Promise((resolve3, reject) => {
903
1084
  if (signal?.aborted === true) {
904
1085
  reject(new Error("aborted"));
@@ -915,7 +1096,7 @@ function sleep4(ms, signal) {
915
1096
  signal?.addEventListener("abort", onAbort, { once: true });
916
1097
  });
917
1098
  }
918
- function nonRetryable4(message) {
1099
+ function nonRetryable5(message) {
919
1100
  const err = new Error(message);
920
1101
  err.nonRetryable = true;
921
1102
  return err;
@@ -942,14 +1123,14 @@ var OpenAICompatRerankProvider = class {
942
1123
  if (opts.timeoutMs !== void 0 && (!Number.isInteger(opts.timeoutMs) || opts.timeoutMs < 0)) {
943
1124
  throw new Error(`OpenAICompatRerankProvider: timeoutMs must be a non-negative integer (0 disables), got ${opts.timeoutMs}`);
944
1125
  }
945
- this.model = opts.model ?? DEFAULT_MODEL2;
1126
+ this.model = opts.model ?? DEFAULT_MODEL3;
946
1127
  this.name = opts.name ?? `openai-compat:${this.model}`;
947
1128
  this.region = opts.region ?? "self-hosted";
948
1129
  this.#baseUrl = opts.baseUrl.replace(/\/+$/, "");
949
1130
  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;
1131
+ this.#batchSize = opts.batchSize ?? DEFAULT_BATCH5;
1132
+ this.#maxRetries = opts.maxRetries ?? DEFAULT_RETRIES5;
1133
+ this.#retryBaseMs = opts.retryBaseMs ?? DEFAULT_BACKOFF5;
953
1134
  this.#retryMaxMs = opts.retryMaxMs ?? DEFAULT_RETRY_MAX3;
954
1135
  this.#timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT;
955
1136
  this.#fetch = opts.fetch ?? fetch;
@@ -1018,12 +1199,12 @@ var OpenAICompatRerankProvider = class {
1018
1199
  if (attempt > this.#maxRetries)
1019
1200
  break;
1020
1201
  const backoff = this.#computeBackoff(attempt, res.headers.get("retry-after"));
1021
- await sleep4(backoff, signal);
1202
+ await sleep5(backoff, signal);
1022
1203
  continue;
1023
1204
  }
1024
1205
  if (!res.ok) {
1025
1206
  const text = await res.text().catch(() => "");
1026
- throw nonRetryable4(`${this.name}: HTTP ${res.status} ${res.statusText}${text === "" ? "" : ` \u2014 ${text}`}`);
1207
+ throw nonRetryable5(`${this.name}: HTTP ${res.status} ${res.statusText}${text === "" ? "" : ` \u2014 ${text}`}`);
1027
1208
  }
1028
1209
  const payload = await res.json();
1029
1210
  return this.#decode(payload, batch.length);
@@ -1038,7 +1219,7 @@ var OpenAICompatRerankProvider = class {
1038
1219
  throw normalized;
1039
1220
  lastError = normalized;
1040
1221
  attempt += 1;
1041
- await sleep4(this.#computeBackoff(attempt, null), signal);
1222
+ await sleep5(this.#computeBackoff(attempt, null), signal);
1042
1223
  } finally {
1043
1224
  if (timer !== void 0)
1044
1225
  clearTimeout(timer);
@@ -1056,14 +1237,14 @@ var OpenAICompatRerankProvider = class {
1056
1237
  }
1057
1238
  #decode(payload, expected) {
1058
1239
  if (!Array.isArray(payload.results) || payload.results.length !== expected) {
1059
- throw nonRetryable4(`${this.name}: expected ${expected} rerank rows, got ${payload.results?.length ?? 0}`);
1240
+ throw nonRetryable5(`${this.name}: expected ${expected} rerank rows, got ${payload.results?.length ?? 0}`);
1060
1241
  }
1061
1242
  return payload.results.map((row) => {
1062
1243
  if (!Number.isInteger(row.index) || row.index < 0 || row.index >= expected) {
1063
- throw nonRetryable4(`${this.name}: invalid index ${row.index} in rerank response`);
1244
+ throw nonRetryable5(`${this.name}: invalid index ${row.index} in rerank response`);
1064
1245
  }
1065
1246
  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}`);
1247
+ throw nonRetryable5(`${this.name}: invalid relevance_score ${row.relevance_score} at index ${row.index}`);
1067
1248
  }
1068
1249
  return { localIndex: row.index, score: row.relevance_score };
1069
1250
  });
@@ -2107,9 +2288,25 @@ function discoverMemoryEmbedder(env) {
2107
2288
  return { id: "none", embedder: void 0 };
2108
2289
  }
2109
2290
  function discoverMemoryReranker(env) {
2110
- const forced = (env.PROMETHEUS_MEMORY_RERANK_PROVIDER ?? "none").toLowerCase();
2111
- if (forced === "" || forced === "none")
2291
+ const explicit = env.PROMETHEUS_MEMORY_RERANK_PROVIDER?.toLowerCase();
2292
+ const hasKey = (env[API_KEY_ENV] ?? "").trim() !== "";
2293
+ const forced = explicit === void 0 || explicit === "" ? hasKey ? "prometheus" : "none" : explicit;
2294
+ if (forced === "none")
2112
2295
  return { id: "none", provider: null };
2296
+ if (forced === "prometheus") {
2297
+ const apiKey = requireApiKey(env);
2298
+ const baseUrl = env.PROMETHEUS_API_URL;
2299
+ const provider = new PrometheusRerankProvider({
2300
+ name: "prometheus-rerank",
2301
+ apiKey,
2302
+ region: "eu",
2303
+ ...baseUrl !== void 0 && baseUrl !== "" ? { baseUrl } : {},
2304
+ maxRetries: intEnv(env, "PROMETHEUS_MEMORY_RERANK_MAX_RETRIES", 4),
2305
+ retryBaseMs: intEnv(env, "PROMETHEUS_MEMORY_RERANK_RETRY_BASE_MS", 250),
2306
+ batchSize: intEnv(env, "PROMETHEUS_MEMORY_RERANK_BATCH", 100)
2307
+ });
2308
+ return { id: "prometheus", provider };
2309
+ }
2113
2310
  if (forced === "voyage") {
2114
2311
  const apiKey = env.VOYAGE_API_KEY;
2115
2312
  if (apiKey === void 0 || apiKey === "") {
@@ -2146,7 +2343,7 @@ function discoverMemoryReranker(env) {
2146
2343
  });
2147
2344
  return { id: "bge", provider };
2148
2345
  }
2149
- throw new Error(`unknown PROMETHEUS_MEMORY_RERANK_PROVIDER="${forced}" (expected "none", "voyage", or "bge")`);
2346
+ throw new Error(`unknown PROMETHEUS_MEMORY_RERANK_PROVIDER="${forced}" (expected "none", "prometheus", "voyage", or "bge")`);
2150
2347
  }
2151
2348
  function discoverMemoryExtractor(env) {
2152
2349
  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.1",
3
+ "version": "0.10.1",
4
4
  "description": "prom.codes Memory — persistent, local-first agent memory as an MCP server.",
5
5
  "type": "module",
6
6
  "bin": {