@anuma/sdk 6.43.0 → 6.43.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.
@@ -122,6 +122,18 @@ var init_logger = __esm({
122
122
  }
123
123
  });
124
124
 
125
+ // src/lib/taskType.ts
126
+ function taskTypeHeader(taskType) {
127
+ return taskType ? { [TASK_TYPE_HEADER]: taskType } : {};
128
+ }
129
+ var TASK_TYPE_HEADER;
130
+ var init_taskType = __esm({
131
+ "src/lib/taskType.ts"() {
132
+ "use strict";
133
+ TASK_TYPE_HEADER = "X-Anuma-Task-Type";
134
+ }
135
+ });
136
+
125
137
  // src/lib/pii/patterns.ts
126
138
  function luhnCheck(digits) {
127
139
  let sum = 0;
@@ -766,18 +778,6 @@ var init_internalFlowMarker = __esm({
766
778
  }
767
779
  });
768
780
 
769
- // src/lib/taskType.ts
770
- function taskTypeHeader(taskType) {
771
- return taskType ? { [TASK_TYPE_HEADER]: taskType } : {};
772
- }
773
- var TASK_TYPE_HEADER;
774
- var init_taskType = __esm({
775
- "src/lib/taskType.ts"() {
776
- "use strict";
777
- TASK_TYPE_HEADER = "X-Anuma-Task-Type";
778
- }
779
- });
780
-
781
781
  // src/lib/memory/portalLlm.ts
782
782
  function defaultBaseUrl() {
783
783
  return typeof process !== "undefined" && process.env?.ANUMA_PORTAL_BASE_URL || BASE_URL;
@@ -7132,6 +7132,7 @@ var DEFAULT_API_EMBEDDING_MODEL = "qwen/qwen3-embedding-8b";
7132
7132
 
7133
7133
  // src/lib/memoryEngine/generate.ts
7134
7134
  init_clientConfig();
7135
+ init_taskType();
7135
7136
  var EMBED_MAX_ATTEMPTS = 4;
7136
7137
  var DEFAULT_EMBEDDING_REQUEST_TIMEOUT_MS = 15e3;
7137
7138
  var DEFAULT_EMBEDDING_TOKEN_TIMEOUT_MS = 1e4;
@@ -7229,7 +7230,10 @@ async function embedOne(text14, options, outer) {
7229
7230
  const cached = cache.get(text14);
7230
7231
  if (cached) return Array.from(cached);
7231
7232
  }
7232
- const headers = await resolveAuthHeaders(options, outer);
7233
+ const headers = {
7234
+ ...await resolveAuthHeaders(options, outer),
7235
+ ...taskTypeHeader(options.taskType)
7236
+ };
7233
7237
  const response = await withEmbeddingRetry(
7234
7238
  () => withDeadline(
7235
7239
  (signal) => postApiV1Embeddings({
@@ -7324,7 +7328,10 @@ async function embedMany(texts, options, outer) {
7324
7328
  if (uncachedTexts.length === 0) {
7325
7329
  return results;
7326
7330
  }
7327
- const headers = await resolveAuthHeaders(options, outer);
7331
+ const headers = {
7332
+ ...await resolveAuthHeaders(options, outer),
7333
+ ...taskTypeHeader(options.taskType)
7334
+ };
7328
7335
  if (outer?.aborted) throw outer.reason;
7329
7336
  const embeddingModel = model ?? DEFAULT_API_EMBEDDING_MODEL;
7330
7337
  let newEmbeddings;
@@ -18522,6 +18529,50 @@ function applyMMR(candidates, k, lambda = 0.5) {
18522
18529
  return selected;
18523
18530
  }
18524
18531
 
18532
+ // src/lib/memory/recallTelemetry.ts
18533
+ function notifyMemoryCallback(callback, value) {
18534
+ try {
18535
+ callback?.(value);
18536
+ } catch {
18537
+ return;
18538
+ }
18539
+ }
18540
+ function factRecallDiagnostics(scores, elapsedMs, options = {}) {
18541
+ return {
18542
+ usedBudget: "low",
18543
+ reranked: false,
18544
+ candidateCount: options.candidateCount ?? scores.length,
18545
+ ...options.vaultSize !== void 0 && { vaultSize: options.vaultSize },
18546
+ ...options.decryptLast !== void 0 && { decryptLast: options.decryptLast },
18547
+ ...options.vaultRowsDecrypted !== void 0 && {
18548
+ vaultRowsDecrypted: options.vaultRowsDecrypted
18549
+ },
18550
+ ...options.vaultRowsEmbedded !== void 0 && {
18551
+ vaultRowsEmbedded: options.vaultRowsEmbedded
18552
+ },
18553
+ factCount: scores.length,
18554
+ chunkCount: 0,
18555
+ admittedCount: scores.length,
18556
+ topScore: scores.length ? Math.max(...scores) : -1,
18557
+ lowestAdmittedScore: scores.length ? Math.min(...scores) : -1,
18558
+ minScoreApplied: options.minScore ?? -1,
18559
+ truncated: options.truncated ?? false,
18560
+ graphLaneCount: 0,
18561
+ temporalLaneCount: 0,
18562
+ emptyReason: scores.length ? "" : options.vaultSize === 0 ? "vault-empty" : "no-candidates",
18563
+ timings: {
18564
+ total: elapsedMs,
18565
+ prep: 0,
18566
+ factLane: elapsedMs,
18567
+ rerank: 0,
18568
+ queryEmbed: options.queryEmbedMs ?? 0,
18569
+ chunkLane: 0,
18570
+ fuse: 0
18571
+ },
18572
+ degraded: options.embeddingsUnavailable ? ["embeddings-unavailable"] : []
18573
+ };
18574
+ }
18575
+
18525
18576
  // src/lib/memory/recency.ts
18526
18577
  var DAY_MS2 = 1e3 * 60 * 60 * 24;
18527
18578
  var DEFAULT_PER_YEAR_DECAY = 0.2;
@@ -19214,6 +19265,7 @@ function rankVaultMemories(_query, queryEmbedding, items, options) {
19214
19265
  }
19215
19266
  filtered.sort((a, b) => b.similarity - a.similarity);
19216
19267
  }
19268
+ notifyMemoryCallback(options?.onCandidateCount, filtered.length);
19217
19269
  const itemById = new Map(items.map((i) => [i.id, i]));
19218
19270
  return filtered.slice(0, limit).map((r) => resultFromItem(itemById.get(r.uniqueId), r.similarity));
19219
19271
  }
@@ -20196,6 +20248,7 @@ async function rankPreparedVaultCandidates(query, prepared, embeddingOptions, se
20196
20248
  const results = rankVaultMemories(query, queryEmbedding, embeddedItems, {
20197
20249
  limit,
20198
20250
  minSimilarity,
20251
+ onCandidateCount: searchOptions?.onCandidateCount,
20199
20252
  ...searchOptions?.supersessionBoost !== void 0 && {
20200
20253
  supersessionBoost: searchOptions.supersessionBoost
20201
20254
  },
@@ -20297,16 +20350,20 @@ var DEFAULT_SCOPE = "private";
20297
20350
  var DEFAULT_CONSOLIDATE_THRESHOLD = 0.55;
20298
20351
  var DEFAULT_CONSOLIDATE_TOP_K = 20;
20299
20352
  async function retain(content, ctx, options = {}) {
20353
+ const embeddingOptions = {
20354
+ ...ctx.embeddingOptions,
20355
+ taskType: "memory_retain_embed"
20356
+ };
20300
20357
  return retainWithPersistence(
20301
20358
  content,
20302
20359
  {
20303
- embeddingOptions: ctx.embeddingOptions,
20360
+ embeddingOptions,
20304
20361
  vaultCache: ctx.vaultCache,
20305
20362
  persistence: {
20306
20363
  prepare: (query, searchOptions) => prepareVaultCandidates(
20307
20364
  query,
20308
20365
  ctx.vaultCtx,
20309
- ctx.embeddingOptions,
20366
+ embeddingOptions,
20310
20367
  ctx.vaultCache,
20311
20368
  searchOptions
20312
20369
  ),
@@ -21460,7 +21517,17 @@ function parseVerdict(parsed) {
21460
21517
 
21461
21518
  // src/lib/memory/decayWorker.ts
21462
21519
  init_logger();
21463
- var EMPTY_RESULT = { archived: 0, deleted: 0, scanned: 0 };
21520
+ function emptyResult() {
21521
+ return {
21522
+ archived: 0,
21523
+ deleted: 0,
21524
+ scanned: 0,
21525
+ ruleDriven: { archived: 0, deleted: 0 },
21526
+ classifierDriven: { archived: 0, deleted: 0 },
21527
+ classifierCalls: 0,
21528
+ classifierCapHits: 0
21529
+ };
21530
+ }
21464
21531
  var DEFAULT_MAX_CLASSIFIER_CALLS_PER_SWEEP = 20;
21465
21532
  function resolveNow(now) {
21466
21533
  if (typeof now === "function") return now();
@@ -21499,42 +21566,45 @@ function createDecaySweeper(options) {
21499
21566
  let disposed = false;
21500
21567
  async function verdictFor(input, now, sweep) {
21501
21568
  const ruleVerdict = classifyDecay(input, now, policy);
21502
- if (ruleVerdict !== "keep") return ruleVerdict;
21503
- if (!classifier || !isBorderline(input)) return ruleVerdict;
21569
+ const rule = { verdict: ruleVerdict, source: "ruleDriven" };
21570
+ if (ruleVerdict !== "keep") return rule;
21571
+ if (!classifier || !isBorderline(input)) return rule;
21504
21572
  if (input.id) {
21505
21573
  const cached = classifierCache.get(input.id);
21506
- if (cached && cached.activityAt === lastActivityAt(input)) return cached.verdict;
21574
+ if (cached && cached.activityAt === lastActivityAt(input))
21575
+ return { verdict: cached.verdict, source: "classifierDriven" };
21507
21576
  }
21508
21577
  if (sweep.classifierCalls >= maxClassifierCalls) {
21578
+ sweep.classifierCapHits++;
21509
21579
  if (!sweep.ceilingLogged) {
21510
21580
  sweep.ceilingLogged = true;
21511
21581
  getLogger().warn(
21512
21582
  `[memory/decay] classifier per-sweep ceiling (${maxClassifierCalls}) reached; remaining borderline rows use the rule verdict this sweep`
21513
21583
  );
21514
21584
  }
21515
- return ruleVerdict;
21585
+ return rule;
21516
21586
  }
21517
21587
  sweep.classifierCalls++;
21518
21588
  try {
21519
21589
  const verdict = await classifier.classify(input, ruleVerdict, now);
21520
21590
  if (input.id) classifierCache.set(input.id, { activityAt: lastActivityAt(input), verdict });
21521
- return verdict;
21591
+ return { verdict, source: "classifierDriven" };
21522
21592
  } catch (err) {
21523
21593
  getLogger().warn(
21524
21594
  `[memory/decay] classifier failed; falling back to rule verdict: ${err instanceof Error ? err.message : String(err)}`
21525
21595
  );
21526
- return ruleVerdict;
21596
+ return rule;
21527
21597
  }
21528
21598
  }
21529
21599
  async function runSweep() {
21530
- if (disposed) return { ...EMPTY_RESULT };
21600
+ if (disposed) return emptyResult();
21531
21601
  const now = resolveNow(options.now);
21532
21602
  let candidates;
21533
21603
  try {
21534
21604
  candidates = await getDecayCandidatesRawOp(vaultCtx);
21535
21605
  } catch (err) {
21536
21606
  onError?.(err instanceof Error ? err : new Error(String(err)));
21537
- return { ...EMPTY_RESULT };
21607
+ return emptyResult();
21538
21608
  }
21539
21609
  if (classifierCache.size > 0) {
21540
21610
  const liveIds = new Set(candidates.map((c) => c.uniqueId));
@@ -21542,33 +21612,40 @@ function createDecaySweeper(options) {
21542
21612
  if (!liveIds.has(id)) classifierCache.delete(id);
21543
21613
  }
21544
21614
  }
21545
- const sweep = { classifierCalls: 0, ceilingLogged: false };
21615
+ const sweep = { classifierCalls: 0, classifierCapHits: 0, ceilingLogged: false };
21546
21616
  const toArchive = [];
21547
21617
  const toDelete = [];
21548
21618
  for (const c of candidates) {
21549
- const verdict = await verdictFor(toDecayInput(c), now, sweep);
21550
- if (verdict === "archive") toArchive.push(c);
21551
- else if (verdict === "delete") toDelete.push(c);
21552
- }
21553
- let archived = 0;
21554
- let deleted = 0;
21619
+ const { verdict, source } = await verdictFor(toDecayInput(c), now, sweep);
21620
+ if (verdict === "archive") toArchive.push({ candidate: c, source });
21621
+ else if (verdict === "delete") toDelete.push({ candidate: c, source });
21622
+ }
21623
+ const result = emptyResult();
21624
+ result.scanned = candidates.length;
21625
+ result.classifierCalls = sweep.classifierCalls;
21626
+ result.classifierCapHits = sweep.classifierCapHits;
21555
21627
  try {
21556
- for (const c of toArchive) {
21628
+ for (const { candidate: c, source } of toArchive) {
21557
21629
  const ok = await archiveVaultMemoryOp(vaultCtx, c.uniqueId, {
21558
21630
  now,
21559
21631
  expectedUpdatedAt: c.updatedAt,
21560
21632
  expectedLastObservedAt: c.lastObservedAt ?? null
21561
21633
  });
21562
- if (ok) archived++;
21634
+ if (ok) {
21635
+ result.archived++;
21636
+ result[source].archived++;
21637
+ }
21563
21638
  }
21564
- for (const c of toDelete) {
21639
+ for (const { candidate: c, source } of toDelete) {
21565
21640
  const ok = await hardDeleteDecayedOp(vaultCtx, c.uniqueId, { hardDeleteWindowMs, now });
21566
- if (ok) deleted++;
21641
+ if (ok) {
21642
+ result.deleted++;
21643
+ result[source].deleted++;
21644
+ }
21567
21645
  }
21568
21646
  } catch (err) {
21569
21647
  onError?.(err instanceof Error ? err : new Error(String(err)));
21570
21648
  }
21571
- const result = { archived, deleted, scanned: candidates.length };
21572
21649
  onSwept?.(result);
21573
21650
  return result;
21574
21651
  }
@@ -22173,6 +22250,10 @@ async function recall(query, ctx, options = {}) {
22173
22250
  const flags = flagsForBudget(usedBudget);
22174
22251
  const normalizedFacets = normalizeSubQueries(options.subQueries);
22175
22252
  const subQueries = normalizedFacets.length >= 2 ? normalizedFacets : void 0;
22253
+ const embeddingOptions = {
22254
+ ...ctx.embeddingOptions,
22255
+ taskType: "memory_recall_embed"
22256
+ };
22176
22257
  const t0 = nowMs2();
22177
22258
  let prepMs = 0;
22178
22259
  let factLaneMs = 0;
@@ -22268,7 +22349,7 @@ async function recall(query, ctx, options = {}) {
22268
22349
  let sharedEmbedMs = 0;
22269
22350
  const [queryEmbedding, entityRanking, temporalRanking] = await Promise.all([
22270
22351
  needsChunkEmbedding ? generateEmbedding(query, {
22271
- ...ctx.embeddingOptions,
22352
+ ...embeddingOptions,
22272
22353
  totalTimeoutMs: queryEmbedTotalTimeoutMs
22273
22354
  }).finally(() => {
22274
22355
  sharedEmbedMs = nowMs2() - prepStart;
@@ -22326,7 +22407,7 @@ async function recall(query, ctx, options = {}) {
22326
22407
  } = await (ctx.factSource?.search.bind(ctx.factSource) ?? ((searchQuery, searchOptions) => searchVaultMemoriesWithSize(
22327
22408
  searchQuery,
22328
22409
  ctx.vaultCtx,
22329
- ctx.embeddingOptions,
22410
+ embeddingOptions,
22330
22411
  ctx.vaultCache,
22331
22412
  searchOptions
22332
22413
  )))(query, {
@@ -22692,7 +22773,7 @@ function isDumpQuery(query) {
22692
22773
  async function listRecentFacts(ctx, limit, toolOptions) {
22693
22774
  const store = toolOptions?.memoryStore;
22694
22775
  const vaultCtx = ctx.vaultCtx;
22695
- if (!vaultCtx && !store) return [];
22776
+ if (!vaultCtx && !store) return { memories: [], truncated: false };
22696
22777
  const read = (ids) => store ? store.list({ memoryIds: ids }) : getVaultMemoriesByIdsOp(vaultCtx, ids);
22697
22778
  const filters = {
22698
22779
  ...toolOptions?.scopes && { scopes: toolOptions.scopes },
@@ -22703,34 +22784,47 @@ async function listRecentFacts(ctx, limit, toolOptions) {
22703
22784
  ...toolOptions?.folderId !== void 0 && { folderId: toolOptions.folderId }
22704
22785
  });
22705
22786
  const readable = [];
22787
+ const readLimit = limit + 1;
22788
+ let batchLimit = limit;
22706
22789
  let start = 0;
22707
- for (let size = limit; readable.length < limit; size *= 2) {
22790
+ let projected = 0;
22791
+ let exhausted = false;
22792
+ for (let size = readLimit; readable.length < readLimit; size *= 2) {
22708
22793
  const ordered = await project(size);
22709
- for (let i = start; i < ordered.length && readable.length < limit; i += limit) {
22710
- const batch = ordered.slice(i, i + limit).map((p) => p.uniqueId);
22794
+ projected = ordered.length;
22795
+ exhausted = !store || ordered.length < size;
22796
+ for (let i = start; i < ordered.length && readable.length < readLimit; ) {
22797
+ const batch = ordered.slice(i, i + batchLimit).map((p) => p.uniqueId);
22711
22798
  const byId = new Map((await read(batch)).map((m) => [m.uniqueId, m]));
22799
+ const previousCount = readable.length;
22712
22800
  for (const id of batch) {
22713
22801
  const m = byId.get(id);
22714
22802
  if (m && !isEncrypted(m.content)) readable.push(m);
22715
- if (readable.length >= limit) break;
22803
+ if (readable.length >= readLimit) break;
22716
22804
  }
22805
+ batchLimit = readable.length > previousCount ? Math.min(limit, readLimit - readable.length) : limit;
22806
+ i += batch.length;
22717
22807
  }
22718
- if (!store || ordered.length < size) break;
22808
+ if (exhausted) break;
22719
22809
  start = ordered.length;
22720
22810
  }
22721
- return readable.map((m) => ({
22722
- id: m.uniqueId,
22723
- kind: "fact",
22724
- content: m.content,
22725
- score: 0,
22726
- folderId: m.folderId,
22727
- eventTimeStart: m.eventTimeStart,
22728
- eventTimeEnd: m.eventTimeEnd,
22729
- eventTimeKind: m.eventTimeKind,
22730
- factType: m.factType,
22731
- createdAt: m.createdAt,
22732
- updatedAt: m.updatedAt
22733
- }));
22811
+ return {
22812
+ memories: readable.slice(0, limit).map((m) => ({
22813
+ id: m.uniqueId,
22814
+ kind: "fact",
22815
+ content: m.content,
22816
+ score: 0,
22817
+ folderId: m.folderId,
22818
+ eventTimeStart: m.eventTimeStart,
22819
+ eventTimeEnd: m.eventTimeEnd,
22820
+ eventTimeKind: m.eventTimeKind,
22821
+ factType: m.factType,
22822
+ createdAt: m.createdAt,
22823
+ updatedAt: m.updatedAt
22824
+ })),
22825
+ ...exhausted && { vaultSize: projected },
22826
+ truncated: readable.length > limit
22827
+ };
22734
22828
  }
22735
22829
  function createRecallTool(ctx, toolOptions, callbacks) {
22736
22830
  if (toolOptions?.memoryStore && toolOptions.folderId !== void 0)
@@ -22769,7 +22863,11 @@ function createRecallTool(ctx, toolOptions, callbacks) {
22769
22863
  },
22770
22864
  executor: async (args) => {
22771
22865
  if (typeof args.query !== "string" || args.query.length === 0) {
22772
- throw new Error("recall_memory: `query` is required and must be a non-empty string.");
22866
+ const error = new Error(
22867
+ "recall_memory: `query` is required and must be a non-empty string."
22868
+ );
22869
+ notifyMemoryCallback(callbacks?.onError, error);
22870
+ throw error;
22773
22871
  }
22774
22872
  const query = args.query;
22775
22873
  const now = Date.now();
@@ -22797,10 +22895,17 @@ function createRecallTool(ctx, toolOptions, callbacks) {
22797
22895
  RECALL_MAX_MEMORIES_PER_CONVERSATION - conversationMemories
22798
22896
  );
22799
22897
  const effectiveLimit = Math.min(requestLimit, remainingTurnBudget, remainingConvBudget);
22898
+ const truncatedByBudget = effectiveLimit < requestLimit;
22899
+ if (truncatedByBudget) {
22900
+ notifyMemoryCallback(callbacks?.onTruncated, {
22901
+ requestedLimit: requestLimit,
22902
+ effectiveLimit,
22903
+ truncatedByBudget
22904
+ });
22905
+ }
22800
22906
  if (effectiveLimit <= 0) {
22801
22907
  return VOLUME_LIMIT_NOTICE;
22802
22908
  }
22803
- const truncatedByBudget = effectiveLimit < requestLimit;
22804
22909
  turnMemories += effectiveLimit;
22805
22910
  conversationMemories += effectiveLimit;
22806
22911
  let surfaced = 0;
@@ -22809,7 +22914,27 @@ function createRecallTool(ctx, toolOptions, callbacks) {
22809
22914
  const wantsRecent = args.sort === "recent" && defaultTypes.includes("fact") && (ctx.vaultCtx !== void 0 || toolOptions?.memoryStore !== void 0);
22810
22915
  let result;
22811
22916
  if (wantsRecent) {
22812
- result = { memories: await listRecentFacts(ctx, effectiveLimit, toolOptions) };
22917
+ const start = Date.now();
22918
+ const recent = await listRecentFacts(ctx, effectiveLimit, toolOptions);
22919
+ result = recent;
22920
+ notifyMemoryCallback(callbacks?.onDiagnostics, {
22921
+ ...factRecallDiagnostics(
22922
+ result.memories.map((m) => m.score),
22923
+ Date.now() - start,
22924
+ {
22925
+ truncated: recent.truncated,
22926
+ ...recent.vaultSize !== void 0 && { vaultSize: recent.vaultSize }
22927
+ }
22928
+ ),
22929
+ truncatedByBudget
22930
+ });
22931
+ if (recent.truncated && !truncatedByBudget) {
22932
+ notifyMemoryCallback(callbacks?.onTruncated, {
22933
+ requestedLimit: requestLimit,
22934
+ effectiveLimit,
22935
+ truncatedByBudget
22936
+ });
22937
+ }
22813
22938
  } else {
22814
22939
  let subQueries;
22815
22940
  if (defaultBudget === "high" && toolOptions?.decomposeOptions) {
@@ -22821,6 +22946,14 @@ function createRecallTool(ctx, toolOptions, callbacks) {
22821
22946
  const recallOpts = {
22822
22947
  onDiagnostics: (d) => {
22823
22948
  recallDegraded = d.degraded;
22949
+ notifyMemoryCallback(callbacks?.onDiagnostics, { ...d, truncatedByBudget });
22950
+ if (d.truncated && !truncatedByBudget) {
22951
+ notifyMemoryCallback(callbacks?.onTruncated, {
22952
+ requestedLimit: requestLimit,
22953
+ effectiveLimit,
22954
+ truncatedByBudget
22955
+ });
22956
+ }
22824
22957
  },
22825
22958
  types: defaultTypes,
22826
22959
  limit: effectiveLimit,
@@ -22864,6 +22997,7 @@ ${TRUNCATION_NOTICE}` : formatted;
22864
22997
  const message = error instanceof Error ? error.message : "Unknown error";
22865
22998
  const wrapped = new Error(`recall_memory: search failed \u2014 ${message}`);
22866
22999
  wrapped.cause = error;
23000
+ notifyMemoryCallback(callbacks?.onError, wrapped);
22867
23001
  throw wrapped;
22868
23002
  } finally {
22869
23003
  const unused = effectiveLimit - surfaced;
@@ -23105,6 +23239,18 @@ When a memory refers to one of these, reuse the EXACT existing name (same spelli
23105
23239
  ` : "";
23106
23240
  for (let i = 0; i < memories.length; i += TOPIC_EXTRACTION_BATCH_SIZE) {
23107
23241
  const batch = memories.slice(i, i + TOPIC_EXTRACTION_BATCH_SIZE);
23242
+ const start = Date.now();
23243
+ let attempts = 0;
23244
+ let failure;
23245
+ const reportBatch = (unansweredCount) => {
23246
+ notifyMemoryCallback(options.onBatch, {
23247
+ memoryIds: batch.map((m) => m.id),
23248
+ attempts,
23249
+ durationMs: Date.now() - start,
23250
+ unansweredCount,
23251
+ ...failure && { failure }
23252
+ });
23253
+ };
23108
23254
  const rows = [];
23109
23255
  for (const m of batch) {
23110
23256
  const content = m.content.slice(0, MAX_CHARS_PER_MEMORY).replace(/\s+/g, " ").trim();
@@ -23126,6 +23272,12 @@ ${listing}
23126
23272
 
23127
23273
  List each memory's named entities.`,
23128
23274
  tag: "memory/topics",
23275
+ onAttempt: (attempt) => {
23276
+ attempts = attempt.attempt;
23277
+ },
23278
+ onFailure: (value) => {
23279
+ failure = value;
23280
+ },
23129
23281
  extra: { max_completion_tokens: MAX_COMPLETION_TOKENS },
23130
23282
  ...options.fetchFn && { fetchFn: options.fetchFn },
23131
23283
  ...options.maxAttempts !== void 0 && { maxAttempts: options.maxAttempts },
@@ -23134,6 +23286,7 @@ List each memory's named entities.`,
23134
23286
  ...options.backoffMs && { backoffMs: options.backoffMs }
23135
23287
  });
23136
23288
  if (parsed === null) {
23289
+ reportBatch(batch.length);
23137
23290
  getLogger().warn(
23138
23291
  `[memory/topics] batch of ${batch.length} failed after retries \u2014 will retry next sweep`
23139
23292
  );
@@ -23142,6 +23295,7 @@ List each memory's named entities.`,
23142
23295
  const validIds = new Set(batch.map((m) => m.id));
23143
23296
  const byId = parseTopicResponse(parsed, validIds);
23144
23297
  if (byId === null) {
23298
+ reportBatch(batch.length);
23145
23299
  getLogger().warn(
23146
23300
  `[memory/topics] batch of ${batch.length} returned an unrecognized shape \u2014 will retry next sweep`
23147
23301
  );
@@ -23154,6 +23308,7 @@ List each memory's named entities.`,
23154
23308
  }
23155
23309
  out.set(m.id, redactor ? restoreEntities(entities, redactor) : entities);
23156
23310
  }
23311
+ reportBatch(batch.filter((m) => !byId.has(m.id)).length);
23157
23312
  }
23158
23313
  return out;
23159
23314
  }
@@ -23192,6 +23347,7 @@ function isDegradedTopicSkip(reason) {
23192
23347
  return DEGRADED_TOPIC_SKIP[reason] === true;
23193
23348
  }
23194
23349
  async function extractAndLinkEntitiesForMemoriesOp(ctx, memoryIds, options) {
23350
+ const start = Date.now();
23195
23351
  const entityCtx = ctx.entityCtx;
23196
23352
  if (!entityCtx) {
23197
23353
  throw new Error("extractAndLinkEntitiesForMemoriesOp requires ctx.entityCtx");
@@ -23232,7 +23388,18 @@ async function extractAndLinkEntitiesForMemoriesOp(ctx, memoryIds, options) {
23232
23388
  skip(id, "unreadable");
23233
23389
  }
23234
23390
  }
23235
- const entitiesByMemory = await extractEntitiesForMemories(inputs, options);
23391
+ const batches = [];
23392
+ const llmFailures = /* @__PURE__ */ new Map();
23393
+ const entitiesByMemory = await extractEntitiesForMemories(inputs, {
23394
+ ...options,
23395
+ onBatch: (batch) => {
23396
+ batches.push(batch);
23397
+ if (batch.failure) {
23398
+ for (const id of batch.memoryIds) llmFailures.set(id, batch.failure);
23399
+ }
23400
+ notifyMemoryCallback(options.onBatch, batch);
23401
+ }
23402
+ });
23236
23403
  const toStamp = [];
23237
23404
  for (const input of inputs) {
23238
23405
  const entities = entitiesByMemory.get(input.id);
@@ -23263,7 +23430,16 @@ async function extractAndLinkEntitiesForMemoriesOp(ctx, memoryIds, options) {
23263
23430
  entitiesByMemory.delete(id);
23264
23431
  }
23265
23432
  }
23266
- return { entitiesByMemory, stampedIds, skippedIds, skippedReasons };
23433
+ return {
23434
+ entitiesByMemory,
23435
+ stampedIds,
23436
+ skippedIds,
23437
+ skippedReasons,
23438
+ batchCount: batches.length,
23439
+ durationMs: Date.now() - start,
23440
+ batches,
23441
+ llmFailures
23442
+ };
23267
23443
  }
23268
23444
 
23269
23445
  // src/lib/memory/store/local.ts
@@ -23418,7 +23594,7 @@ function createLocalMemoryStore(options) {
23418
23594
  search: (query, searchOptions) => searchVaultMemoriesWithSize(
23419
23595
  query,
23420
23596
  vaultCtx,
23421
- options.embeddingOptions,
23597
+ { ...options.embeddingOptions, taskType: "memory_recall_embed" },
23422
23598
  vaultCache,
23423
23599
  searchOptions
23424
23600
  ),
@@ -24082,6 +24258,7 @@ function createRemoteMemoryPipeline(options) {
24082
24258
  const budgetMs = search.queryEmbedTotalTimeoutMs ?? 8e3;
24083
24259
  const embed = (texts) => generateEmbeddings(texts, {
24084
24260
  ...embeddingOptions,
24261
+ taskType: mode === "retain" ? "memory_retain_embed" : "memory_recall_embed",
24085
24262
  totalTimeoutMs: budgetMs
24086
24263
  });
24087
24264
  const embedFacets = async () => {
@@ -24330,7 +24507,7 @@ function createRemoteMemoryPipeline(options) {
24330
24507
  const ranked = await rankPreparedVaultCandidates(
24331
24508
  text14,
24332
24509
  prepared,
24333
- embeddingOptions,
24510
+ { ...embeddingOptions, taskType: "memory_recall_embed" },
24334
24511
  searchOptions
24335
24512
  );
24336
24513
  return {
@@ -24352,7 +24529,7 @@ function createRemoteMemoryPipeline(options) {
24352
24529
  content,
24353
24530
  {
24354
24531
  persistence: port,
24355
- embeddingOptions,
24532
+ embeddingOptions: { ...embeddingOptions, taskType: "memory_retain_embed" },
24356
24533
  vaultCache: /* @__PURE__ */ new Map(),
24357
24534
  normalizeEmbedding: storedVector
24358
24535
  },
@@ -25081,7 +25258,8 @@ function createRemoteMemoryStore(options) {
25081
25258
  };
25082
25259
  },
25083
25260
  extractTopics: async (memoryIds, extractOptions) => {
25084
- const extractedAt = extractOptions.now ?? Date.now();
25261
+ const start = Date.now();
25262
+ const extractedAt = extractOptions.now ?? start;
25085
25263
  const skippedIds = [];
25086
25264
  const skippedReasons = /* @__PURE__ */ new Map();
25087
25265
  const skip = (id, reason) => {
@@ -25099,7 +25277,18 @@ function createRemoteMemoryStore(options) {
25099
25277
  else if (memory.is_deleted || memory.topics_user_managed) skip(id, "excluded");
25100
25278
  else inputs.push({ id, content: memory.content });
25101
25279
  }
25102
- const entitiesByMemory = await extractEntitiesForMemories(inputs, extractOptions);
25280
+ const batches = [];
25281
+ const llmFailures = /* @__PURE__ */ new Map();
25282
+ const entitiesByMemory = await extractEntitiesForMemories(inputs, {
25283
+ ...extractOptions,
25284
+ onBatch: (batch) => {
25285
+ batches.push(batch);
25286
+ if (batch.failure) {
25287
+ for (const id of batch.memoryIds) llmFailures.set(id, batch.failure);
25288
+ }
25289
+ notifyMemoryCallback(extractOptions.onBatch, batch);
25290
+ }
25291
+ });
25103
25292
  const answered = /* @__PURE__ */ new Map();
25104
25293
  for (const input of inputs) {
25105
25294
  const entities = entitiesByMemory.get(input.id);
@@ -25148,7 +25337,16 @@ function createRemoteMemoryStore(options) {
25148
25337
  getLogger().warn("[memory/remote-store] Failed to write extracted topics:", error);
25149
25338
  for (const id of answered.keys()) fail(id, "link-failed");
25150
25339
  }
25151
- return { entitiesByMemory, stampedIds, skippedIds, skippedReasons };
25340
+ return {
25341
+ entitiesByMemory,
25342
+ stampedIds,
25343
+ skippedIds,
25344
+ skippedReasons,
25345
+ batchCount: batches.length,
25346
+ durationMs: Date.now() - start,
25347
+ batches,
25348
+ llmFailures
25349
+ };
25152
25350
  },
25153
25351
  stampTopicsExtracted: async (memoryIds, extractedAt, version = TOPICS_EXTRACTION_VERSION) => {
25154
25352
  const ids = [...new Set(memoryIds)];
@@ -27164,9 +27362,13 @@ function formatVaultHits(hits) {
27164
27362
  return hits.map((h, i) => `[${i + 1}] (id: ${h.id}, similarity: ${h.score.toFixed(2)})
27165
27363
  ${h.content}`).join("\n\n");
27166
27364
  }
27167
- function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOptions) {
27365
+ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOptions, callbacks) {
27168
27366
  const limit = searchOptions?.limit ?? 5;
27169
27367
  const minSimilarity = searchOptions?.minSimilarity ?? 0.1;
27368
+ const recallEmbeddingOptions = {
27369
+ ...embeddingOptions,
27370
+ taskType: "memory_recall_embed"
27371
+ };
27170
27372
  const tuningForward = {
27171
27373
  ...searchOptions?.rerankTopN !== void 0 && { rerankTopN: searchOptions.rerankTopN },
27172
27374
  ...searchOptions?.ceWeight !== void 0 && { ceWeight: searchOptions.ceWeight },
@@ -27220,6 +27422,7 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
27220
27422
  const requestLimit = Number.isFinite(rawLimit) ? Math.min(Math.max(Math.floor(rawLimit), 1), RECALL_MAX_LIMIT) : limit;
27221
27423
  const argFolderId = args.folder_id;
27222
27424
  if (!query || typeof query !== "string") {
27425
+ notifyMemoryCallback(callbacks?.onError, new Error("A search query is required."));
27223
27426
  return "Error: A search query is required.";
27224
27427
  }
27225
27428
  try {
@@ -27227,7 +27430,11 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
27227
27430
  const budget = wantsDecompose ? "high" : searchOptions?.rerank ? "mid" : "low";
27228
27431
  const folderId = searchOptions?.folderId ?? argFolderId;
27229
27432
  if (searchOptions?.useFusion === false) {
27433
+ let candidateCount;
27230
27434
  const legacyOptions = {
27435
+ onCandidateCount: (count2) => {
27436
+ candidateCount = count2;
27437
+ },
27231
27438
  limit: requestLimit,
27232
27439
  minSimilarity,
27233
27440
  useFusion: false,
@@ -27235,13 +27442,38 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
27235
27442
  ...folderId !== void 0 && { folderId },
27236
27443
  ...searchOptions?.scopes && { scopes: searchOptions.scopes }
27237
27444
  };
27238
- const { results: legacy, embeddingsUnavailable } = "retain" in vaultCtx ? await vaultCtx.factSource.search(query, legacyOptions) : await searchVaultMemoriesWithSize(
27445
+ const start = Date.now();
27446
+ const outcome2 = "retain" in vaultCtx ? await vaultCtx.factSource.search(query, legacyOptions) : await searchVaultMemoriesWithSize(
27239
27447
  query,
27240
27448
  vaultCtx,
27241
- embeddingOptions,
27449
+ recallEmbeddingOptions,
27242
27450
  cache,
27243
27451
  legacyOptions
27244
27452
  );
27453
+ const { results: legacy, embeddingsUnavailable, vaultSize } = outcome2;
27454
+ const diagnostics = factRecallDiagnostics(
27455
+ legacy.map((m) => m.similarity),
27456
+ Date.now() - start,
27457
+ {
27458
+ vaultSize,
27459
+ minScore: minSimilarity,
27460
+ embeddingsUnavailable,
27461
+ candidateCount,
27462
+ truncated: candidateCount !== void 0 && candidateCount > requestLimit,
27463
+ decryptLast: outcome2.decryptLast,
27464
+ vaultRowsDecrypted: outcome2.rowsDecrypted,
27465
+ vaultRowsEmbedded: outcome2.rowsEmbedded,
27466
+ queryEmbedMs: outcome2.queryEmbedMs
27467
+ }
27468
+ );
27469
+ notifyMemoryCallback(callbacks?.onDiagnostics, diagnostics);
27470
+ if (diagnostics.truncated) {
27471
+ notifyMemoryCallback(callbacks?.onTruncated, {
27472
+ requestedLimit: requestLimit,
27473
+ effectiveLimit: requestLimit,
27474
+ truncatedByBudget: false
27475
+ });
27476
+ }
27245
27477
  if (legacy.length === 0) {
27246
27478
  return embeddingsUnavailable ? EMBEDDINGS_DEGRADED_EMPTY_NO_LEXICAL : "No relevant memories found in the vault.";
27247
27479
  }
@@ -27263,6 +27495,14 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
27263
27495
  {
27264
27496
  onDiagnostics: (d) => {
27265
27497
  recallDegraded = d.degraded;
27498
+ notifyMemoryCallback(callbacks?.onDiagnostics, d);
27499
+ if (d.truncated) {
27500
+ notifyMemoryCallback(callbacks?.onTruncated, {
27501
+ requestedLimit: requestLimit,
27502
+ effectiveLimit: requestLimit,
27503
+ truncatedByBudget: false
27504
+ });
27505
+ }
27266
27506
  },
27267
27507
  types: ["fact"],
27268
27508
  limit: requestLimit,
@@ -27295,6 +27535,10 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
27295
27535
 
27296
27536
  ${formatted}`;
27297
27537
  } catch (error) {
27538
+ notifyMemoryCallback(
27539
+ callbacks?.onError,
27540
+ error instanceof Error ? error : new Error(String(error))
27541
+ );
27298
27542
  const message = error instanceof Error ? error.message : "Unknown error";
27299
27543
  return `Error searching vault: ${message}`;
27300
27544
  }