@equationalapplications/core-llm-wiki 5.5.0 → 6.0.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.
package/dist/testing.js CHANGED
@@ -387,7 +387,12 @@ function extractParsePosition(err) {
387
387
  return match ? Number(match[1]) : null;
388
388
  }
389
389
  function safeErrorToString(e) {
390
- if (e instanceof Error) {
390
+ let isErrorLike = false;
391
+ try {
392
+ isErrorLike = e instanceof Error;
393
+ } catch {
394
+ }
395
+ if (isErrorLike) {
391
396
  const msg = readErrorField(e, "message");
392
397
  if (typeof msg === "string" && msg.length > 0) return msg;
393
398
  const name = readErrorField(e, "name");
@@ -412,16 +417,54 @@ function readErrorField(e, key) {
412
417
  }
413
418
  }
414
419
  function sanitizeRankerError(err, sanitizeRankerErrors) {
420
+ let isErrorLike = false;
421
+ try {
422
+ isErrorLike = err instanceof Error;
423
+ } catch {
424
+ }
415
425
  if (sanitizeRankerErrors === false) {
416
- return err instanceof Error ? err : new Error(String(err));
426
+ return isErrorLike ? err : new Error(safeErrorToString(err));
427
+ }
428
+ let errLike = null;
429
+ if (isErrorLike) errLike = err;
430
+ let typeName;
431
+ try {
432
+ if (errLike) {
433
+ const rawName = errLike.constructor?.name;
434
+ typeName = typeof rawName === "string" ? rawName : "Error";
435
+ } else {
436
+ typeName = typeof err;
437
+ }
438
+ } catch {
439
+ typeName = isErrorLike ? "Error" : typeof err;
440
+ }
441
+ let innerCause;
442
+ if (errLike) {
443
+ let cause;
444
+ try {
445
+ cause = errLike.cause;
446
+ } catch {
447
+ cause = void 0;
448
+ }
449
+ if (cause !== void 0) {
450
+ let causeName;
451
+ try {
452
+ const rawCauseName = cause?.constructor?.name;
453
+ causeName = typeof rawCauseName === "string" ? rawCauseName : typeof cause;
454
+ } catch {
455
+ causeName = typeof cause;
456
+ }
457
+ innerCause = new Error(`Caused by: ${causeName}`);
458
+ }
417
459
  }
418
- const typeName = err instanceof Error ? err.constructor?.name ?? "Error" : typeof err;
419
- const innerCause = err instanceof Error && err.cause !== void 0 ? new Error(`Caused by: ${err.cause?.constructor?.name ?? typeof err.cause}`) : void 0;
420
460
  const sanitized = new Error(
421
461
  `VectorRanker ${typeName} (message scrubbed for security)`,
422
462
  innerCause ? { cause: innerCause } : void 0
423
463
  );
424
- sanitized.name = typeName;
464
+ try {
465
+ sanitized.name = typeName;
466
+ } catch {
467
+ }
425
468
  return sanitized;
426
469
  }
427
470
  function safeSlice(value, start, end) {
@@ -1227,6 +1270,12 @@ Return ONLY a valid JSON object matching this schema:
1227
1270
  If no manifest type fits a fact, omit that fact from "classifications" entirely \u2014 do not guess.
1228
1271
  When echoing an existing fact's title verbatim into "target_title", preserve every JSON escape sequence (\\", \\n, \\\\, \\/) exactly as it appeared in the input body \u2014 do not strip backslashes, do not add unescaped quotes. Do not return markdown, just raw JSON.`;
1229
1272
 
1273
+ // src/utils/healConstants.ts
1274
+ var HEAL_MAX_ANCHORS = 50;
1275
+ var HEAL_ANCHORS_PER_CANDIDATE = 4;
1276
+ var HEAL_MAX_FACT_BODY_CHARS_L3 = 4e3;
1277
+ var HEAL_MAX_TASKS = 50;
1278
+
1230
1279
  // src/services/PromptService.ts
1231
1280
  var PromptService = class {
1232
1281
  constructor(globalOverrides) {
@@ -1294,25 +1343,67 @@ Current Facts:
1294
1343
  ${JSON.stringify(currentFacts, null, 2)}`
1295
1344
  };
1296
1345
  }
1297
- buildHealPrompt(healCandidates, documentAnchors, allTasks, recentEvents, runtimeOverride) {
1346
+ /**
1347
+ * Heal-prompt level interpretation for the `attemptLevel` ladder.
1348
+ *
1349
+ * Caller contract: `documentAnchors` may be a slice sized for `batch.length`
1350
+ * or a larger set (e.g. a cache hit from `_selectHealAnchors`). This function
1351
+ * applies the prompt-side anchor cap `min(HEAL_MAX_ANCHORS=50, batch.length
1352
+ * * HEAL_ANCHORS_PER_CANDIDATE=4)` so the rendered prompt is bounded
1353
+ * regardless of caller input. `HEAL_MAX_ANCHORS` and
1354
+ * `HEAL_ANCHORS_PER_CANDIDATE` live here too — keeping the formula
1355
+ * co-located with its application avoids a "MaintenanceService policy"
1356
+ * import cycle (`PromptService` is constructed before `MaintenanceService`
1357
+ * exists) and makes the cap testable without a `MaintenanceService`
1358
+ * instance. Task 3 exports the same two constants from `MaintenanceService`
1359
+ * for caller-side overfetch sizing; the values must match.
1360
+ *
1361
+ * Level semantics:
1362
+ * - L0: allTasks + recentEvents + full candidate bodies; anchors re-capped
1363
+ * - L1: drop allTasks; recentEvents present; candidate bodies full
1364
+ * - L2: drop allTasks and recentEvents; candidate bodies full
1365
+ * - L3: drop allTasks and recentEvents; truncate each candidate body to
1366
+ * `bodyTruncationChars` and emit a `degraded` record per truncated fact
1367
+ */
1368
+ buildHealPrompt(healCandidates, documentAnchors, allTasks, recentEvents, runtimeOverride, attemptLevel, bodyTruncationChars = HEAL_MAX_FACT_BODY_CHARS_L3) {
1369
+ const effectiveTasks = attemptLevel >= 1 ? [] : allTasks;
1370
+ const effectiveEvents = attemptLevel >= 2 ? [] : recentEvents;
1371
+ const maxAnchors = Math.max(1, Math.min(HEAL_MAX_ANCHORS, healCandidates.length * HEAL_ANCHORS_PER_CANDIDATE));
1372
+ const effectiveAnchors = documentAnchors.slice(0, maxAnchors);
1373
+ const { shapedCandidates, degraded } = applyBodyTruncation(
1374
+ healCandidates,
1375
+ attemptLevel,
1376
+ bodyTruncationChars
1377
+ );
1298
1378
  const template = runtimeOverride ?? this.globalOverrides?.healSystemPrompt ?? HEAL_SYSTEM_PROMPT;
1299
1379
  if (/\{\{\s*healCandidates\s*\}\}/.test(template) || /\{\{\s*documentAnchors\s*\}\}/.test(template) || /\{\{\s*allTasks\s*\}\}/.test(template) || /\{\{\s*recentEvents\s*\}\}/.test(template)) {
1300
1380
  return {
1301
- systemPrompt: this.hydrate(template, { healCandidates, documentAnchors, allTasks, recentEvents }),
1302
- userPrompt: "Please heal the memory graph."
1381
+ prompts: {
1382
+ systemPrompt: this.hydrate(template, {
1383
+ healCandidates: shapedCandidates,
1384
+ documentAnchors: effectiveAnchors,
1385
+ allTasks: effectiveTasks,
1386
+ recentEvents: effectiveEvents
1387
+ }),
1388
+ userPrompt: "Please heal the memory graph."
1389
+ },
1390
+ degraded
1303
1391
  };
1304
1392
  }
1305
1393
  return {
1306
- systemPrompt: template,
1307
- userPrompt: `Heal Candidates:
1308
- ${JSON.stringify(healCandidates, null, 2)}
1394
+ prompts: {
1395
+ systemPrompt: template,
1396
+ userPrompt: `Heal Candidates:
1397
+ ${JSON.stringify(shapedCandidates, null, 2)}
1309
1398
  Document Anchors (DO NOT MODIFY OR DELETE):
1310
- ${JSON.stringify(documentAnchors, null, 2)}
1399
+ ${JSON.stringify(effectiveAnchors, null, 2)}
1311
1400
  All Tasks:
1312
- ${JSON.stringify(allTasks, null, 2)}
1401
+ ${JSON.stringify(effectiveTasks, null, 2)}
1313
1402
  Recent Events:
1314
- ${JSON.stringify(recentEvents, null, 2)}
1403
+ ${JSON.stringify(effectiveEvents, null, 2)}
1315
1404
  The following document anchors are provided for contradiction detection only. Do not include them in \`downgraded\`, \`deleted\`, or \`newFacts\`.`
1405
+ },
1406
+ degraded
1316
1407
  };
1317
1408
  }
1318
1409
  buildOntologyBackfillPrompt(facts, runtimeOverride, ontologyContext) {
@@ -1332,6 +1423,36 @@ ${JSON.stringify(facts, null, 2)}`
1332
1423
  };
1333
1424
  }
1334
1425
  };
1426
+ function applyBodyTruncation(candidates, attemptLevel, bodyTruncationChars) {
1427
+ if (attemptLevel < 3) {
1428
+ return { shapedCandidates: candidates, degraded: [] };
1429
+ }
1430
+ const shapedCandidates = [];
1431
+ const degraded = [];
1432
+ for (const c of candidates) {
1433
+ if (typeof c !== "object" || c === null) {
1434
+ shapedCandidates.push(c);
1435
+ continue;
1436
+ }
1437
+ const fact = c;
1438
+ const body = typeof fact.body === "string" ? fact.body : "";
1439
+ if (body.length <= bodyTruncationChars) {
1440
+ shapedCandidates.push(c);
1441
+ continue;
1442
+ }
1443
+ if (typeof fact.id !== "string") {
1444
+ shapedCandidates.push(c);
1445
+ continue;
1446
+ }
1447
+ const originalBodyChars = body.length;
1448
+ const prefix = safeSlice(body, 0, bodyTruncationChars);
1449
+ const truncatedBodyChars = prefix.length;
1450
+ const truncated = `${prefix}\u2026[truncated at ${truncatedBodyChars} chars, original was ${originalBodyChars}]`;
1451
+ shapedCandidates.push({ ...fact, body: truncated });
1452
+ degraded.push({ id: fact.id, originalBodyChars, truncatedBodyChars });
1453
+ }
1454
+ return { shapedCandidates, degraded };
1455
+ }
1335
1456
 
1336
1457
  // src/utils/chunkingDefaults.ts
1337
1458
  var DEFAULT_MAX_CHUNK_LENGTH = 12e3;
@@ -1842,11 +1963,24 @@ var TRUNCATION_PATTERNS = [
1842
1963
  /finish[_ ]?reason/i
1843
1964
  ];
1844
1965
  var EXCEEDS_LIMIT_PATTERN = /exceed[a-z]*[^.]{0,40}\b(model|context)?[ _-]?limit/i;
1966
+ function getErrorMessage(err) {
1967
+ try {
1968
+ return err instanceof Error ? err.message : String(err ?? "");
1969
+ } catch {
1970
+ return void 0;
1971
+ }
1972
+ }
1845
1973
  function isTruncationError(err) {
1846
- const message = err instanceof Error ? err.message : String(err ?? "");
1974
+ const message = getErrorMessage(err);
1975
+ if (message === void 0) return false;
1847
1976
  if (EXCEEDS_LIMIT_PATTERN.test(message)) return false;
1848
1977
  return TRUNCATION_PATTERNS.some((pattern) => pattern.test(message));
1849
1978
  }
1979
+ function isConfigError(err) {
1980
+ const message = getErrorMessage(err);
1981
+ if (message === void 0) return false;
1982
+ return EXCEEDS_LIMIT_PATTERN.test(message);
1983
+ }
1850
1984
  function initialBatchSize(maxOutputTokens) {
1851
1985
  if (!maxOutputTokens || !Number.isFinite(maxOutputTokens) || maxOutputTokens <= 0) {
1852
1986
  return DEFAULT_BATCH_SIZE;
@@ -1888,14 +2022,18 @@ async function runBatched(args) {
1888
2022
  const single = candidate.slice(0, 1);
1889
2023
  return { batch: single, prompts: await buildPrompt(single) };
1890
2024
  };
1891
- const onFailure = async (batch, err) => {
1892
- if (batch.length <= 1) {
1893
- if (batch.length === 1) {
1894
- skipped.push(batch[0]);
2025
+ const onFailure = async (batch, err, attemptLevel, fromCall) => {
2026
+ if (batch.length === 1) {
2027
+ if (fromCall && isTruncationError(err) && attemptLevel < 3) {
2028
+ await attempt(batch, void 0, attemptLevel + 1);
2029
+ } else {
2030
+ const reason = fromCall && !isTruncationError(err) && !isConfigError(err) ? "call_error" : "non_convergent";
2031
+ skipped.push({ item: batch[0], reason });
1895
2032
  onSkip?.(batch[0], err);
1896
2033
  }
1897
2034
  return;
1898
2035
  }
2036
+ if (fromCall && !isTruncationError(err)) throw err;
1899
2037
  const mid = Math.ceil(batch.length / 2);
1900
2038
  if (mid < batchSize) batchSize = mid;
1901
2039
  let i = 0;
@@ -1906,23 +2044,22 @@ async function runBatched(args) {
1906
2044
  i += trimmed.batch.length;
1907
2045
  }
1908
2046
  };
1909
- const attempt = async (batch, prebuilt) => {
2047
+ const attempt = async (batch, prebuilt, attemptLevel = 0) => {
1910
2048
  if (batch.length === 0) return;
1911
- const prompts = prebuilt ?? await buildPrompt(batch);
2049
+ const prompts = prebuilt && attemptLevel === 0 ? prebuilt : await buildPrompt(batch, attemptLevel);
1912
2050
  batches++;
1913
2051
  let responseText;
1914
2052
  try {
1915
2053
  responseText = await call(prompts);
1916
2054
  } catch (err) {
1917
- if (!isTruncationError(err)) throw err;
1918
- await onFailure(batch, err);
2055
+ await onFailure(batch, err, attemptLevel, true);
1919
2056
  return;
1920
2057
  }
1921
2058
  let result;
1922
2059
  try {
1923
2060
  result = parse(responseText, batch);
1924
2061
  } catch (err) {
1925
- await onFailure(batch, err);
2062
+ await onFailure(batch, err, attemptLevel, false);
1926
2063
  return;
1927
2064
  }
1928
2065
  results.push(result);
@@ -1931,7 +2068,7 @@ async function runBatched(args) {
1931
2068
  while (index < items.length) {
1932
2069
  const { batch, prompts } = await trim(items.slice(index, index + batchSize));
1933
2070
  index += batch.length;
1934
- await attempt(batch, prompts);
2071
+ await attempt(batch, prompts, 0);
1935
2072
  }
1936
2073
  return { results, skipped, batches };
1937
2074
  }
@@ -1942,7 +2079,6 @@ var MIN_TOKENS_TO_QUALIFY = 3;
1942
2079
  var ONTOLOGY_BACKFILL_BATCH_SIZE = 25;
1943
2080
  var ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS = 4e4;
1944
2081
  var ONTOLOGY_BACKFILL_RECHECK_MS = 7 * 24 * 60 * 60 * 1e3;
1945
- var HEAL_MAX_ANCHORS = 50;
1946
2082
  var HEAL_ANCHOR_SEARCH_OVERFETCH = 4;
1947
2083
  var HEAL_MAX_PROMPT_CHARS = 4e4;
1948
2084
  var HEAL_BATCH_SIZE = 25;
@@ -1950,7 +2086,12 @@ var HEAL_RECHECK_MS = 7 * 24 * 60 * 60 * 1e3;
1950
2086
  var SKIP_ERROR_LOG_CHARS = 4096;
1951
2087
  var formatSkipError = (err) => {
1952
2088
  let base;
1953
- if (err instanceof Error || typeof err !== "object" && typeof err !== "function") {
2089
+ let isErrorLike = false;
2090
+ try {
2091
+ isErrorLike = err instanceof Error;
2092
+ } catch {
2093
+ }
2094
+ if (isErrorLike || typeof err !== "object" && typeof err !== "function") {
1954
2095
  base = safeErrorToString(err);
1955
2096
  } else {
1956
2097
  try {
@@ -2368,6 +2509,10 @@ var MaintenanceService = class {
2368
2509
  if (!Number.isInteger(batchSize) || batchSize < 1) {
2369
2510
  throw new Error("Invalid batchSize: must be an integer >= 1");
2370
2511
  }
2512
+ const bodyTruncationChars = options?.bodyTruncationChars ?? HEAL_MAX_FACT_BODY_CHARS_L3;
2513
+ if (!Number.isInteger(bodyTruncationChars) || bodyTruncationChars < 1) {
2514
+ throw new Error("Invalid bodyTruncationChars: must be an integer >= 1");
2515
+ }
2371
2516
  const now = Date.now();
2372
2517
  const recheckCutoff = now - HEAL_RECHECK_MS;
2373
2518
  const orphanAfterDays = this.options.config?.orphanAfterDays !== void 0 ? this.options.config?.orphanAfterDays : 30;
@@ -2408,29 +2553,44 @@ var MaintenanceService = class {
2408
2553
  downgraded: staleDowngradedIds.length,
2409
2554
  deleted: orphanedIds.length,
2410
2555
  newFactsCreated: 0,
2411
- skipped: 0,
2556
+ skipped: [],
2557
+ degraded: [],
2412
2558
  remaining: counts2.eligible,
2413
2559
  deferred: counts2.deferred
2414
2560
  };
2415
2561
  }
2416
- const allTasks = await this.taskRepo.findAllPending([entityId]);
2562
+ const allTasks = await this.taskRepo.findAllPending([entityId], HEAL_MAX_TASKS);
2417
2563
  const recentEvents = await this.eventRepo.getRecent(entityId, 20);
2418
2564
  const toPromptShape = (f) => {
2419
2565
  const { embedding: _embedding, embedding_blob: _blob, ...rest } = f;
2420
2566
  return { ...rest, tags: typeof rest.tags === "string" ? JSON.parse(rest.tags) : rest.tags };
2421
2567
  };
2422
2568
  const anchorCache = /* @__PURE__ */ new Map();
2569
+ const degraded = [];
2423
2570
  const outcome = await runBatched({
2424
2571
  items: healCandidates,
2425
- buildPrompt: async (batch) => {
2426
- const documentAnchors = await this._selectHealAnchors(entityId, batch, anchorCache);
2427
- return this.promptService.buildHealPrompt(
2572
+ buildPrompt: async (batch, attemptLevel = 0) => {
2573
+ const documentAnchors = await this._selectHealAnchors(
2574
+ entityId,
2575
+ batch,
2576
+ // Per-batch anchor cap: `batch.length * HEAL_ANCHORS_PER_CANDIDATE` (capped at
2577
+ // HEAL_MAX_ANCHORS) right-sizes the anchor lookup so a 1-fact batch does
2578
+ // not overfetch the same 200 keyword hits a 25-fact batch once did.
2579
+ // buildHealPrompt applies the matching cap on its side — values must match.
2580
+ Math.min(HEAL_MAX_ANCHORS, batch.length * HEAL_ANCHORS_PER_CANDIDATE),
2581
+ anchorCache
2582
+ );
2583
+ const { prompts, degraded: batchDegraded } = await this.promptService.buildHealPrompt(
2428
2584
  batch.map(toPromptShape),
2429
2585
  documentAnchors,
2430
2586
  allTasks,
2431
2587
  recentEvents,
2432
- promptOverride
2588
+ promptOverride,
2589
+ attemptLevel,
2590
+ bodyTruncationChars
2433
2591
  );
2592
+ degraded.push(...batchDegraded);
2593
+ return prompts;
2434
2594
  },
2435
2595
  call: (prompts) => this.options.llmProvider.generateText(prompts),
2436
2596
  parse: (responseText, batch) => {
@@ -2504,8 +2664,14 @@ var MaintenanceService = class {
2504
2664
  insertedFacts.push({ id, entity_id: entityId, title: fact.title, body: fact.body, tags: JSON.stringify(fact.tags) });
2505
2665
  healFactsForDedupe.push({ id, title: fact.title });
2506
2666
  }
2667
+ const callErrorIds = new Set(
2668
+ outcome.skipped.filter((s) => s.reason === "call_error").map((s) => s.item.id)
2669
+ );
2507
2670
  await this.entryRepo.markHealChecked(
2508
- [...healCandidates.map((f) => f.id), ...insertedFacts.map((f) => f.id)],
2671
+ [
2672
+ ...healCandidates.map((f) => f.id).filter((id) => !callErrorIds.has(id)),
2673
+ ...insertedFacts.map((f) => f.id)
2674
+ ],
2509
2675
  entityId,
2510
2676
  now,
2511
2677
  tx
@@ -2523,6 +2689,13 @@ var MaintenanceService = class {
2523
2689
  await this.embeddingService.embedFact(fact);
2524
2690
  }
2525
2691
  this.searchService.evictCache(entityId);
2692
+ const skippedIds = new Set(outcome.skipped.map(({ item }) => item.id));
2693
+ const healedDegraded = degraded.filter((d) => !skippedIds.has(d.id));
2694
+ for (const d of healedDegraded) {
2695
+ console.warn(
2696
+ `[WikiMemory] heal healed under degraded context ${entityId}/${d.id}: body truncated from ${d.originalBodyChars} to ${d.truncatedBodyChars} chars`
2697
+ );
2698
+ }
2526
2699
  let scanned = outcome.skipped.length;
2527
2700
  for (const batchResult of outcome.results) scanned += batchResult.batch.length;
2528
2701
  const allDowngraded = /* @__PURE__ */ new Set([...staleDowngradedIds, ...safeDowngraded]);
@@ -2533,7 +2706,8 @@ var MaintenanceService = class {
2533
2706
  downgraded: allDowngraded.size,
2534
2707
  deleted: allDeleted.size,
2535
2708
  newFactsCreated: insertedFacts.length,
2536
- skipped: outcome.skipped.length,
2709
+ skipped: outcome.skipped.map(({ item, reason }) => ({ id: item.id, reason })),
2710
+ degraded: healedDegraded,
2537
2711
  remaining: counts.eligible,
2538
2712
  deferred: counts.deferred
2539
2713
  };
@@ -2617,7 +2791,15 @@ var MaintenanceService = class {
2617
2791
  };
2618
2792
  }
2619
2793
  if (outcome.skipped.length > 0) {
2620
- await this.entryRepo.markOntologyChecked(outcome.skipped.map((f) => f.id), entityId, now, this.db);
2794
+ const callErrorIds = new Set(
2795
+ outcome.skipped.filter((s) => s.reason === "call_error").map((s) => s.item.id)
2796
+ );
2797
+ await this.entryRepo.markOntologyChecked(
2798
+ outcome.skipped.filter(({ item }) => !callErrorIds.has(item.id)).map(({ item }) => item.id),
2799
+ entityId,
2800
+ now,
2801
+ this.db
2802
+ );
2621
2803
  }
2622
2804
  this.searchService.evictCache(entityId);
2623
2805
  const counts = await this.entryRepo.countUntypedByEntityId(entityId, recheckCutoff);
@@ -2733,7 +2915,7 @@ var MaintenanceService = class {
2733
2915
  * batches that reduce to the same query share one lookup. Caller-owned and
2734
2916
  * per-pass — see the call site in doRunHeal.
2735
2917
  */
2736
- async _selectHealAnchors(entityId, batch, cache) {
2918
+ async _selectHealAnchors(entityId, batch, cap = HEAL_MAX_ANCHORS, cache) {
2737
2919
  const query = batch.map((f) => f.title).join(" ").trim();
2738
2920
  if (!query) return [];
2739
2921
  const cached = cache?.get(query);
@@ -2741,7 +2923,7 @@ var MaintenanceService = class {
2741
2923
  const hits = this.searchService.searchKeyword(
2742
2924
  query,
2743
2925
  [entityId],
2744
- HEAL_MAX_ANCHORS * HEAL_ANCHOR_SEARCH_OVERFETCH
2926
+ cap * HEAL_ANCHOR_SEARCH_OVERFETCH
2745
2927
  );
2746
2928
  const hitIds = hits.map((h) => h.id);
2747
2929
  const anchors = [];
@@ -2752,7 +2934,7 @@ var MaintenanceService = class {
2752
2934
  const row = byId.get(id);
2753
2935
  if (!row) continue;
2754
2936
  anchors.push(row);
2755
- if (anchors.length >= HEAL_MAX_ANCHORS) break;
2937
+ if (anchors.length >= cap) break;
2756
2938
  }
2757
2939
  }
2758
2940
  cache?.set(query, anchors);
@@ -3043,7 +3225,12 @@ var RetrievalService = class {
3043
3225
  }
3044
3226
  }
3045
3227
  } catch (rankerErr) {
3046
- const rankerError = rankerErr instanceof Error ? rankerErr : new Error(String(rankerErr));
3228
+ let isErrorLike = false;
3229
+ try {
3230
+ isErrorLike = rankerErr instanceof Error;
3231
+ } catch {
3232
+ }
3233
+ const rankerError = isErrorLike ? rankerErr : new Error(safeErrorToString(rankerErr));
3047
3234
  const policy = this.options.vectorRankerFallback ?? "js-cosine";
3048
3235
  this.options.onVectorRankerFallback?.({
3049
3236
  error: this._sanitizeRankerError(rankerError),
@@ -3155,12 +3342,21 @@ var RetrievalService = class {
3155
3342
  }
3156
3343
  }
3157
3344
  } catch (err) {
3158
- const error = err instanceof Error ? err : new Error(String(err));
3345
+ let isErrorLike = false;
3346
+ try {
3347
+ isErrorLike = err instanceof Error;
3348
+ } catch {
3349
+ }
3350
+ const error = isErrorLike ? err : new Error(safeErrorToString(err));
3159
3351
  if (rankerShouldRethrow) {
3160
3352
  throw error;
3161
3353
  }
3162
3354
  if (pendingRankerFallbackError) {
3163
- error.cause = pendingRankerFallbackError;
3355
+ try {
3356
+ error.cause = pendingRankerFallbackError;
3357
+ } catch {
3358
+ this.options.onRetrievalFallback?.(pendingRankerFallbackError);
3359
+ }
3164
3360
  pendingRankerFallbackError = void 0;
3165
3361
  }
3166
3362
  this.options.onRetrievalFallback?.(error);