@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.
@@ -20,7 +20,7 @@ import {
20
20
  taskTypeHeader,
21
21
  validateEndpointOverride,
22
22
  withInternalFlowMarker
23
- } from "./chunk-RQ3B2IYV.mjs";
23
+ } from "./chunk-AF37N5Z6.mjs";
24
24
  import {
25
25
  BASE_URL,
26
26
  MCP_R2_DOMAIN,
@@ -3738,7 +3738,10 @@ async function embedOne(text14, options, outer) {
3738
3738
  const cached = cache.get(text14);
3739
3739
  if (cached) return Array.from(cached);
3740
3740
  }
3741
- const headers = await resolveAuthHeaders(options, outer);
3741
+ const headers = {
3742
+ ...await resolveAuthHeaders(options, outer),
3743
+ ...taskTypeHeader(options.taskType)
3744
+ };
3742
3745
  const response = await withEmbeddingRetry(
3743
3746
  () => withDeadline(
3744
3747
  (signal) => postApiV1Embeddings({
@@ -3833,7 +3836,10 @@ async function embedMany(texts, options, outer) {
3833
3836
  if (uncachedTexts.length === 0) {
3834
3837
  return results;
3835
3838
  }
3836
- const headers = await resolveAuthHeaders(options, outer);
3839
+ const headers = {
3840
+ ...await resolveAuthHeaders(options, outer),
3841
+ ...taskTypeHeader(options.taskType)
3842
+ };
3837
3843
  if (outer?.aborted) throw outer.reason;
3838
3844
  const embeddingModel = model ?? DEFAULT_API_EMBEDDING_MODEL;
3839
3845
  let newEmbeddings;
@@ -13541,6 +13547,50 @@ function applyMMR(candidates, k, lambda = 0.5) {
13541
13547
  return selected;
13542
13548
  }
13543
13549
 
13550
+ // src/lib/memory/recallTelemetry.ts
13551
+ function notifyMemoryCallback(callback, value) {
13552
+ try {
13553
+ callback?.(value);
13554
+ } catch {
13555
+ return;
13556
+ }
13557
+ }
13558
+ function factRecallDiagnostics(scores, elapsedMs, options = {}) {
13559
+ return {
13560
+ usedBudget: "low",
13561
+ reranked: false,
13562
+ candidateCount: options.candidateCount ?? scores.length,
13563
+ ...options.vaultSize !== void 0 && { vaultSize: options.vaultSize },
13564
+ ...options.decryptLast !== void 0 && { decryptLast: options.decryptLast },
13565
+ ...options.vaultRowsDecrypted !== void 0 && {
13566
+ vaultRowsDecrypted: options.vaultRowsDecrypted
13567
+ },
13568
+ ...options.vaultRowsEmbedded !== void 0 && {
13569
+ vaultRowsEmbedded: options.vaultRowsEmbedded
13570
+ },
13571
+ factCount: scores.length,
13572
+ chunkCount: 0,
13573
+ admittedCount: scores.length,
13574
+ topScore: scores.length ? Math.max(...scores) : -1,
13575
+ lowestAdmittedScore: scores.length ? Math.min(...scores) : -1,
13576
+ minScoreApplied: options.minScore ?? -1,
13577
+ truncated: options.truncated ?? false,
13578
+ graphLaneCount: 0,
13579
+ temporalLaneCount: 0,
13580
+ emptyReason: scores.length ? "" : options.vaultSize === 0 ? "vault-empty" : "no-candidates",
13581
+ timings: {
13582
+ total: elapsedMs,
13583
+ prep: 0,
13584
+ factLane: elapsedMs,
13585
+ rerank: 0,
13586
+ queryEmbed: options.queryEmbedMs ?? 0,
13587
+ chunkLane: 0,
13588
+ fuse: 0
13589
+ },
13590
+ degraded: options.embeddingsUnavailable ? ["embeddings-unavailable"] : []
13591
+ };
13592
+ }
13593
+
13544
13594
  // src/lib/memory/recency.ts
13545
13595
  var DAY_MS2 = 1e3 * 60 * 60 * 24;
13546
13596
  var DEFAULT_PER_YEAR_DECAY = 0.2;
@@ -14230,6 +14280,7 @@ function rankVaultMemories(_query, queryEmbedding, items, options) {
14230
14280
  }
14231
14281
  filtered.sort((a, b) => b.similarity - a.similarity);
14232
14282
  }
14283
+ notifyMemoryCallback(options?.onCandidateCount, filtered.length);
14233
14284
  const itemById = new Map(items.map((i) => [i.id, i]));
14234
14285
  return filtered.slice(0, limit).map((r) => resultFromItem(itemById.get(r.uniqueId), r.similarity));
14235
14286
  }
@@ -15212,6 +15263,7 @@ async function rankPreparedVaultCandidates(query, prepared, embeddingOptions, se
15212
15263
  const results = rankVaultMemories(query, queryEmbedding, embeddedItems, {
15213
15264
  limit,
15214
15265
  minSimilarity,
15266
+ onCandidateCount: searchOptions?.onCandidateCount,
15215
15267
  ...searchOptions?.supersessionBoost !== void 0 && {
15216
15268
  supersessionBoost: searchOptions.supersessionBoost
15217
15269
  },
@@ -15312,16 +15364,20 @@ var DEFAULT_SCOPE = "private";
15312
15364
  var DEFAULT_CONSOLIDATE_THRESHOLD = 0.55;
15313
15365
  var DEFAULT_CONSOLIDATE_TOP_K = 20;
15314
15366
  async function retain(content, ctx, options = {}) {
15367
+ const embeddingOptions = {
15368
+ ...ctx.embeddingOptions,
15369
+ taskType: "memory_retain_embed"
15370
+ };
15315
15371
  return retainWithPersistence(
15316
15372
  content,
15317
15373
  {
15318
- embeddingOptions: ctx.embeddingOptions,
15374
+ embeddingOptions,
15319
15375
  vaultCache: ctx.vaultCache,
15320
15376
  persistence: {
15321
15377
  prepare: (query, searchOptions) => prepareVaultCandidates(
15322
15378
  query,
15323
15379
  ctx.vaultCtx,
15324
- ctx.embeddingOptions,
15380
+ embeddingOptions,
15325
15381
  ctx.vaultCache,
15326
15382
  searchOptions
15327
15383
  ),
@@ -15605,7 +15661,7 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
15605
15661
  content: m.content,
15606
15662
  similarity: m.similarity
15607
15663
  }));
15608
- const { consolidateMemory: doConsolidate } = await import("./consolidate-7WCBP5J7.mjs");
15664
+ const { consolidateMemory: doConsolidate } = await import("./consolidate-TOITBD6W.mjs");
15609
15665
  const decision = await doConsolidate(trimmed, candidates, consolidateOptions);
15610
15666
  if (decision.action === "create") return decision.fallbackReason ? null : { create: true };
15611
15667
  if (decision.action === "supersede" && decision.content) {
@@ -16469,7 +16525,17 @@ function parseVerdict(parsed) {
16469
16525
  }
16470
16526
 
16471
16527
  // src/lib/memory/decayWorker.ts
16472
- var EMPTY_RESULT = { archived: 0, deleted: 0, scanned: 0 };
16528
+ function emptyResult() {
16529
+ return {
16530
+ archived: 0,
16531
+ deleted: 0,
16532
+ scanned: 0,
16533
+ ruleDriven: { archived: 0, deleted: 0 },
16534
+ classifierDriven: { archived: 0, deleted: 0 },
16535
+ classifierCalls: 0,
16536
+ classifierCapHits: 0
16537
+ };
16538
+ }
16473
16539
  var DEFAULT_MAX_CLASSIFIER_CALLS_PER_SWEEP = 20;
16474
16540
  function resolveNow(now) {
16475
16541
  if (typeof now === "function") return now();
@@ -16508,42 +16574,45 @@ function createDecaySweeper(options) {
16508
16574
  let disposed = false;
16509
16575
  async function verdictFor(input, now, sweep) {
16510
16576
  const ruleVerdict = classifyDecay(input, now, policy);
16511
- if (ruleVerdict !== "keep") return ruleVerdict;
16512
- if (!classifier || !isBorderline(input)) return ruleVerdict;
16577
+ const rule = { verdict: ruleVerdict, source: "ruleDriven" };
16578
+ if (ruleVerdict !== "keep") return rule;
16579
+ if (!classifier || !isBorderline(input)) return rule;
16513
16580
  if (input.id) {
16514
16581
  const cached = classifierCache.get(input.id);
16515
- if (cached && cached.activityAt === lastActivityAt(input)) return cached.verdict;
16582
+ if (cached && cached.activityAt === lastActivityAt(input))
16583
+ return { verdict: cached.verdict, source: "classifierDriven" };
16516
16584
  }
16517
16585
  if (sweep.classifierCalls >= maxClassifierCalls) {
16586
+ sweep.classifierCapHits++;
16518
16587
  if (!sweep.ceilingLogged) {
16519
16588
  sweep.ceilingLogged = true;
16520
16589
  getLogger().warn(
16521
16590
  `[memory/decay] classifier per-sweep ceiling (${maxClassifierCalls}) reached; remaining borderline rows use the rule verdict this sweep`
16522
16591
  );
16523
16592
  }
16524
- return ruleVerdict;
16593
+ return rule;
16525
16594
  }
16526
16595
  sweep.classifierCalls++;
16527
16596
  try {
16528
16597
  const verdict = await classifier.classify(input, ruleVerdict, now);
16529
16598
  if (input.id) classifierCache.set(input.id, { activityAt: lastActivityAt(input), verdict });
16530
- return verdict;
16599
+ return { verdict, source: "classifierDriven" };
16531
16600
  } catch (err) {
16532
16601
  getLogger().warn(
16533
16602
  `[memory/decay] classifier failed; falling back to rule verdict: ${err instanceof Error ? err.message : String(err)}`
16534
16603
  );
16535
- return ruleVerdict;
16604
+ return rule;
16536
16605
  }
16537
16606
  }
16538
16607
  async function runSweep() {
16539
- if (disposed) return { ...EMPTY_RESULT };
16608
+ if (disposed) return emptyResult();
16540
16609
  const now = resolveNow(options.now);
16541
16610
  let candidates;
16542
16611
  try {
16543
16612
  candidates = await getDecayCandidatesRawOp(vaultCtx);
16544
16613
  } catch (err) {
16545
16614
  onError?.(err instanceof Error ? err : new Error(String(err)));
16546
- return { ...EMPTY_RESULT };
16615
+ return emptyResult();
16547
16616
  }
16548
16617
  if (classifierCache.size > 0) {
16549
16618
  const liveIds = new Set(candidates.map((c) => c.uniqueId));
@@ -16551,33 +16620,40 @@ function createDecaySweeper(options) {
16551
16620
  if (!liveIds.has(id)) classifierCache.delete(id);
16552
16621
  }
16553
16622
  }
16554
- const sweep = { classifierCalls: 0, ceilingLogged: false };
16623
+ const sweep = { classifierCalls: 0, classifierCapHits: 0, ceilingLogged: false };
16555
16624
  const toArchive = [];
16556
16625
  const toDelete = [];
16557
16626
  for (const c of candidates) {
16558
- const verdict = await verdictFor(toDecayInput(c), now, sweep);
16559
- if (verdict === "archive") toArchive.push(c);
16560
- else if (verdict === "delete") toDelete.push(c);
16561
- }
16562
- let archived = 0;
16563
- let deleted = 0;
16627
+ const { verdict, source } = await verdictFor(toDecayInput(c), now, sweep);
16628
+ if (verdict === "archive") toArchive.push({ candidate: c, source });
16629
+ else if (verdict === "delete") toDelete.push({ candidate: c, source });
16630
+ }
16631
+ const result = emptyResult();
16632
+ result.scanned = candidates.length;
16633
+ result.classifierCalls = sweep.classifierCalls;
16634
+ result.classifierCapHits = sweep.classifierCapHits;
16564
16635
  try {
16565
- for (const c of toArchive) {
16636
+ for (const { candidate: c, source } of toArchive) {
16566
16637
  const ok = await archiveVaultMemoryOp(vaultCtx, c.uniqueId, {
16567
16638
  now,
16568
16639
  expectedUpdatedAt: c.updatedAt,
16569
16640
  expectedLastObservedAt: c.lastObservedAt ?? null
16570
16641
  });
16571
- if (ok) archived++;
16642
+ if (ok) {
16643
+ result.archived++;
16644
+ result[source].archived++;
16645
+ }
16572
16646
  }
16573
- for (const c of toDelete) {
16647
+ for (const { candidate: c, source } of toDelete) {
16574
16648
  const ok = await hardDeleteDecayedOp(vaultCtx, c.uniqueId, { hardDeleteWindowMs, now });
16575
- if (ok) deleted++;
16649
+ if (ok) {
16650
+ result.deleted++;
16651
+ result[source].deleted++;
16652
+ }
16576
16653
  }
16577
16654
  } catch (err) {
16578
16655
  onError?.(err instanceof Error ? err : new Error(String(err)));
16579
16656
  }
16580
- const result = { archived, deleted, scanned: candidates.length };
16581
16657
  onSwept?.(result);
16582
16658
  return result;
16583
16659
  }
@@ -17175,6 +17251,10 @@ async function recall(query, ctx, options = {}) {
17175
17251
  const flags = flagsForBudget(usedBudget);
17176
17252
  const normalizedFacets = normalizeSubQueries(options.subQueries);
17177
17253
  const subQueries = normalizedFacets.length >= 2 ? normalizedFacets : void 0;
17254
+ const embeddingOptions = {
17255
+ ...ctx.embeddingOptions,
17256
+ taskType: "memory_recall_embed"
17257
+ };
17178
17258
  const t0 = nowMs2();
17179
17259
  let prepMs = 0;
17180
17260
  let factLaneMs = 0;
@@ -17270,7 +17350,7 @@ async function recall(query, ctx, options = {}) {
17270
17350
  let sharedEmbedMs = 0;
17271
17351
  const [queryEmbedding, entityRanking, temporalRanking] = await Promise.all([
17272
17352
  needsChunkEmbedding ? generateEmbedding(query, {
17273
- ...ctx.embeddingOptions,
17353
+ ...embeddingOptions,
17274
17354
  totalTimeoutMs: queryEmbedTotalTimeoutMs
17275
17355
  }).finally(() => {
17276
17356
  sharedEmbedMs = nowMs2() - prepStart;
@@ -17328,7 +17408,7 @@ async function recall(query, ctx, options = {}) {
17328
17408
  } = await (ctx.factSource?.search.bind(ctx.factSource) ?? ((searchQuery, searchOptions) => searchVaultMemoriesWithSize(
17329
17409
  searchQuery,
17330
17410
  ctx.vaultCtx,
17331
- ctx.embeddingOptions,
17411
+ embeddingOptions,
17332
17412
  ctx.vaultCache,
17333
17413
  searchOptions
17334
17414
  )))(query, {
@@ -17694,7 +17774,7 @@ function isDumpQuery(query) {
17694
17774
  async function listRecentFacts(ctx, limit, toolOptions) {
17695
17775
  const store = toolOptions?.memoryStore;
17696
17776
  const vaultCtx = ctx.vaultCtx;
17697
- if (!vaultCtx && !store) return [];
17777
+ if (!vaultCtx && !store) return { memories: [], truncated: false };
17698
17778
  const read = (ids) => store ? store.list({ memoryIds: ids }) : getVaultMemoriesByIdsOp(vaultCtx, ids);
17699
17779
  const filters = {
17700
17780
  ...toolOptions?.scopes && { scopes: toolOptions.scopes },
@@ -17705,34 +17785,47 @@ async function listRecentFacts(ctx, limit, toolOptions) {
17705
17785
  ...toolOptions?.folderId !== void 0 && { folderId: toolOptions.folderId }
17706
17786
  });
17707
17787
  const readable = [];
17788
+ const readLimit = limit + 1;
17789
+ let batchLimit = limit;
17708
17790
  let start = 0;
17709
- for (let size = limit; readable.length < limit; size *= 2) {
17791
+ let projected = 0;
17792
+ let exhausted = false;
17793
+ for (let size = readLimit; readable.length < readLimit; size *= 2) {
17710
17794
  const ordered = await project(size);
17711
- for (let i = start; i < ordered.length && readable.length < limit; i += limit) {
17712
- const batch = ordered.slice(i, i + limit).map((p) => p.uniqueId);
17795
+ projected = ordered.length;
17796
+ exhausted = !store || ordered.length < size;
17797
+ for (let i = start; i < ordered.length && readable.length < readLimit; ) {
17798
+ const batch = ordered.slice(i, i + batchLimit).map((p) => p.uniqueId);
17713
17799
  const byId = new Map((await read(batch)).map((m) => [m.uniqueId, m]));
17800
+ const previousCount = readable.length;
17714
17801
  for (const id of batch) {
17715
17802
  const m = byId.get(id);
17716
17803
  if (m && !isEncrypted(m.content)) readable.push(m);
17717
- if (readable.length >= limit) break;
17804
+ if (readable.length >= readLimit) break;
17718
17805
  }
17806
+ batchLimit = readable.length > previousCount ? Math.min(limit, readLimit - readable.length) : limit;
17807
+ i += batch.length;
17719
17808
  }
17720
- if (!store || ordered.length < size) break;
17809
+ if (exhausted) break;
17721
17810
  start = ordered.length;
17722
17811
  }
17723
- return readable.map((m) => ({
17724
- id: m.uniqueId,
17725
- kind: "fact",
17726
- content: m.content,
17727
- score: 0,
17728
- folderId: m.folderId,
17729
- eventTimeStart: m.eventTimeStart,
17730
- eventTimeEnd: m.eventTimeEnd,
17731
- eventTimeKind: m.eventTimeKind,
17732
- factType: m.factType,
17733
- createdAt: m.createdAt,
17734
- updatedAt: m.updatedAt
17735
- }));
17812
+ return {
17813
+ memories: readable.slice(0, limit).map((m) => ({
17814
+ id: m.uniqueId,
17815
+ kind: "fact",
17816
+ content: m.content,
17817
+ score: 0,
17818
+ folderId: m.folderId,
17819
+ eventTimeStart: m.eventTimeStart,
17820
+ eventTimeEnd: m.eventTimeEnd,
17821
+ eventTimeKind: m.eventTimeKind,
17822
+ factType: m.factType,
17823
+ createdAt: m.createdAt,
17824
+ updatedAt: m.updatedAt
17825
+ })),
17826
+ ...exhausted && { vaultSize: projected },
17827
+ truncated: readable.length > limit
17828
+ };
17736
17829
  }
17737
17830
  function createRecallTool(ctx, toolOptions, callbacks) {
17738
17831
  if (toolOptions?.memoryStore && toolOptions.folderId !== void 0)
@@ -17771,7 +17864,11 @@ function createRecallTool(ctx, toolOptions, callbacks) {
17771
17864
  },
17772
17865
  executor: async (args) => {
17773
17866
  if (typeof args.query !== "string" || args.query.length === 0) {
17774
- throw new Error("recall_memory: `query` is required and must be a non-empty string.");
17867
+ const error = new Error(
17868
+ "recall_memory: `query` is required and must be a non-empty string."
17869
+ );
17870
+ notifyMemoryCallback(callbacks?.onError, error);
17871
+ throw error;
17775
17872
  }
17776
17873
  const query = args.query;
17777
17874
  const now = Date.now();
@@ -17799,10 +17896,17 @@ function createRecallTool(ctx, toolOptions, callbacks) {
17799
17896
  RECALL_MAX_MEMORIES_PER_CONVERSATION - conversationMemories
17800
17897
  );
17801
17898
  const effectiveLimit = Math.min(requestLimit, remainingTurnBudget, remainingConvBudget);
17899
+ const truncatedByBudget = effectiveLimit < requestLimit;
17900
+ if (truncatedByBudget) {
17901
+ notifyMemoryCallback(callbacks?.onTruncated, {
17902
+ requestedLimit: requestLimit,
17903
+ effectiveLimit,
17904
+ truncatedByBudget
17905
+ });
17906
+ }
17802
17907
  if (effectiveLimit <= 0) {
17803
17908
  return VOLUME_LIMIT_NOTICE;
17804
17909
  }
17805
- const truncatedByBudget = effectiveLimit < requestLimit;
17806
17910
  turnMemories += effectiveLimit;
17807
17911
  conversationMemories += effectiveLimit;
17808
17912
  let surfaced = 0;
@@ -17811,7 +17915,27 @@ function createRecallTool(ctx, toolOptions, callbacks) {
17811
17915
  const wantsRecent = args.sort === "recent" && defaultTypes.includes("fact") && (ctx.vaultCtx !== void 0 || toolOptions?.memoryStore !== void 0);
17812
17916
  let result;
17813
17917
  if (wantsRecent) {
17814
- result = { memories: await listRecentFacts(ctx, effectiveLimit, toolOptions) };
17918
+ const start = Date.now();
17919
+ const recent = await listRecentFacts(ctx, effectiveLimit, toolOptions);
17920
+ result = recent;
17921
+ notifyMemoryCallback(callbacks?.onDiagnostics, {
17922
+ ...factRecallDiagnostics(
17923
+ result.memories.map((m) => m.score),
17924
+ Date.now() - start,
17925
+ {
17926
+ truncated: recent.truncated,
17927
+ ...recent.vaultSize !== void 0 && { vaultSize: recent.vaultSize }
17928
+ }
17929
+ ),
17930
+ truncatedByBudget
17931
+ });
17932
+ if (recent.truncated && !truncatedByBudget) {
17933
+ notifyMemoryCallback(callbacks?.onTruncated, {
17934
+ requestedLimit: requestLimit,
17935
+ effectiveLimit,
17936
+ truncatedByBudget
17937
+ });
17938
+ }
17815
17939
  } else {
17816
17940
  let subQueries;
17817
17941
  if (defaultBudget === "high" && toolOptions?.decomposeOptions) {
@@ -17823,6 +17947,14 @@ function createRecallTool(ctx, toolOptions, callbacks) {
17823
17947
  const recallOpts = {
17824
17948
  onDiagnostics: (d) => {
17825
17949
  recallDegraded = d.degraded;
17950
+ notifyMemoryCallback(callbacks?.onDiagnostics, { ...d, truncatedByBudget });
17951
+ if (d.truncated && !truncatedByBudget) {
17952
+ notifyMemoryCallback(callbacks?.onTruncated, {
17953
+ requestedLimit: requestLimit,
17954
+ effectiveLimit,
17955
+ truncatedByBudget
17956
+ });
17957
+ }
17826
17958
  },
17827
17959
  types: defaultTypes,
17828
17960
  limit: effectiveLimit,
@@ -17866,6 +17998,7 @@ ${TRUNCATION_NOTICE}` : formatted;
17866
17998
  const message = error instanceof Error ? error.message : "Unknown error";
17867
17999
  const wrapped = new Error(`recall_memory: search failed \u2014 ${message}`);
17868
18000
  wrapped.cause = error;
18001
+ notifyMemoryCallback(callbacks?.onError, wrapped);
17869
18002
  throw wrapped;
17870
18003
  } finally {
17871
18004
  const unused = effectiveLimit - surfaced;
@@ -18099,6 +18232,18 @@ When a memory refers to one of these, reuse the EXACT existing name (same spelli
18099
18232
  ` : "";
18100
18233
  for (let i = 0; i < memories.length; i += TOPIC_EXTRACTION_BATCH_SIZE) {
18101
18234
  const batch = memories.slice(i, i + TOPIC_EXTRACTION_BATCH_SIZE);
18235
+ const start = Date.now();
18236
+ let attempts = 0;
18237
+ let failure;
18238
+ const reportBatch = (unansweredCount) => {
18239
+ notifyMemoryCallback(options.onBatch, {
18240
+ memoryIds: batch.map((m) => m.id),
18241
+ attempts,
18242
+ durationMs: Date.now() - start,
18243
+ unansweredCount,
18244
+ ...failure && { failure }
18245
+ });
18246
+ };
18102
18247
  const rows = [];
18103
18248
  for (const m of batch) {
18104
18249
  const content = m.content.slice(0, MAX_CHARS_PER_MEMORY).replace(/\s+/g, " ").trim();
@@ -18120,6 +18265,12 @@ ${listing}
18120
18265
 
18121
18266
  List each memory's named entities.`,
18122
18267
  tag: "memory/topics",
18268
+ onAttempt: (attempt) => {
18269
+ attempts = attempt.attempt;
18270
+ },
18271
+ onFailure: (value) => {
18272
+ failure = value;
18273
+ },
18123
18274
  extra: { max_completion_tokens: MAX_COMPLETION_TOKENS },
18124
18275
  ...options.fetchFn && { fetchFn: options.fetchFn },
18125
18276
  ...options.maxAttempts !== void 0 && { maxAttempts: options.maxAttempts },
@@ -18128,6 +18279,7 @@ List each memory's named entities.`,
18128
18279
  ...options.backoffMs && { backoffMs: options.backoffMs }
18129
18280
  });
18130
18281
  if (parsed === null) {
18282
+ reportBatch(batch.length);
18131
18283
  getLogger().warn(
18132
18284
  `[memory/topics] batch of ${batch.length} failed after retries \u2014 will retry next sweep`
18133
18285
  );
@@ -18136,6 +18288,7 @@ List each memory's named entities.`,
18136
18288
  const validIds = new Set(batch.map((m) => m.id));
18137
18289
  const byId = parseTopicResponse(parsed, validIds);
18138
18290
  if (byId === null) {
18291
+ reportBatch(batch.length);
18139
18292
  getLogger().warn(
18140
18293
  `[memory/topics] batch of ${batch.length} returned an unrecognized shape \u2014 will retry next sweep`
18141
18294
  );
@@ -18148,6 +18301,7 @@ List each memory's named entities.`,
18148
18301
  }
18149
18302
  out.set(m.id, redactor ? restoreEntities(entities, redactor) : entities);
18150
18303
  }
18304
+ reportBatch(batch.filter((m) => !byId.has(m.id)).length);
18151
18305
  }
18152
18306
  return out;
18153
18307
  }
@@ -18186,6 +18340,7 @@ function isDegradedTopicSkip(reason) {
18186
18340
  return DEGRADED_TOPIC_SKIP[reason] === true;
18187
18341
  }
18188
18342
  async function extractAndLinkEntitiesForMemoriesOp(ctx, memoryIds, options) {
18343
+ const start = Date.now();
18189
18344
  const entityCtx = ctx.entityCtx;
18190
18345
  if (!entityCtx) {
18191
18346
  throw new Error("extractAndLinkEntitiesForMemoriesOp requires ctx.entityCtx");
@@ -18226,7 +18381,18 @@ async function extractAndLinkEntitiesForMemoriesOp(ctx, memoryIds, options) {
18226
18381
  skip(id, "unreadable");
18227
18382
  }
18228
18383
  }
18229
- const entitiesByMemory = await extractEntitiesForMemories(inputs, options);
18384
+ const batches = [];
18385
+ const llmFailures = /* @__PURE__ */ new Map();
18386
+ const entitiesByMemory = await extractEntitiesForMemories(inputs, {
18387
+ ...options,
18388
+ onBatch: (batch) => {
18389
+ batches.push(batch);
18390
+ if (batch.failure) {
18391
+ for (const id of batch.memoryIds) llmFailures.set(id, batch.failure);
18392
+ }
18393
+ notifyMemoryCallback(options.onBatch, batch);
18394
+ }
18395
+ });
18230
18396
  const toStamp = [];
18231
18397
  for (const input of inputs) {
18232
18398
  const entities = entitiesByMemory.get(input.id);
@@ -18257,7 +18423,16 @@ async function extractAndLinkEntitiesForMemoriesOp(ctx, memoryIds, options) {
18257
18423
  entitiesByMemory.delete(id);
18258
18424
  }
18259
18425
  }
18260
- return { entitiesByMemory, stampedIds, skippedIds, skippedReasons };
18426
+ return {
18427
+ entitiesByMemory,
18428
+ stampedIds,
18429
+ skippedIds,
18430
+ skippedReasons,
18431
+ batchCount: batches.length,
18432
+ durationMs: Date.now() - start,
18433
+ batches,
18434
+ llmFailures
18435
+ };
18261
18436
  }
18262
18437
 
18263
18438
  // src/lib/memory/store/local.ts
@@ -18412,7 +18587,7 @@ function createLocalMemoryStore(options) {
18412
18587
  search: (query, searchOptions) => searchVaultMemoriesWithSize(
18413
18588
  query,
18414
18589
  vaultCtx,
18415
- options.embeddingOptions,
18590
+ { ...options.embeddingOptions, taskType: "memory_recall_embed" },
18416
18591
  vaultCache,
18417
18592
  searchOptions
18418
18593
  ),
@@ -19074,6 +19249,7 @@ function createRemoteMemoryPipeline(options) {
19074
19249
  const budgetMs = search.queryEmbedTotalTimeoutMs ?? 8e3;
19075
19250
  const embed = (texts) => generateEmbeddings(texts, {
19076
19251
  ...embeddingOptions,
19252
+ taskType: mode === "retain" ? "memory_retain_embed" : "memory_recall_embed",
19077
19253
  totalTimeoutMs: budgetMs
19078
19254
  });
19079
19255
  const embedFacets = async () => {
@@ -19322,7 +19498,7 @@ function createRemoteMemoryPipeline(options) {
19322
19498
  const ranked = await rankPreparedVaultCandidates(
19323
19499
  text14,
19324
19500
  prepared,
19325
- embeddingOptions,
19501
+ { ...embeddingOptions, taskType: "memory_recall_embed" },
19326
19502
  searchOptions
19327
19503
  );
19328
19504
  return {
@@ -19344,7 +19520,7 @@ function createRemoteMemoryPipeline(options) {
19344
19520
  content,
19345
19521
  {
19346
19522
  persistence: port,
19347
- embeddingOptions,
19523
+ embeddingOptions: { ...embeddingOptions, taskType: "memory_retain_embed" },
19348
19524
  vaultCache: /* @__PURE__ */ new Map(),
19349
19525
  normalizeEmbedding: storedVector
19350
19526
  },
@@ -20072,7 +20248,8 @@ function createRemoteMemoryStore(options) {
20072
20248
  };
20073
20249
  },
20074
20250
  extractTopics: async (memoryIds, extractOptions) => {
20075
- const extractedAt = extractOptions.now ?? Date.now();
20251
+ const start = Date.now();
20252
+ const extractedAt = extractOptions.now ?? start;
20076
20253
  const skippedIds = [];
20077
20254
  const skippedReasons = /* @__PURE__ */ new Map();
20078
20255
  const skip = (id, reason) => {
@@ -20090,7 +20267,18 @@ function createRemoteMemoryStore(options) {
20090
20267
  else if (memory.is_deleted || memory.topics_user_managed) skip(id, "excluded");
20091
20268
  else inputs.push({ id, content: memory.content });
20092
20269
  }
20093
- const entitiesByMemory = await extractEntitiesForMemories(inputs, extractOptions);
20270
+ const batches = [];
20271
+ const llmFailures = /* @__PURE__ */ new Map();
20272
+ const entitiesByMemory = await extractEntitiesForMemories(inputs, {
20273
+ ...extractOptions,
20274
+ onBatch: (batch) => {
20275
+ batches.push(batch);
20276
+ if (batch.failure) {
20277
+ for (const id of batch.memoryIds) llmFailures.set(id, batch.failure);
20278
+ }
20279
+ notifyMemoryCallback(extractOptions.onBatch, batch);
20280
+ }
20281
+ });
20094
20282
  const answered = /* @__PURE__ */ new Map();
20095
20283
  for (const input of inputs) {
20096
20284
  const entities = entitiesByMemory.get(input.id);
@@ -20139,7 +20327,16 @@ function createRemoteMemoryStore(options) {
20139
20327
  getLogger().warn("[memory/remote-store] Failed to write extracted topics:", error);
20140
20328
  for (const id of answered.keys()) fail(id, "link-failed");
20141
20329
  }
20142
- return { entitiesByMemory, stampedIds, skippedIds, skippedReasons };
20330
+ return {
20331
+ entitiesByMemory,
20332
+ stampedIds,
20333
+ skippedIds,
20334
+ skippedReasons,
20335
+ batchCount: batches.length,
20336
+ durationMs: Date.now() - start,
20337
+ batches,
20338
+ llmFailures
20339
+ };
20143
20340
  },
20144
20341
  stampTopicsExtracted: async (memoryIds, extractedAt, version = TOPICS_EXTRACTION_VERSION) => {
20145
20342
  const ids = [...new Set(memoryIds)];
@@ -22150,9 +22347,13 @@ function formatVaultHits(hits) {
22150
22347
  return hits.map((h, i) => `[${i + 1}] (id: ${h.id}, similarity: ${h.score.toFixed(2)})
22151
22348
  ${h.content}`).join("\n\n");
22152
22349
  }
22153
- function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOptions) {
22350
+ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOptions, callbacks) {
22154
22351
  const limit = searchOptions?.limit ?? 5;
22155
22352
  const minSimilarity = searchOptions?.minSimilarity ?? 0.1;
22353
+ const recallEmbeddingOptions = {
22354
+ ...embeddingOptions,
22355
+ taskType: "memory_recall_embed"
22356
+ };
22156
22357
  const tuningForward = {
22157
22358
  ...searchOptions?.rerankTopN !== void 0 && { rerankTopN: searchOptions.rerankTopN },
22158
22359
  ...searchOptions?.ceWeight !== void 0 && { ceWeight: searchOptions.ceWeight },
@@ -22206,6 +22407,7 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
22206
22407
  const requestLimit = Number.isFinite(rawLimit) ? Math.min(Math.max(Math.floor(rawLimit), 1), RECALL_MAX_LIMIT) : limit;
22207
22408
  const argFolderId = args.folder_id;
22208
22409
  if (!query || typeof query !== "string") {
22410
+ notifyMemoryCallback(callbacks?.onError, new Error("A search query is required."));
22209
22411
  return "Error: A search query is required.";
22210
22412
  }
22211
22413
  try {
@@ -22213,7 +22415,11 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
22213
22415
  const budget = wantsDecompose ? "high" : searchOptions?.rerank ? "mid" : "low";
22214
22416
  const folderId = searchOptions?.folderId ?? argFolderId;
22215
22417
  if (searchOptions?.useFusion === false) {
22418
+ let candidateCount;
22216
22419
  const legacyOptions = {
22420
+ onCandidateCount: (count2) => {
22421
+ candidateCount = count2;
22422
+ },
22217
22423
  limit: requestLimit,
22218
22424
  minSimilarity,
22219
22425
  useFusion: false,
@@ -22221,13 +22427,38 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
22221
22427
  ...folderId !== void 0 && { folderId },
22222
22428
  ...searchOptions?.scopes && { scopes: searchOptions.scopes }
22223
22429
  };
22224
- const { results: legacy, embeddingsUnavailable } = "retain" in vaultCtx ? await vaultCtx.factSource.search(query, legacyOptions) : await searchVaultMemoriesWithSize(
22430
+ const start = Date.now();
22431
+ const outcome2 = "retain" in vaultCtx ? await vaultCtx.factSource.search(query, legacyOptions) : await searchVaultMemoriesWithSize(
22225
22432
  query,
22226
22433
  vaultCtx,
22227
- embeddingOptions,
22434
+ recallEmbeddingOptions,
22228
22435
  cache,
22229
22436
  legacyOptions
22230
22437
  );
22438
+ const { results: legacy, embeddingsUnavailable, vaultSize } = outcome2;
22439
+ const diagnostics = factRecallDiagnostics(
22440
+ legacy.map((m) => m.similarity),
22441
+ Date.now() - start,
22442
+ {
22443
+ vaultSize,
22444
+ minScore: minSimilarity,
22445
+ embeddingsUnavailable,
22446
+ candidateCount,
22447
+ truncated: candidateCount !== void 0 && candidateCount > requestLimit,
22448
+ decryptLast: outcome2.decryptLast,
22449
+ vaultRowsDecrypted: outcome2.rowsDecrypted,
22450
+ vaultRowsEmbedded: outcome2.rowsEmbedded,
22451
+ queryEmbedMs: outcome2.queryEmbedMs
22452
+ }
22453
+ );
22454
+ notifyMemoryCallback(callbacks?.onDiagnostics, diagnostics);
22455
+ if (diagnostics.truncated) {
22456
+ notifyMemoryCallback(callbacks?.onTruncated, {
22457
+ requestedLimit: requestLimit,
22458
+ effectiveLimit: requestLimit,
22459
+ truncatedByBudget: false
22460
+ });
22461
+ }
22231
22462
  if (legacy.length === 0) {
22232
22463
  return embeddingsUnavailable ? EMBEDDINGS_DEGRADED_EMPTY_NO_LEXICAL : "No relevant memories found in the vault.";
22233
22464
  }
@@ -22249,6 +22480,14 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
22249
22480
  {
22250
22481
  onDiagnostics: (d) => {
22251
22482
  recallDegraded = d.degraded;
22483
+ notifyMemoryCallback(callbacks?.onDiagnostics, d);
22484
+ if (d.truncated) {
22485
+ notifyMemoryCallback(callbacks?.onTruncated, {
22486
+ requestedLimit: requestLimit,
22487
+ effectiveLimit: requestLimit,
22488
+ truncatedByBudget: false
22489
+ });
22490
+ }
22252
22491
  },
22253
22492
  types: ["fact"],
22254
22493
  limit: requestLimit,
@@ -22281,6 +22520,10 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
22281
22520
 
22282
22521
  ${formatted}`;
22283
22522
  } catch (error) {
22523
+ notifyMemoryCallback(
22524
+ callbacks?.onError,
22525
+ error instanceof Error ? error : new Error(String(error))
22526
+ );
22284
22527
  const message = error instanceof Error ? error.message : "Unknown error";
22285
22528
  return `Error searching vault: ${message}`;
22286
22529
  }
@@ -23746,6 +23989,15 @@ function useChat(options) {
23746
23989
  detachControllerRef.current = null;
23747
23990
  pendingResumeRef.current = null;
23748
23991
  }, [resumable, fireCancel]);
23992
+ const cancel = useCallback3(
23993
+ (handle) => {
23994
+ if (resumable) fireCancel(handle);
23995
+ if (pendingResumeRef.current?.inferenceId === handle.inferenceId) {
23996
+ pendingResumeRef.current = null;
23997
+ }
23998
+ },
23999
+ [resumable, fireCancel]
24000
+ );
23749
24001
  const detach = useCallback3(() => {
23750
24002
  if (detachControllerRef.current) {
23751
24003
  detachControllerRef.current.abort();
@@ -24004,6 +24256,7 @@ function useChat(options) {
24004
24256
  isLoading,
24005
24257
  sendMessage,
24006
24258
  stop,
24259
+ cancel,
24007
24260
  detach,
24008
24261
  resumeStream: resumeStream2
24009
24262
  };
@@ -25403,6 +25656,7 @@ function useChatStorage(options) {
25403
25656
  isLoading,
25404
25657
  sendMessage: baseSendMessage,
25405
25658
  stop: baseStop,
25659
+ cancel: baseCancel,
25406
25660
  detach,
25407
25661
  resumeStream: baseResumeStream
25408
25662
  } = useChat({
@@ -25426,6 +25680,9 @@ function useChatStorage(options) {
25426
25680
  });
25427
25681
  const pendingResumeRef = useRef4(null);
25428
25682
  const isResumingRef = useRef4(false);
25683
+ const activeResumeRef = useRef4(null);
25684
+ const retirementRef = useRef4(null);
25685
+ const retirementStopRequestedRef = useRef4(false);
25429
25686
  const createConversation = useCallback4(
25430
25687
  async (opts) => {
25431
25688
  const { result, queued } = await writeOrQueue(
@@ -25625,6 +25882,143 @@ function useChatStorage(options) {
25625
25882
  activeToolSetsRef.current = activeToolSets;
25626
25883
  const getTokenRef = useRef4(getToken);
25627
25884
  getTokenRef.current = getToken;
25885
+ const finalizeResumedRow = useCallback4(
25886
+ async (ctx, data, wasStopped, responseDuration, viaQueue = false) => {
25887
+ const { content, thinking } = extractAssistantText(data);
25888
+ let parentMessageId = ctx.userMessageUniqueId;
25889
+ let knownIds = ctx.knownToolCallEventIds;
25890
+ if (ctx.convId && (!knownIds || !parentMessageId)) {
25891
+ let storedMessages = null;
25892
+ try {
25893
+ storedMessages = await getMessages(ctx.convId);
25894
+ } catch {
25895
+ }
25896
+ if (storedMessages) {
25897
+ if (!knownIds) {
25898
+ knownIds = /* @__PURE__ */ new Set();
25899
+ for (const msg of storedMessages) {
25900
+ if (msg.uniqueId === ctx.assistantUniqueId) continue;
25901
+ if (msg.toolCallEvents) {
25902
+ for (const evt of msg.toolCallEvents) {
25903
+ if (evt.id) knownIds.add(evt.id);
25904
+ }
25905
+ }
25906
+ }
25907
+ }
25908
+ if (!parentMessageId) {
25909
+ for (let i = storedMessages.length - 1; i >= 0; i--) {
25910
+ if (storedMessages[i].uniqueId !== ctx.assistantUniqueId) {
25911
+ parentMessageId = storedMessages[i].uniqueId;
25912
+ break;
25913
+ }
25914
+ }
25915
+ }
25916
+ }
25917
+ }
25918
+ const allToolCallEvents = getToolCallEvents(data);
25919
+ const currentTurnToolCallEvents = knownIds ? allToolCallEvents?.filter(
25920
+ (evt) => evt.id !== void 0 && evt.id !== null && !knownIds.has(evt.id)
25921
+ ) : allToolCallEvents;
25922
+ const baseSources = ctx.sources ?? [];
25923
+ const seenSourceUrls = new Set(
25924
+ baseSources.map((s) => s.url).filter((url) => !!url)
25925
+ );
25926
+ const toolEventSources = extractSourcesFromToolCallEvents(currentTurnToolCallEvents);
25927
+ const sources = [
25928
+ ...baseSources,
25929
+ ...toolEventSources.filter((s) => !s.url || !seenSourceUrls.has(s.url))
25930
+ ].filter((source) => !source.url?.includes(MCP_R2_DOMAIN));
25931
+ const rowOpts = {
25932
+ conversationId: ctx.convId,
25933
+ role: "assistant",
25934
+ content,
25935
+ model: data.model || ctx.model || "",
25936
+ imageModel: ctx.imageModel || getImageModel(data),
25937
+ usage: convertUsageToStored(data),
25938
+ responseDuration,
25939
+ sources,
25940
+ thoughtProcess: finalizeThoughtProcess(ctx.thoughtProcess),
25941
+ thinking,
25942
+ wasStopped,
25943
+ parentMessageId,
25944
+ toolCallEvents: currentTurnToolCallEvents && currentTurnToolCallEvents.length > 0 ? currentTurnToolCallEvents : void 0,
25945
+ uniqueId: ctx.assistantUniqueId
25946
+ };
25947
+ if (!viaQueue) return upsertMessageOp(storageCtx, rowOpts);
25948
+ const { result } = await writeOrQueue(
25949
+ "createMessage",
25950
+ rowOpts,
25951
+ () => upsertMessageOp(storageCtx, rowOpts),
25952
+ () => makeSyntheticStoredMessage(rowOpts),
25953
+ ctx.userMessageQueueId ? [ctx.userMessageQueueId] : []
25954
+ );
25955
+ return result;
25956
+ },
25957
+ [storageCtx, getMessages, writeOrQueue]
25958
+ );
25959
+ const retirePendingTurn = useCallback4(async () => {
25960
+ const visible = activeResumeRef.current;
25961
+ if (visible && !visible.headless) {
25962
+ baseStop();
25963
+ await visible.settled;
25964
+ }
25965
+ if (retirementRef.current) return retirementRef.current;
25966
+ const active = activeResumeRef.current;
25967
+ if (active && !active.headless) return "Resume already in progress";
25968
+ const pending = pendingResumeRef.current;
25969
+ if (!pending) return null;
25970
+ if (!pending.userMessageUniqueId) {
25971
+ if (active?.context === pending) return null;
25972
+ if (!pending.partialData) {
25973
+ pendingResumeRef.current = null;
25974
+ return null;
25975
+ }
25976
+ }
25977
+ if (active?.context === pending) return "Resume already in progress";
25978
+ pendingResumeRef.current = null;
25979
+ retirementStopRequestedRef.current = false;
25980
+ const retirement = Promise.resolve().then(async () => {
25981
+ try {
25982
+ const data = pending.partialData;
25983
+ if (data && pending.assistantUniqueId) {
25984
+ const { content, thinking } = extractAssistantText(data);
25985
+ if (content || thinking || (getToolCallEvents(data)?.length ?? 0) > 0) {
25986
+ if (walletAddress && signMessage && !hasEncryptionKey(walletAddress)) {
25987
+ try {
25988
+ await requestEncryptionKey(walletAddress, signMessage, embeddedWalletSigner);
25989
+ } catch {
25990
+ }
25991
+ }
25992
+ await finalizeResumedRow(
25993
+ pending,
25994
+ data,
25995
+ true,
25996
+ (Date.now() - pending.startTime) / 1e3,
25997
+ true
25998
+ );
25999
+ }
26000
+ }
26001
+ if (pending.handle) baseCancel(pending.handle);
26002
+ return null;
26003
+ } catch (err) {
26004
+ pendingResumeRef.current = pending;
26005
+ if (retirementStopRequestedRef.current && pending.handle) {
26006
+ baseCancel(pending.handle);
26007
+ pendingResumeRef.current = { ...pending, handle: null };
26008
+ }
26009
+ return err instanceof Error ? err.message : "Failed to store detached message";
26010
+ }
26011
+ });
26012
+ retirementRef.current = retirement;
26013
+ try {
26014
+ return await retirement;
26015
+ } finally {
26016
+ if (retirementRef.current === retirement) {
26017
+ retirementRef.current = null;
26018
+ retirementStopRequestedRef.current = false;
26019
+ }
26020
+ }
26021
+ }, [baseCancel, baseStop, finalizeResumedRow, walletAddress, signMessage, embeddedWalletSigner]);
25628
26022
  const sendMessage = useCallback4(
25629
26023
  async (args) => {
25630
26024
  const {
@@ -25676,7 +26070,11 @@ function useChatStorage(options) {
25676
26070
  maskForCall: (text14) => redactor ? redactor.maskText(text14) : text14
25677
26071
  };
25678
26072
  };
25679
- pendingResumeRef.current = null;
26073
+ const retirementError = await retirePendingTurn();
26074
+ if (retirementError) {
26075
+ if (!skipStorage) return { data: null, error: retirementError };
26076
+ getLogger().warn("[useChatStorage] detached partial not saved:", retirementError);
26077
+ }
25680
26078
  const effectiveAssistantUniqueId = assistantUniqueId ?? (resumable ? `msg_${uuidv77()}` : void 0);
25681
26079
  const embedToolText = (text14, mask, masked, token) => {
25682
26080
  const opts = {
@@ -26200,6 +26598,7 @@ function useChatStorage(options) {
26200
26598
  handle: detachedResult.resume ?? null,
26201
26599
  convId,
26202
26600
  userMessageUniqueId: storedUserMessage.uniqueId,
26601
+ userMessageQueueId: userMsgQueueId,
26203
26602
  assistantUniqueId: rowId,
26204
26603
  model,
26205
26604
  imageModel,
@@ -26426,6 +26825,7 @@ function useChatStorage(options) {
26426
26825
  getMessages,
26427
26826
  storageCtx,
26428
26827
  baseSendMessage,
26828
+ retirePendingTurn,
26429
26829
  embedMessageAsync,
26430
26830
  minContentLength,
26431
26831
  walletAddress,
@@ -26440,72 +26840,9 @@ function useChatStorage(options) {
26440
26840
  onPiiRedacted
26441
26841
  ]
26442
26842
  );
26443
- const finalizeResumedRow = useCallback4(
26444
- async (ctx, data, wasStopped, responseDuration) => {
26445
- const { content, thinking } = extractAssistantText(data);
26446
- let parentMessageId = ctx.userMessageUniqueId;
26447
- let knownIds = ctx.knownToolCallEventIds;
26448
- if (ctx.convId && (!knownIds || !parentMessageId)) {
26449
- let storedMessages = null;
26450
- try {
26451
- storedMessages = await getMessages(ctx.convId);
26452
- } catch {
26453
- }
26454
- if (storedMessages) {
26455
- if (!knownIds) {
26456
- knownIds = /* @__PURE__ */ new Set();
26457
- for (const msg of storedMessages) {
26458
- if (msg.toolCallEvents) {
26459
- for (const evt of msg.toolCallEvents) {
26460
- if (evt.id) knownIds.add(evt.id);
26461
- }
26462
- }
26463
- }
26464
- }
26465
- if (!parentMessageId) {
26466
- for (let i = storedMessages.length - 1; i >= 0; i--) {
26467
- if (storedMessages[i].uniqueId !== ctx.assistantUniqueId) {
26468
- parentMessageId = storedMessages[i].uniqueId;
26469
- break;
26470
- }
26471
- }
26472
- }
26473
- }
26474
- }
26475
- const allToolCallEvents = getToolCallEvents(data);
26476
- const currentTurnToolCallEvents = knownIds ? allToolCallEvents?.filter(
26477
- (evt) => evt.id !== void 0 && evt.id !== null && !knownIds.has(evt.id)
26478
- ) : allToolCallEvents;
26479
- const baseSources = ctx.sources ?? [];
26480
- const seenSourceUrls = new Set(
26481
- baseSources.map((s) => s.url).filter((url) => !!url)
26482
- );
26483
- const toolEventSources = extractSourcesFromToolCallEvents(currentTurnToolCallEvents);
26484
- const sources = [
26485
- ...baseSources,
26486
- ...toolEventSources.filter((s) => !s.url || !seenSourceUrls.has(s.url))
26487
- ].filter((source) => !source.url?.includes(MCP_R2_DOMAIN));
26488
- return upsertMessageOp(storageCtx, {
26489
- conversationId: ctx.convId,
26490
- role: "assistant",
26491
- content,
26492
- model: data.model || ctx.model || "",
26493
- imageModel: ctx.imageModel || getImageModel(data),
26494
- usage: convertUsageToStored(data),
26495
- responseDuration,
26496
- sources,
26497
- thoughtProcess: finalizeThoughtProcess(ctx.thoughtProcess),
26498
- thinking,
26499
- wasStopped,
26500
- parentMessageId,
26501
- uniqueId: ctx.assistantUniqueId
26502
- });
26503
- },
26504
- [storageCtx, getMessages]
26505
- );
26506
26843
  const resumeStream2 = useCallback4(
26507
26844
  async (handleOverride, opts) => {
26508
- if (isResumingRef.current) {
26845
+ if (isResumingRef.current || retirementRef.current) {
26509
26846
  return { data: null, error: "Resume already in progress", assistantMessage: null };
26510
26847
  }
26511
26848
  const pending = pendingResumeRef.current;
@@ -26534,6 +26871,7 @@ function useChatStorage(options) {
26534
26871
  if (pendingResumeRef.current === rctx) pendingResumeRef.current = null;
26535
26872
  };
26536
26873
  const safeFinalize = async (data, wasStopped, responseDuration) => {
26874
+ rctx.partialData = data;
26537
26875
  try {
26538
26876
  return { message: await finalizeResumedRow(rctx, data, wasStopped, responseDuration) };
26539
26877
  } catch (writeErr) {
@@ -26543,6 +26881,13 @@ function useChatStorage(options) {
26543
26881
  }
26544
26882
  };
26545
26883
  isResumingRef.current = true;
26884
+ let markSettled = () => {
26885
+ };
26886
+ const settled = new Promise((resolve) => {
26887
+ markSettled = resolve;
26888
+ });
26889
+ const activeResume = { context: rctx, headless: opts?.headless === true, settled };
26890
+ activeResumeRef.current = activeResume;
26546
26891
  try {
26547
26892
  const result = await baseResumeStream(handle, { headless: opts?.headless });
26548
26893
  const responseDuration = (Date.now() - rctx.startTime) / 1e3;
@@ -26619,11 +26964,17 @@ function useChatStorage(options) {
26619
26964
  throw err;
26620
26965
  } finally {
26621
26966
  isResumingRef.current = false;
26967
+ if (activeResumeRef.current === activeResume) activeResumeRef.current = null;
26968
+ markSettled();
26622
26969
  }
26623
26970
  },
26624
26971
  [baseResumeStream, currentConversationId, finalizeResumedRow, embedMessageAsync]
26625
26972
  );
26626
26973
  const stop = useCallback4(() => {
26974
+ if (retirementRef.current) {
26975
+ retirementStopRequestedRef.current = true;
26976
+ return;
26977
+ }
26627
26978
  const pending = pendingResumeRef.current;
26628
26979
  if (pending && pending.assistantUniqueId && !isResumingRef.current) {
26629
26980
  const finalize = pending;