memorix 1.7.1 → 1.7.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +43 -1
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/assets/star-history-dark.svg +4 -4
  5. package/assets/star-history-light.svg +4 -4
  6. package/assets/star-history.json +47 -3
  7. package/dist/cli/index.js +423 -74
  8. package/dist/index.js +396 -73
  9. package/dist/maintenance-runner.js +393 -69
  10. package/dist/media-audio-runner.js +393 -69
  11. package/dist/media-video-runner.js +393 -69
  12. package/dist/memcode-runtime/CHANGELOG.md +43 -1
  13. package/dist/sdk.js +395 -72
  14. package/dist/vector-backfill-runner.js +393 -69
  15. package/docs/ARCHITECTURE.md +1 -1
  16. package/docs/CONFIGURATION.md +57 -1
  17. package/docs/DEVELOPMENT.md +6 -2
  18. package/docs/PERFORMANCE.md +13 -4
  19. package/docs/README.md +3 -1
  20. package/docs/SETUP.md +2 -1
  21. package/package.json +1 -1
  22. package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
  23. package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
  24. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  25. package/plugins/copilot/memorix/plugin.json +1 -1
  26. package/plugins/gemini/memorix/gemini-extension.json +1 -1
  27. package/plugins/omp/memorix/package.json +1 -1
  28. package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
  29. package/plugins/pi/memorix/package.json +1 -1
  30. package/server.json +2 -2
  31. package/src/cli/commands/config-migrate.ts +7 -0
  32. package/src/cli/commands/doctor.ts +5 -1
  33. package/src/cli/commands/init.ts +17 -0
  34. package/src/cli/commands/status.ts +2 -0
  35. package/src/config/resolved-config.ts +110 -12
  36. package/src/config/toml-loader.ts +8 -0
  37. package/src/config/yaml-loader.ts +9 -0
  38. package/src/llm/provider.ts +33 -7
  39. package/src/multimodal/image-loader.ts +6 -3
  40. package/src/rerank/http-provider.ts +125 -0
  41. package/src/rerank/index.ts +81 -0
  42. package/src/rerank/policy.ts +88 -0
  43. package/src/store/orama-store.ts +111 -65
package/dist/index.js CHANGED
@@ -2793,6 +2793,7 @@ function loadYamlConfig(projectRoot) {
2793
2793
  llm: { ...userConfig.llm, ...projectConfig.llm },
2794
2794
  agent: { ...userConfig.agent, ...projectConfig.agent },
2795
2795
  embedding: { ...userConfig.embedding, ...projectConfig.embedding },
2796
+ rerank: { ...userConfig.rerank, ...projectConfig.rerank },
2796
2797
  git: { ...userConfig.git, ...projectConfig.git },
2797
2798
  codegraph: { ...userConfig.codegraph, ...projectConfig.codegraph },
2798
2799
  behavior: { ...userConfig.behavior, ...projectConfig.behavior },
@@ -3068,6 +3069,7 @@ function mergeTomlConfig(base, override) {
3068
3069
  llm: { ...base.memory?.llm, ...override.memory?.llm }
3069
3070
  },
3070
3071
  embedding: { ...base.embedding, ...override.embedding },
3072
+ rerank: { ...base.rerank, ...override.rerank },
3071
3073
  hooks: { ...base.hooks, ...override.hooks },
3072
3074
  git: { ...base.git, ...override.git },
3073
3075
  codegraph: { ...base.codegraph, ...override.codegraph },
@@ -3223,6 +3225,7 @@ __export(resolved_config_exports, {
3223
3225
  getResolvedConfigForCwd: () => getResolvedConfigForCwd,
3224
3226
  getResolvedEmbeddingLane: () => getResolvedEmbeddingLane,
3225
3227
  getResolvedMemoryLane: () => getResolvedMemoryLane,
3228
+ getResolvedRerankLane: () => getResolvedRerankLane,
3226
3229
  resetResolvedConfigCache: () => resetResolvedConfigCache
3227
3230
  });
3228
3231
  import { homedir as homedir5 } from "os";
@@ -3255,6 +3258,16 @@ function getResolvedConfig(options = {}) {
3255
3258
  legacy.llm?.baseUrl
3256
3259
  );
3257
3260
  const openRouterMemoryLlmApiKey = isOpenRouterMemoryLane(memoryLlmProvider, memoryLlmBaseUrl) ? process.env.OPENROUTER_API_KEY : void 0;
3261
+ const memoryLlmApiKey = first(
3262
+ process.env.MEMORIX_LLM_API_KEY,
3263
+ process.env.MEMORIX_API_KEY,
3264
+ toml.memory?.llm?.api_key,
3265
+ yaml.llm?.apiKey,
3266
+ legacy.llm?.apiKey,
3267
+ process.env.OPENAI_API_KEY,
3268
+ process.env.ANTHROPIC_API_KEY,
3269
+ openRouterMemoryLlmApiKey
3270
+ );
3258
3271
  const resolved = {
3259
3272
  agent: {
3260
3273
  provider: first(
@@ -3295,16 +3308,7 @@ function getResolvedConfig(options = {}) {
3295
3308
  provider: memoryLlmProvider,
3296
3309
  model: memoryLlmModel,
3297
3310
  baseUrl: memoryLlmBaseUrl,
3298
- apiKey: first(
3299
- process.env.MEMORIX_LLM_API_KEY,
3300
- process.env.MEMORIX_API_KEY,
3301
- toml.memory?.llm?.api_key,
3302
- yaml.llm?.apiKey,
3303
- legacy.llm?.apiKey,
3304
- process.env.OPENAI_API_KEY,
3305
- process.env.ANTHROPIC_API_KEY,
3306
- openRouterMemoryLlmApiKey
3307
- )
3311
+ apiKey: memoryLlmApiKey
3308
3312
  }
3309
3313
  },
3310
3314
  embedding: {
@@ -3314,6 +3318,12 @@ function getResolvedConfig(options = {}) {
3314
3318
  apiKey: first(process.env.MEMORIX_EMBEDDING_API_KEY, toml.embedding?.api_key, yaml.embedding?.apiKey, legacy.embeddingApi?.apiKey, openRouterEmbeddingApiKey),
3315
3319
  dimensions: firstNumber(parseNumber(process.env.MEMORIX_EMBEDDING_DIMENSIONS), toml.embedding?.dimensions, yaml.embedding?.dimensions, legacy.embeddingApi?.dimensions)
3316
3320
  },
3321
+ rerank: resolveRerankLane({
3322
+ toml,
3323
+ yaml,
3324
+ memoryLlmApiKey,
3325
+ memoryLlmBaseUrl
3326
+ }),
3317
3327
  git: {
3318
3328
  autoHook: firstBool(toml.git?.auto_hook, yaml.git?.autoHook),
3319
3329
  ingestOnCommit: firstBool(toml.git?.ingest_on_commit, yaml.git?.ingestOnCommit),
@@ -3383,6 +3393,9 @@ function getResolvedMemoryLane(options = {}) {
3383
3393
  function getResolvedEmbeddingLane(options = {}) {
3384
3394
  return getResolvedConfig(options).embedding;
3385
3395
  }
3396
+ function getResolvedRerankLane(options = {}) {
3397
+ return getResolvedConfig(options).rerank;
3398
+ }
3386
3399
  function resetResolvedConfigCache() {
3387
3400
  }
3388
3401
  function first(...values) {
@@ -3425,6 +3438,10 @@ function getEnvSourceNames() {
3425
3438
  "MEMORIX_EMBEDDING_BASE_URL",
3426
3439
  "MEMORIX_EMBEDDING_MODEL",
3427
3440
  "MEMORIX_EMBEDDING_DIMENSIONS",
3441
+ "MEMORIX_RERANK_PROVIDER",
3442
+ "MEMORIX_RERANK_MODEL",
3443
+ "MEMORIX_RERANK_BASE_URL",
3444
+ "MEMORIX_RERANK_API_KEY",
3428
3445
  "MEMORIX_CODEGRAPH_EXTERNAL_CONTEXT",
3429
3446
  "MEMORIX_CODEGRAPH_EXTERNAL_COMMAND",
3430
3447
  "MEMORIX_CODEGRAPH_EXTERNAL_TIMEOUT_MS",
@@ -3448,6 +3465,63 @@ function normalizeExternalContext(value) {
3448
3465
  if (normalized === "auto" || normalized === "off") return normalized;
3449
3466
  return void 0;
3450
3467
  }
3468
+ function normalizeRerankProvider(value) {
3469
+ const normalized = value?.trim().toLowerCase();
3470
+ if (normalized === "http") return "http";
3471
+ return "off";
3472
+ }
3473
+ function resolveRerankLane(args) {
3474
+ const provider2 = normalizeRerankProvider(first(
3475
+ process.env.MEMORIX_RERANK_PROVIDER,
3476
+ args.toml.rerank?.provider,
3477
+ args.yaml.rerank?.provider,
3478
+ "off"
3479
+ ));
3480
+ const model = first(
3481
+ process.env.MEMORIX_RERANK_MODEL,
3482
+ args.toml.rerank?.model,
3483
+ args.yaml.rerank?.model
3484
+ );
3485
+ const explicitBaseUrl = first(
3486
+ process.env.MEMORIX_RERANK_BASE_URL,
3487
+ args.toml.rerank?.base_url,
3488
+ args.yaml.rerank?.baseUrl
3489
+ );
3490
+ const baseUrl = provider2 === "http" ? first(explicitBaseUrl, args.memoryLlmBaseUrl) : explicitBaseUrl;
3491
+ const dedicatedApiKey = first(
3492
+ process.env.MEMORIX_RERANK_API_KEY,
3493
+ args.toml.rerank?.api_key,
3494
+ args.yaml.rerank?.apiKey
3495
+ );
3496
+ const sameEndpoint = provider2 === "http" && sameTrustedEndpoint(baseUrl, args.memoryLlmBaseUrl);
3497
+ const inheritedApiKey = sameEndpoint ? args.memoryLlmApiKey : void 0;
3498
+ const apiKey = first(dedicatedApiKey, inheritedApiKey);
3499
+ const canSendRequest = provider2 === "http" && Boolean(baseUrl) && (Boolean(apiKey) || !args.memoryLlmApiKey || sameEndpoint);
3500
+ return { provider: provider2, model, baseUrl, apiKey, canSendRequest };
3501
+ }
3502
+ function sameTrustedEndpoint(left, right) {
3503
+ const a = normalizeTrustedEndpoint(left);
3504
+ const b = normalizeTrustedEndpoint(right);
3505
+ return Boolean(a && b && a === b);
3506
+ }
3507
+ function normalizeTrustedEndpoint(value) {
3508
+ const trimmed = value?.trim();
3509
+ if (!trimmed) return void 0;
3510
+ try {
3511
+ const url = new URL(trimmed);
3512
+ const protocol = url.protocol.toLowerCase();
3513
+ const hostname = url.hostname.toLowerCase();
3514
+ const port = url.port;
3515
+ let pathname = url.pathname.replace(/\/+$/, "");
3516
+ if (pathname.toLowerCase().endsWith("/rerank")) {
3517
+ pathname = pathname.slice(0, -"/rerank".length).replace(/\/+$/, "");
3518
+ }
3519
+ return `${protocol}//${hostname}${port ? `:${port}` : ""}${pathname}`;
3520
+ } catch {
3521
+ const fallback = trimmed.replace(/\/+$/, "").replace(/\/rerank$/i, "").replace(/\/+$/, "");
3522
+ return fallback.toLowerCase() || void 0;
3523
+ }
3524
+ }
3451
3525
  var init_resolved_config = __esm({
3452
3526
  "src/config/resolved-config.ts"() {
3453
3527
  "use strict";
@@ -5683,9 +5757,27 @@ __export(provider_exports2, {
5683
5757
  getLLMConfig: () => getLLMConfig,
5684
5758
  initLLM: () => initLLM,
5685
5759
  isLLMEnabled: () => isLLMEnabled,
5760
+ normalizeOpenAICompatibleBaseUrl: () => normalizeOpenAICompatibleBaseUrl,
5686
5761
  parseLLMTimeoutMs: () => parseLLMTimeoutMs,
5687
5762
  setLLMConfig: () => setLLMConfig
5688
5763
  });
5764
+ function normalizeOpenAICompatibleBaseUrl(baseUrl) {
5765
+ const base = baseUrl.trim().replace(/\/+$/, "");
5766
+ if (!base) return base;
5767
+ let pathname = "";
5768
+ try {
5769
+ pathname = new URL(base).pathname.replace(/\/+$/, "");
5770
+ } catch {
5771
+ pathname = base.split(/[?#]/, 1)[0].replace(/\/+$/, "");
5772
+ }
5773
+ const lastSegment = pathname.split("/").filter(Boolean).at(-1);
5774
+ if (lastSegment && OPENAI_API_VERSION_SEGMENT.test(lastSegment)) {
5775
+ return base;
5776
+ }
5777
+ const queryOrHash = base.match(/[?#].*$/)?.[0] ?? "";
5778
+ const pathWithoutQuery = base.slice(0, base.length - queryOrHash.length).replace(/\/+$/, "");
5779
+ return `${pathWithoutQuery}/v1${queryOrHash}`;
5780
+ }
5689
5781
  function parseLLMTimeoutMs(raw) {
5690
5782
  if (raw === void 0 || raw.trim() === "") return LLM_TIMEOUT_DEFAULT_MS;
5691
5783
  const parsed = Number(raw);
@@ -5789,8 +5881,7 @@ async function callLLM(systemPrompt, userMessage, signal) {
5789
5881
  }
5790
5882
  async function callOpenAICompatible(systemPrompt, userMessage, signal) {
5791
5883
  const config = currentConfig;
5792
- let base = config.baseUrl.replace(/\/+$/, "");
5793
- if (!base.endsWith("/v1")) base += "/v1";
5884
+ const base = normalizeOpenAICompatibleBaseUrl(config.baseUrl);
5794
5885
  const url = `${base}/chat/completions`;
5795
5886
  const fetchSignal = signal ? AbortSignal.any([signal, AbortSignal.timeout(LLM_CALL_TIMEOUT_MS)]) : AbortSignal.timeout(LLM_CALL_TIMEOUT_MS);
5796
5887
  const response = await fetch(url, {
@@ -5869,8 +5960,7 @@ async function callLLMWithTools(messages, tools, signal) {
5869
5960
  }
5870
5961
  async function callOpenAIWithTools(messages, tools, signal) {
5871
5962
  const config = currentConfig;
5872
- let base = config.baseUrl.replace(/\/+$/, "");
5873
- if (!base.endsWith("/v1")) base += "/v1";
5963
+ const base = normalizeOpenAICompatibleBaseUrl(config.baseUrl);
5874
5964
  const url = `${base}/chat/completions`;
5875
5965
  const openaiMessages = messages.map((msg) => {
5876
5966
  if (msg.role === "tool") {
@@ -6020,8 +6110,7 @@ async function callAnthropicWithTools(messages, tools, signal) {
6020
6110
  }
6021
6111
  async function* callOpenAIWithToolsStream(messages, tools) {
6022
6112
  const config = currentConfig;
6023
- let base = config.baseUrl.replace(/\/+$/, "");
6024
- if (!base.endsWith("/v1")) base += "/v1";
6113
+ const base = normalizeOpenAICompatibleBaseUrl(config.baseUrl);
6025
6114
  const url = `${base}/chat/completions`;
6026
6115
  const openaiMessages = messages.map((msg) => {
6027
6116
  if (msg.role === "tool") {
@@ -6269,7 +6358,7 @@ async function* callAnthropicWithToolsStream(messages, tools) {
6269
6358
  }
6270
6359
  };
6271
6360
  }
6272
- var LLM_TIMEOUT_DEFAULT_MS, LLM_TIMEOUT_MIN_MS, LLM_TIMEOUT_MAX_MS, MAX_LLM_RESPONSE_BYTES, LLM_CALL_TIMEOUT_MS, PROVIDER_DEFAULTS, currentConfig;
6361
+ var LLM_TIMEOUT_DEFAULT_MS, LLM_TIMEOUT_MIN_MS, LLM_TIMEOUT_MAX_MS, MAX_LLM_RESPONSE_BYTES, OPENAI_API_VERSION_SEGMENT, LLM_CALL_TIMEOUT_MS, PROVIDER_DEFAULTS, currentConfig;
6273
6362
  var init_provider2 = __esm({
6274
6363
  "src/llm/provider.ts"() {
6275
6364
  "use strict";
@@ -6279,6 +6368,7 @@ var init_provider2 = __esm({
6279
6368
  LLM_TIMEOUT_MIN_MS = 1e3;
6280
6369
  LLM_TIMEOUT_MAX_MS = 3e5;
6281
6370
  MAX_LLM_RESPONSE_BYTES = 2 * 1024 * 1024;
6371
+ OPENAI_API_VERSION_SEGMENT = /^v\d+(?:[a-z][a-z0-9-]*)?$/i;
6282
6372
  LLM_CALL_TIMEOUT_MS = parseLLMTimeoutMs(process.env.MEMORIX_LLM_TIMEOUT_MS);
6283
6373
  PROVIDER_DEFAULTS = {
6284
6374
  openai: { baseUrl: "https://api.openai.com/v1", model: "gpt-4.1-nano" },
@@ -6338,6 +6428,48 @@ Rules:
6338
6428
  }
6339
6429
  });
6340
6430
 
6431
+ // src/rerank/policy.ts
6432
+ function parseRerankTimeoutMs(raw) {
6433
+ const value = raw?.trim();
6434
+ if (!value) return DEFAULT_RERANK_TIMEOUT_MS;
6435
+ const parsed = Number(value);
6436
+ if (!Number.isInteger(parsed) || Number.isNaN(parsed)) {
6437
+ console.warn(
6438
+ `[memorix] MEMORIX_RERANK_TIMEOUT_MS="${raw}" is invalid (must be a positive integer between ${RERANK_TIMEOUT_MIN_MS}-${RERANK_TIMEOUT_MAX_MS}ms). Using default ${DEFAULT_RERANK_TIMEOUT_MS}ms.`
6439
+ );
6440
+ return DEFAULT_RERANK_TIMEOUT_MS;
6441
+ }
6442
+ if (parsed < RERANK_TIMEOUT_MIN_MS) return RERANK_TIMEOUT_MIN_MS;
6443
+ if (parsed > RERANK_TIMEOUT_MAX_MS) return RERANK_TIMEOUT_MAX_MS;
6444
+ return parsed;
6445
+ }
6446
+ function resolveRerankTimeoutMs(explicitTimeoutMs) {
6447
+ if (typeof explicitTimeoutMs === "number" && Number.isFinite(explicitTimeoutMs) && explicitTimeoutMs > 0) {
6448
+ return explicitTimeoutMs;
6449
+ }
6450
+ return parseRerankTimeoutMs(process.env.MEMORIX_RERANK_TIMEOUT_MS);
6451
+ }
6452
+ function shouldAttemptHttpRerank(input) {
6453
+ return input.quality === "thorough" && input.hasQuery && input.candidateCount >= MIN_RERANK_CANDIDATES;
6454
+ }
6455
+ function shouldAttemptLlmRerank(input) {
6456
+ if (input.httpRerankConfigured) return false;
6457
+ if (input.quality !== "thorough" || input.tier !== "heavy" || !input.hasQuery) return false;
6458
+ if (input.candidateCount < MIN_RERANK_CANDIDATES) return false;
6459
+ return input.topScore > 0 && input.secondScore / input.topScore > 0.7;
6460
+ }
6461
+ var DEFAULT_RERANK_TIMEOUT_MS, RERANK_TIMEOUT_MIN_MS, RERANK_TIMEOUT_MAX_MS, MIN_RERANK_CANDIDATES;
6462
+ var init_policy = __esm({
6463
+ "src/rerank/policy.ts"() {
6464
+ "use strict";
6465
+ init_esm_shims();
6466
+ DEFAULT_RERANK_TIMEOUT_MS = 3e4;
6467
+ RERANK_TIMEOUT_MIN_MS = 1e3;
6468
+ RERANK_TIMEOUT_MAX_MS = 3e5;
6469
+ MIN_RERANK_CANDIDATES = 3;
6470
+ }
6471
+ });
6472
+
6341
6473
  // src/timeout.ts
6342
6474
  function withTimeout(promise, ms, label) {
6343
6475
  return new Promise((resolve2, reject) => {
@@ -6623,6 +6755,148 @@ var init_aliases = __esm({
6623
6755
  }
6624
6756
  });
6625
6757
 
6758
+ // src/rerank/http-provider.ts
6759
+ function buildRerankUrl(baseUrl) {
6760
+ const trimmed = baseUrl.trim().replace(/\/+$/, "");
6761
+ if (trimmed.endsWith("/rerank")) return trimmed;
6762
+ return `${trimmed}/rerank`;
6763
+ }
6764
+ function parseRerankResponse(payload) {
6765
+ const rows = extractRows(payload);
6766
+ if (rows.length === 0) {
6767
+ throw new Error("Neural rerank response contained no ranked results");
6768
+ }
6769
+ return rows.sort((a, b) => b.score - a.score);
6770
+ }
6771
+ function extractRows(payload) {
6772
+ if (Array.isArray(payload)) {
6773
+ return payload.map(rowFromUnknown).filter((row) => row !== null);
6774
+ }
6775
+ if (payload && typeof payload === "object" && "results" in payload) {
6776
+ const results = payload.results;
6777
+ if (Array.isArray(results)) {
6778
+ return results.map(rowFromUnknown).filter((row) => row !== null);
6779
+ }
6780
+ }
6781
+ throw new Error("Neural rerank response was not Cohere or TEI shaped");
6782
+ }
6783
+ function rowFromUnknown(value) {
6784
+ if (!value || typeof value !== "object") return null;
6785
+ const row = value;
6786
+ if (typeof row.index !== "number" || !Number.isFinite(row.index)) return null;
6787
+ const score = typeof row.relevance_score === "number" ? row.relevance_score : typeof row.score === "number" ? row.score : null;
6788
+ if (score === null || !Number.isFinite(score)) return null;
6789
+ return { index: row.index, score };
6790
+ }
6791
+ function buildRerankBody(query, documents, model) {
6792
+ const body = {
6793
+ query,
6794
+ documents,
6795
+ top_n: documents.length,
6796
+ return_documents: false
6797
+ };
6798
+ const trimmed = model?.trim();
6799
+ if (trimmed) body.model = trimmed;
6800
+ return body;
6801
+ }
6802
+ async function rerankViaHttp(query, documents, config) {
6803
+ if (documents.length === 0) return [];
6804
+ const url = buildRerankUrl(config.baseUrl);
6805
+ const headers = {
6806
+ "content-type": "application/json"
6807
+ };
6808
+ if (config.apiKey) {
6809
+ headers.authorization = `Bearer ${config.apiKey}`;
6810
+ }
6811
+ const controller = new AbortController();
6812
+ const timeoutMs = resolveRerankTimeoutMs(config.timeoutMs);
6813
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
6814
+ try {
6815
+ const fetchImpl = config.fetchImpl ?? fetch;
6816
+ const response = await fetchImpl(url, {
6817
+ method: "POST",
6818
+ headers,
6819
+ signal: controller.signal,
6820
+ body: JSON.stringify(buildRerankBody(query, documents, config.model))
6821
+ });
6822
+ if (!response.ok) {
6823
+ throw new Error(`Neural rerank HTTP ${response.status} from ${url}`);
6824
+ }
6825
+ return parseRerankResponse(await response.json());
6826
+ } finally {
6827
+ clearTimeout(timer);
6828
+ }
6829
+ }
6830
+ var init_http_provider = __esm({
6831
+ "src/rerank/http-provider.ts"() {
6832
+ "use strict";
6833
+ init_esm_shims();
6834
+ init_policy();
6835
+ }
6836
+ });
6837
+
6838
+ // src/rerank/index.ts
6839
+ var rerank_exports = {};
6840
+ __export(rerank_exports, {
6841
+ DEFAULT_RERANK_TIMEOUT_MS: () => DEFAULT_RERANK_TIMEOUT_MS,
6842
+ MIN_RERANK_CANDIDATES: () => MIN_RERANK_CANDIDATES,
6843
+ isNeuralRerankEnabled: () => isNeuralRerankEnabled,
6844
+ neuralRerankCandidates: () => neuralRerankCandidates,
6845
+ parseRerankTimeoutMs: () => parseRerankTimeoutMs,
6846
+ resolveRerankTimeoutMs: () => resolveRerankTimeoutMs,
6847
+ shouldAttemptHttpRerank: () => shouldAttemptHttpRerank,
6848
+ shouldAttemptLlmRerank: () => shouldAttemptLlmRerank
6849
+ });
6850
+ function isNeuralRerankEnabled(options = {}) {
6851
+ const lane = getResolvedRerankLane(options);
6852
+ return lane.provider === "http" && Boolean(lane.baseUrl);
6853
+ }
6854
+ async function neuralRerankCandidates(query, candidates, options = {}) {
6855
+ if (!isNeuralRerankEnabled(options) || candidates.length === 0) return null;
6856
+ const lane = getResolvedRerankLane(options);
6857
+ if (!lane.canSendRequest || !lane.baseUrl) return null;
6858
+ const toRerank = candidates.slice(0, MAX_RERANK);
6859
+ const rest = candidates.slice(MAX_RERANK);
6860
+ const documents = toRerank.map((candidate) => candidateText(candidate));
6861
+ const ranked = await rerankViaHttp(query, documents, {
6862
+ provider: "http",
6863
+ model: lane.model,
6864
+ baseUrl: lane.baseUrl,
6865
+ apiKey: lane.apiKey,
6866
+ timeoutMs: resolveRerankTimeoutMs()
6867
+ });
6868
+ const seen = /* @__PURE__ */ new Set();
6869
+ const reranked = [];
6870
+ for (const row of ranked) {
6871
+ const candidate = toRerank[row.index];
6872
+ if (!candidate || seen.has(row.index)) continue;
6873
+ reranked.push(candidate);
6874
+ seen.add(row.index);
6875
+ }
6876
+ for (const [index, candidate] of toRerank.entries()) {
6877
+ if (!seen.has(index)) reranked.push(candidate);
6878
+ }
6879
+ reranked.push(...rest);
6880
+ return reranked;
6881
+ }
6882
+ function candidateText(candidate) {
6883
+ const narrative = candidate.narrative?.trim();
6884
+ if (narrative) return `${candidate.title} \u2014 ${narrative.slice(0, 200)}`;
6885
+ return candidate.title;
6886
+ }
6887
+ var MAX_RERANK;
6888
+ var init_rerank = __esm({
6889
+ "src/rerank/index.ts"() {
6890
+ "use strict";
6891
+ init_esm_shims();
6892
+ init_resolved_config();
6893
+ init_http_provider();
6894
+ init_policy();
6895
+ init_policy();
6896
+ MAX_RERANK = 10;
6897
+ }
6898
+ });
6899
+
6626
6900
  // src/llm/quality.ts
6627
6901
  var quality_exports = {};
6628
6902
  __export(quality_exports, {
@@ -6662,9 +6936,9 @@ async function rerankResults(query, candidates) {
6662
6936
  if (!isLLMEnabled() || candidates.length <= 2) {
6663
6937
  return { reranked: candidates, usedLLM: false };
6664
6938
  }
6665
- const MAX_RERANK = 10;
6666
- const toRerank = candidates.slice(0, MAX_RERANK);
6667
- const rest = candidates.slice(MAX_RERANK);
6939
+ const MAX_RERANK2 = 10;
6940
+ const toRerank = candidates.slice(0, MAX_RERANK2);
6941
+ const rest = candidates.slice(MAX_RERANK2);
6668
6942
  try {
6669
6943
  const candidateList = toRerank.map(
6670
6944
  (c) => `[ID: ${c.id}] (${c.type}) ${c.title}${c.narrative ? ` \u2014 ${c.narrative.substring(0, 100)}` : ""}`
@@ -6888,7 +7162,7 @@ __export(orama_store_exports, {
6888
7162
  searchObservations: () => searchObservations,
6889
7163
  updateObservationMetadata: () => updateObservationMetadata
6890
7164
  });
6891
- import { create, insert as insert2, search, remove as remove2, update, count, getByID } from "@orama/orama";
7165
+ import { create, insert as insert2, insertMultiple, search, remove as remove2, update, count, getByID } from "@orama/orama";
6892
7166
  function getLastSearchMode(projectId) {
6893
7167
  return lastSearchModeByProject.get(projectId ?? SEARCH_MODE_DEFAULT_KEY) ?? "fulltext";
6894
7168
  }
@@ -7100,7 +7374,7 @@ async function hydrateIndex(observations2, options = {}) {
7100
7374
  }
7101
7375
  const deferCachedVectors = options.deferCachedVectors === true && Boolean(indexEmbeddingProvider?.getCachedEmbeddings);
7102
7376
  const cachedVectors = deferCachedVectors ? candidates.map(() => null) : await getCachedEmbeddings(candidates.map(({ observation }) => observationEmbeddingText(observation)));
7103
- let inserted = 0;
7377
+ const documents = [];
7104
7378
  for (let index = 0; index < candidates.length; index++) {
7105
7379
  const { observation: obs, id } = candidates[index];
7106
7380
  try {
@@ -7134,18 +7408,35 @@ async function hydrateIndex(observations2, options = {}) {
7134
7408
  knowledgeLayer: resolveKnowledgeLayer("observation", obs.sourceDetail, obs.source),
7135
7409
  ...compatibleVector ? { embedding: compatibleVector } : {}
7136
7410
  };
7137
- await insert2(database, doc);
7138
- rememberObservationDoc(doc);
7139
- inserted++;
7140
- if (inserted % 50 === 0) {
7141
- await new Promise((resolve2) => setImmediate(resolve2));
7142
- }
7143
- if (inserted % 200 === 0) {
7144
- await new Promise((resolve2) => setTimeout(resolve2, 0));
7145
- }
7411
+ documents.push(doc);
7146
7412
  } catch {
7147
7413
  }
7148
7414
  }
7415
+ let inserted = 0;
7416
+ const hydrationBatchSize = 200;
7417
+ for (let start = 0; start < documents.length; start += hydrationBatchSize) {
7418
+ const batch = documents.slice(start, start + hydrationBatchSize);
7419
+ try {
7420
+ await insertMultiple(database, batch, batch.length);
7421
+ for (const doc of batch) rememberObservationDoc(doc);
7422
+ inserted += batch.length;
7423
+ } catch {
7424
+ for (const doc of batch) {
7425
+ if (getByID(database, doc.id)) {
7426
+ rememberObservationDoc(doc);
7427
+ inserted++;
7428
+ continue;
7429
+ }
7430
+ try {
7431
+ await insert2(database, doc);
7432
+ rememberObservationDoc(doc);
7433
+ inserted++;
7434
+ } catch {
7435
+ }
7436
+ }
7437
+ }
7438
+ await new Promise((resolve2) => setImmediate(resolve2));
7439
+ }
7149
7440
  deferredCachedVectorHydration = options.skipCachedVectors ? null : deferCachedVectors ? attachCachedVectors(database, candidates).catch(() => {
7150
7441
  }) : null;
7151
7442
  return inserted;
@@ -7540,46 +7831,78 @@ async function searchObservations(options) {
7540
7831
  }
7541
7832
  }
7542
7833
  }
7543
- const shouldRerank = quality === "thorough" && tier === "heavy" && hasQuery && intermediate.length > 2 && (() => {
7544
- const top = intermediate[0]?.score ?? 0;
7545
- const second = intermediate[1]?.score ?? 0;
7546
- return top > 0 && second / top > 0.7;
7547
- })();
7548
- if (shouldRerank) {
7549
- try {
7550
- const { rerankResults: rerankResults2 } = await Promise.resolve().then(() => (init_quality(), quality_exports));
7551
- const narrativeMap = /* @__PURE__ */ new Map();
7552
- for (const hit of results.hits) {
7553
- const doc = hit.document;
7554
- narrativeMap.set(makeEntryKey(doc.projectId, doc.observationId), doc.narrative);
7555
- }
7556
- const RERANK_TOP_K = 5;
7557
- const toRerank = intermediate.slice(0, RERANK_TOP_K);
7558
- const candidates = toRerank.map((e2, index) => ({
7559
- id: `r${index + 1}`,
7560
- title: e2.title,
7561
- type: e2.type,
7562
- score: e2.score,
7563
- narrative: narrativeMap.get(makeEntryKey(e2.projectId, e2.id))
7564
- }));
7565
- const _parsedRerank = parseInt(process.env.MEMORIX_RERANK_TIMEOUT_MS || "", 10);
7566
- const RERANK_TIMEOUT_MS = Number.isFinite(_parsedRerank) && _parsedRerank > 0 ? _parsedRerank : 5e3;
7567
- const { reranked, usedLLM } = await withTimeout(
7568
- rerankResults2(originalQuery, candidates),
7569
- RERANK_TIMEOUT_MS,
7570
- "LLM rerank"
7571
- );
7572
- mark(`rerank(usedLLM=${usedLLM})`);
7573
- if (usedLLM) {
7574
- lastSearchModeByProject.set(modeKey, (lastSearchModeByProject.get(modeKey) ?? "fulltext") + " + LLM rerank");
7575
- const candidateMap = new Map(candidates.map((candidate, index) => [candidate.id, toRerank[index]]));
7576
- const rerankedTop = reranked.map((r2) => candidateMap.get(r2.id)).filter((e2) => e2 != null);
7577
- if (rerankedTop.length > 0) {
7578
- intermediate = [...rerankedTop, ...intermediate.slice(RERANK_TOP_K)];
7834
+ const httpGate = shouldAttemptHttpRerank({
7835
+ quality,
7836
+ hasQuery: Boolean(hasQuery),
7837
+ candidateCount: intermediate.length
7838
+ });
7839
+ const llmGateWithoutHttp = shouldAttemptLlmRerank({
7840
+ quality,
7841
+ tier,
7842
+ hasQuery: Boolean(hasQuery),
7843
+ candidateCount: intermediate.length,
7844
+ topScore: intermediate[0]?.score ?? 0,
7845
+ secondScore: intermediate[1]?.score ?? 0,
7846
+ httpRerankConfigured: false
7847
+ });
7848
+ if (httpGate || llmGateWithoutHttp) {
7849
+ const narrativeMap = /* @__PURE__ */ new Map();
7850
+ for (const hit of results.hits) {
7851
+ const doc = hit.document;
7852
+ narrativeMap.set(makeEntryKey(doc.projectId, doc.observationId), doc.narrative);
7853
+ }
7854
+ const RERANK_TOP_K = 5;
7855
+ const toRerank = intermediate.slice(0, RERANK_TOP_K);
7856
+ const candidates = toRerank.map((e2, index) => ({
7857
+ id: `r${index + 1}`,
7858
+ title: e2.title,
7859
+ type: e2.type,
7860
+ score: e2.score,
7861
+ narrative: narrativeMap.get(makeEntryKey(e2.projectId, e2.id))
7862
+ }));
7863
+ const RERANK_TIMEOUT_MS = parseRerankTimeoutMs(process.env.MEMORIX_RERANK_TIMEOUT_MS);
7864
+ const candidateMap = new Map(candidates.map((candidate, index) => [candidate.id, toRerank[index]]));
7865
+ const { isNeuralRerankEnabled: isNeuralRerankEnabled2, neuralRerankCandidates: neuralRerankCandidates2 } = await Promise.resolve().then(() => (init_rerank(), rerank_exports));
7866
+ const httpConfigured = isNeuralRerankEnabled2();
7867
+ if (httpConfigured && httpGate) {
7868
+ try {
7869
+ const neural = await withTimeout(
7870
+ neuralRerankCandidates2(originalQuery, candidates),
7871
+ RERANK_TIMEOUT_MS,
7872
+ "neural rerank"
7873
+ );
7874
+ if (neural && neural.length > 0) {
7875
+ const rerankedTop = neural.map((row) => candidateMap.get(row.id)).filter((entry) => entry != null);
7876
+ if (rerankedTop.length > 0) {
7877
+ intermediate = [...rerankedTop, ...intermediate.slice(RERANK_TOP_K)];
7878
+ lastSearchModeByProject.set(modeKey, (lastSearchModeByProject.get(modeKey) ?? "fulltext") + " + neural rerank");
7879
+ mark("rerank(usedNeural=true)");
7880
+ }
7579
7881
  }
7882
+ } catch {
7883
+ console.error("[memorix] neural rerank failed or timed out; keeping original order");
7580
7884
  }
7581
- } catch (error) {
7582
- console.error("[memorix] LLM rerank failed or timed out, using original order");
7885
+ } else if (!httpConfigured && llmGateWithoutHttp) {
7886
+ try {
7887
+ const { rerankResults: rerankResults2 } = await Promise.resolve().then(() => (init_quality(), quality_exports));
7888
+ const { reranked, usedLLM } = await withTimeout(
7889
+ rerankResults2(originalQuery, candidates),
7890
+ RERANK_TIMEOUT_MS,
7891
+ "LLM rerank"
7892
+ );
7893
+ mark(`rerank(usedLLM=${usedLLM})`);
7894
+ if (usedLLM) {
7895
+ lastSearchModeByProject.set(modeKey, (lastSearchModeByProject.get(modeKey) ?? "fulltext") + " + LLM rerank");
7896
+ const rerankedTop = reranked.map((row) => candidateMap.get(row.id)).filter((entry) => entry != null);
7897
+ if (rerankedTop.length > 0) {
7898
+ intermediate = [...rerankedTop, ...intermediate.slice(RERANK_TOP_K)];
7899
+ }
7900
+ }
7901
+ } catch {
7902
+ console.error("[memorix] LLM rerank failed or timed out, using original order");
7903
+ }
7904
+ } else {
7905
+ mark(`rerank(skipped,tier=${tier})`);
7583
7906
  }
7584
7907
  } else {
7585
7908
  mark(`rerank(skipped,tier=${tier})`);
@@ -7760,6 +8083,7 @@ var init_orama_store = __esm({
7760
8083
  init_project_affinity();
7761
8084
  init_intent_detector();
7762
8085
  init_query_expansion();
8086
+ init_policy();
7763
8087
  init_timeout();
7764
8088
  db = null;
7765
8089
  dbInitPromise = null;
@@ -165200,8 +165524,7 @@ async function callVisionLLM(systemPrompt, imageBase64, mimeType) {
165200
165524
  if (!apiKey) {
165201
165525
  throw new Error("No LLM API key configured for image analysis.");
165202
165526
  }
165203
- let baseUrl = getLLMBaseUrl("https://api.openai.com/v1").replace(/\/+$/, "");
165204
- if (!baseUrl.endsWith("/v1")) baseUrl += "/v1";
165527
+ const baseUrl = normalizeOpenAICompatibleBaseUrl(getLLMBaseUrl("https://api.openai.com/v1"));
165205
165528
  const model = getLLMModel("gpt-4o");
165206
165529
  const response = await fetch(`${baseUrl}/chat/completions`, {
165207
165530
  method: "POST",
@@ -167285,7 +167608,7 @@ The path should point to a directory containing a .git folder.`
167285
167608
  };
167286
167609
  const server = existingServer ?? new McpServer({
167287
167610
  name: "memorix",
167288
- version: true ? "1.7.1" : "1.0.1"
167611
+ version: true ? "1.7.3" : "1.0.1"
167289
167612
  });
167290
167613
  const originalRegisterTool = server.registerTool.bind(server);
167291
167614
  server.registerTool = ((name, ...args) => {
@@ -171725,7 +172048,7 @@ var init_serve_shared = __esm({
171725
172048
 
171726
172049
  // src/cli/version.ts
171727
172050
  function getCliVersion() {
171728
- return true ? "1.7.1" : pkg.version;
172051
+ return true ? "1.7.3" : pkg.version;
171729
172052
  }
171730
172053
  var init_version3 = __esm({
171731
172054
  "src/cli/version.ts"() {