@anuma/sdk 6.19.2 → 6.20.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.
@@ -6646,6 +6646,7 @@ async function retain(content, ctx, options = {}) {
6646
6646
  const threshold = options.autoMergeThreshold ?? DEFAULT_AUTO_MERGE_THRESHOLD;
6647
6647
  const resolvedScope = options.scope ?? DEFAULT_SCOPE;
6648
6648
  let supersedeTargetIds = [];
6649
+ let consolidationDecidedCreate = false;
6649
6650
  let supersedeContent;
6650
6651
  let prepared;
6651
6652
  if (enableAutoMerge) {
@@ -6673,8 +6674,12 @@ async function retain(content, ctx, options = {}) {
6673
6674
  const outcome = await tryConsolidate(trimmed, ctx, options, prepared);
6674
6675
  if (outcome) {
6675
6676
  if ("done" in outcome) return outcome.done;
6676
- supersedeTargetIds = outcome.supersede;
6677
- supersedeContent = outcome.content;
6677
+ if ("create" in outcome) {
6678
+ consolidationDecidedCreate = true;
6679
+ } else {
6680
+ supersedeTargetIds = outcome.supersede;
6681
+ supersedeContent = outcome.content;
6682
+ }
6678
6683
  }
6679
6684
  }
6680
6685
  if (supersedeTargetIds.length === 0) {
@@ -6724,7 +6729,14 @@ async function retain(content, ctx, options = {}) {
6724
6729
  action: "merge",
6725
6730
  memoryId: targetId,
6726
6731
  targetId,
6727
- proofCount: updated.proofCount ?? (existing.proofCount ?? 1) + 1
6732
+ proofCount: updated.proofCount ?? (existing.proofCount ?? 1) + 1,
6733
+ similarity: matches[0].similarity,
6734
+ // Stage 1 may have run and explicitly said `create`, and Stage 2
6735
+ // then found a strict-cosine match anyway. Reporting the decision
6736
+ // here is what makes that DISAGREEMENT visible — without it the
6737
+ // model's create silently vanished from the distribution whenever
6738
+ // the cosine stage won.
6739
+ ...consolidationDecidedCreate && { consolidation: "create" }
6728
6740
  };
6729
6741
  }
6730
6742
  await assertMergeTargetGoneOrThrow(ctx, targetId);
@@ -6737,12 +6749,18 @@ async function retain(content, ctx, options = {}) {
6737
6749
  const embedding = reusableQueryEmbedding ?? await generateEmbedding(contentToWrite, ctx.embeddingOptions);
6738
6750
  const embeddingModel = ctx.embeddingOptions.model ?? DEFAULT_API_EMBEDDING_MODEL;
6739
6751
  if (options.respectTombstones) {
6740
- const tombstoneId = await findTombstoneMatch(embedding, embeddingModel, resolvedScope, ctx, {
6752
+ const tombstone = await findTombstoneMatch(embedding, embeddingModel, resolvedScope, ctx, {
6741
6753
  threshold,
6742
6754
  folderId: options.folderId
6743
6755
  });
6744
- if (tombstoneId) {
6745
- return { action: "suppressed", memoryId: tombstoneId, tombstoneId, proofCount: 0 };
6756
+ if (tombstone) {
6757
+ return {
6758
+ action: "suppressed",
6759
+ memoryId: tombstone.id,
6760
+ tombstoneId: tombstone.id,
6761
+ proofCount: 0,
6762
+ similarity: tombstone.similarity
6763
+ };
6746
6764
  }
6747
6765
  }
6748
6766
  const createOpts = {
@@ -6809,7 +6827,8 @@ async function retain(content, ctx, options = {}) {
6809
6827
  action: "supersede",
6810
6828
  memoryId: created2.uniqueId,
6811
6829
  targetId: primaryTargetId,
6812
- proofCount: 1
6830
+ proofCount: 1,
6831
+ consolidation: "supersede"
6813
6832
  };
6814
6833
  }
6815
6834
  notifyConsolidationFallback(
@@ -6823,7 +6842,8 @@ async function retain(content, ctx, options = {}) {
6823
6842
  return {
6824
6843
  action: "create",
6825
6844
  memoryId: created.uniqueId,
6826
- proofCount: 1
6845
+ proofCount: 1,
6846
+ ...consolidationDecidedCreate && { consolidation: "create" }
6827
6847
  };
6828
6848
  }
6829
6849
  function unionStrings(a, b) {
@@ -6853,7 +6873,7 @@ async function findTombstoneMatch(embedding, embeddingModel, scope, ctx, opts) {
6853
6873
  bestId = row.uniqueId;
6854
6874
  }
6855
6875
  }
6856
- return bestId;
6876
+ return bestId === null ? null : { id: bestId, similarity: bestSim };
6857
6877
  }
6858
6878
  function pickEventTimeUpdate(existing, incoming) {
6859
6879
  if (incoming === void 0 || incoming === null) return void 0;
@@ -6895,7 +6915,7 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
6895
6915
  }));
6896
6916
  const { consolidateMemory: doConsolidate } = await import("./consolidate-XBYKAFKT.mjs");
6897
6917
  const decision = await doConsolidate(trimmed, candidates, consolidateOptions);
6898
- if (decision.action === "create") return null;
6918
+ if (decision.action === "create") return decision.fallbackReason ? null : { create: true };
6899
6919
  if (decision.action === "supersede" && decision.content) {
6900
6920
  const requestedIds = decision.targetIds?.length ? decision.targetIds : decision.targetId ? [decision.targetId] : [];
6901
6921
  if (requestedIds.length === 0) return null;
@@ -6954,7 +6974,8 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
6954
6974
  action: "merge",
6955
6975
  memoryId: decision.targetId,
6956
6976
  targetId: decision.targetId,
6957
- proofCount: updated.proofCount ?? (existing.proofCount ?? 1) + 1
6977
+ proofCount: updated.proofCount ?? (existing.proofCount ?? 1) + 1,
6978
+ consolidation: "noop"
6958
6979
  }
6959
6980
  };
6960
6981
  }
@@ -7007,7 +7028,8 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
7007
7028
  action: "update",
7008
7029
  memoryId: decision.targetId,
7009
7030
  targetId: decision.targetId,
7010
- proofCount: updated.proofCount ?? (existing.proofCount ?? 1) + 1
7031
+ proofCount: updated.proofCount ?? (existing.proofCount ?? 1) + 1,
7032
+ consolidation: "update"
7011
7033
  }
7012
7034
  };
7013
7035
  }
@@ -7149,6 +7171,11 @@ Extract durable user facts.`,
7149
7171
  fallbackSourceId,
7150
7172
  options.userIdentity ?? []
7151
7173
  );
7174
+ const rawCandidates = parsed.candidates;
7175
+ options.onCandidatesParsed?.({
7176
+ rawCount: Array.isArray(rawCandidates) ? rawCandidates.length : 0,
7177
+ validCount: candidates.length
7178
+ });
7152
7179
  if (!redactor) return candidates;
7153
7180
  const restored = restoreCandidates(candidates, redactor, options.userIdentity ?? []);
7154
7181
  if (candidates.length > 0 && restored.length === 0) {
@@ -7185,8 +7212,12 @@ async function extractAndRetain(messages, retainCtx, options) {
7185
7212
  let exhaustedEmpty = false;
7186
7213
  let exhaustedFailure;
7187
7214
  let droppedAfterRedaction = false;
7215
+ let rawCandidateCount = 0;
7216
+ let validCandidateCount = 0;
7188
7217
  const callerOnExhaustedEmpty = options.extract.onExhaustedEmpty;
7189
7218
  const callerOnCandidatesDropped = options.extract.onCandidatesDropped;
7219
+ const callerOnCandidatesParsed = options.extract.onCandidatesParsed;
7220
+ const tExtract = Date.now();
7190
7221
  const candidates = await extractFacts(messages, {
7191
7222
  ...options.extract,
7192
7223
  onExhaustedEmpty: (failure) => {
@@ -7197,8 +7228,14 @@ async function extractAndRetain(messages, retainCtx, options) {
7197
7228
  onCandidatesDropped: () => {
7198
7229
  droppedAfterRedaction = true;
7199
7230
  callerOnCandidatesDropped?.();
7231
+ },
7232
+ onCandidatesParsed: (stats) => {
7233
+ rawCandidateCount = stats.rawCount;
7234
+ validCandidateCount = stats.validCount;
7235
+ callerOnCandidatesParsed?.(stats);
7200
7236
  }
7201
7237
  });
7238
+ const extractMs = Date.now() - tExtract;
7202
7239
  const filtered = candidates.filter((c) => c.confidence >= minConfidence);
7203
7240
  const log = getLogger();
7204
7241
  const screened = screenCandidatesForInjection(filtered);
@@ -7246,6 +7283,7 @@ async function extractAndRetain(messages, retainCtx, options) {
7246
7283
  const results = [];
7247
7284
  const quarantinedInfo = [];
7248
7285
  let failedWrites = 0;
7286
+ const tRetain = Date.now();
7249
7287
  for (const { candidate, isQuarantined, reason, signature } of toRetain) {
7250
7288
  try {
7251
7289
  const result = await retain(candidate.content, retainCtx, {
@@ -7312,6 +7350,21 @@ async function extractAndRetain(messages, retainCtx, options) {
7312
7350
  failedCount: failedWrites,
7313
7351
  outcome,
7314
7352
  quarantined: quarantinedInfo,
7353
+ funnel: {
7354
+ rawCandidateCount,
7355
+ validCandidateCount,
7356
+ afterRedactionCount: candidates.length,
7357
+ aboveConfidenceCount: filtered.length,
7358
+ // The PERSISTED quarantines (`quarantinedInfo`), not the screened list: a
7359
+ // screened candidate whose retain() throws is counted in `failedCount`,
7360
+ // and counting it here too would double it and break the identity below.
7361
+ // This also keeps the count equal to `quarantined.length` on the result.
7362
+ quarantinedCount: quarantinedInfo.length,
7363
+ retainedCount: results.length,
7364
+ failedCount: failedWrites
7365
+ },
7366
+ timings: { extractMs, retainMs: Date.now() - tRetain },
7367
+ model: options.extract.model ?? DEFAULT_EXTRACTION_MODEL,
7315
7368
  // Only meaningful alongside `outcome: "empty-after-retry"`. Returned as well
7316
7369
  // as pushed through the callback so a consumer that only inspects the result
7317
7370
  // (rather than wiring a hook) can still report WHY.
@@ -7565,27 +7618,23 @@ function createAutoExtractor(options) {
7565
7618
  const t0 = Date.now();
7566
7619
  void (async () => {
7567
7620
  try {
7568
- const { candidates, results, failedCount, outcome, failure } = await extractAndRetain(
7569
- window,
7570
- retainCtx,
7571
- {
7572
- extract,
7573
- ...options.minConfidence !== void 0 && { minConfidence: options.minConfidence },
7574
- ...options.entityCtx !== void 0 && { entityCtx: options.entityCtx },
7575
- ...options.scope !== void 0 && { scope: options.scope },
7576
- ...options.folderId !== void 0 && { folderId: options.folderId },
7577
- ...consolidateOptions !== void 0 && { consolidateOptions },
7578
- ...injectionClassifierOptions !== void 0 && {
7579
- injectionClassifier: injectionClassifierOptions
7580
- },
7581
- ...options.onCandidateFailed && {
7582
- onCandidateFailed: (candidate, error) => options.onCandidateFailed?.({ candidate, error, conversationId })
7583
- },
7584
- ...options.onMemoryQuarantined && {
7585
- onQuarantined: (info) => options.onMemoryQuarantined?.({ ...info, conversationId })
7586
- }
7621
+ const { candidates, results, failedCount, outcome, failure, funnel, timings, model } = await extractAndRetain(window, retainCtx, {
7622
+ extract,
7623
+ ...options.minConfidence !== void 0 && { minConfidence: options.minConfidence },
7624
+ ...options.entityCtx !== void 0 && { entityCtx: options.entityCtx },
7625
+ ...options.scope !== void 0 && { scope: options.scope },
7626
+ ...options.folderId !== void 0 && { folderId: options.folderId },
7627
+ ...consolidateOptions !== void 0 && { consolidateOptions },
7628
+ ...injectionClassifierOptions !== void 0 && {
7629
+ injectionClassifier: injectionClassifierOptions
7630
+ },
7631
+ ...options.onCandidateFailed && {
7632
+ onCandidateFailed: (candidate, error) => options.onCandidateFailed?.({ candidate, error, conversationId })
7633
+ },
7634
+ ...options.onMemoryQuarantined && {
7635
+ onQuarantined: (info) => options.onMemoryQuarantined?.({ ...info, conversationId })
7587
7636
  }
7588
- );
7637
+ });
7589
7638
  if (outcome !== "empty-after-retry") {
7590
7639
  const advancedTo = window[window.length - 1].id;
7591
7640
  stateFor(conversationId).watermark = advancedTo;
@@ -7606,6 +7655,9 @@ function createAutoExtractor(options) {
7606
7655
  durationMs: Date.now() - t0,
7607
7656
  conversationId,
7608
7657
  outcome,
7658
+ funnel,
7659
+ timings,
7660
+ model,
7609
7661
  // Only set alongside `outcome: "empty-after-retry"`. Spread so the key
7610
7662
  // is absent rather than explicitly undefined on a healthy turn —
7611
7663
  // analytics backends store an explicit undefined as a real value.
@@ -17683,6 +17683,7 @@ async function retain(content, ctx, options = {}) {
17683
17683
  const threshold = options.autoMergeThreshold ?? DEFAULT_AUTO_MERGE_THRESHOLD;
17684
17684
  const resolvedScope = options.scope ?? DEFAULT_SCOPE;
17685
17685
  let supersedeTargetIds = [];
17686
+ let consolidationDecidedCreate = false;
17686
17687
  let supersedeContent;
17687
17688
  let prepared;
17688
17689
  if (enableAutoMerge) {
@@ -17710,8 +17711,12 @@ async function retain(content, ctx, options = {}) {
17710
17711
  const outcome = await tryConsolidate(trimmed, ctx, options, prepared);
17711
17712
  if (outcome) {
17712
17713
  if ("done" in outcome) return outcome.done;
17713
- supersedeTargetIds = outcome.supersede;
17714
- supersedeContent = outcome.content;
17714
+ if ("create" in outcome) {
17715
+ consolidationDecidedCreate = true;
17716
+ } else {
17717
+ supersedeTargetIds = outcome.supersede;
17718
+ supersedeContent = outcome.content;
17719
+ }
17715
17720
  }
17716
17721
  }
17717
17722
  if (supersedeTargetIds.length === 0) {
@@ -17761,7 +17766,14 @@ async function retain(content, ctx, options = {}) {
17761
17766
  action: "merge",
17762
17767
  memoryId: targetId,
17763
17768
  targetId,
17764
- proofCount: updated.proofCount ?? (existing.proofCount ?? 1) + 1
17769
+ proofCount: updated.proofCount ?? (existing.proofCount ?? 1) + 1,
17770
+ similarity: matches[0].similarity,
17771
+ // Stage 1 may have run and explicitly said `create`, and Stage 2
17772
+ // then found a strict-cosine match anyway. Reporting the decision
17773
+ // here is what makes that DISAGREEMENT visible — without it the
17774
+ // model's create silently vanished from the distribution whenever
17775
+ // the cosine stage won.
17776
+ ...consolidationDecidedCreate && { consolidation: "create" }
17765
17777
  };
17766
17778
  }
17767
17779
  await assertMergeTargetGoneOrThrow(ctx, targetId);
@@ -17774,12 +17786,18 @@ async function retain(content, ctx, options = {}) {
17774
17786
  const embedding = reusableQueryEmbedding ?? await generateEmbedding(contentToWrite, ctx.embeddingOptions);
17775
17787
  const embeddingModel = ctx.embeddingOptions.model ?? DEFAULT_API_EMBEDDING_MODEL;
17776
17788
  if (options.respectTombstones) {
17777
- const tombstoneId = await findTombstoneMatch(embedding, embeddingModel, resolvedScope, ctx, {
17789
+ const tombstone = await findTombstoneMatch(embedding, embeddingModel, resolvedScope, ctx, {
17778
17790
  threshold,
17779
17791
  folderId: options.folderId
17780
17792
  });
17781
- if (tombstoneId) {
17782
- return { action: "suppressed", memoryId: tombstoneId, tombstoneId, proofCount: 0 };
17793
+ if (tombstone) {
17794
+ return {
17795
+ action: "suppressed",
17796
+ memoryId: tombstone.id,
17797
+ tombstoneId: tombstone.id,
17798
+ proofCount: 0,
17799
+ similarity: tombstone.similarity
17800
+ };
17783
17801
  }
17784
17802
  }
17785
17803
  const createOpts = {
@@ -17846,7 +17864,8 @@ async function retain(content, ctx, options = {}) {
17846
17864
  action: "supersede",
17847
17865
  memoryId: created2.uniqueId,
17848
17866
  targetId: primaryTargetId,
17849
- proofCount: 1
17867
+ proofCount: 1,
17868
+ consolidation: "supersede"
17850
17869
  };
17851
17870
  }
17852
17871
  notifyConsolidationFallback(
@@ -17860,7 +17879,8 @@ async function retain(content, ctx, options = {}) {
17860
17879
  return {
17861
17880
  action: "create",
17862
17881
  memoryId: created.uniqueId,
17863
- proofCount: 1
17882
+ proofCount: 1,
17883
+ ...consolidationDecidedCreate && { consolidation: "create" }
17864
17884
  };
17865
17885
  }
17866
17886
  function unionStrings(a, b) {
@@ -17890,7 +17910,7 @@ async function findTombstoneMatch(embedding, embeddingModel, scope, ctx, opts) {
17890
17910
  bestId = row.uniqueId;
17891
17911
  }
17892
17912
  }
17893
- return bestId;
17913
+ return bestId === null ? null : { id: bestId, similarity: bestSim };
17894
17914
  }
17895
17915
  function pickEventTimeUpdate(existing, incoming) {
17896
17916
  if (incoming === void 0 || incoming === null) return void 0;
@@ -17932,7 +17952,7 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
17932
17952
  }));
17933
17953
  const { consolidateMemory: doConsolidate } = await Promise.resolve().then(() => (init_consolidate(), consolidate_exports));
17934
17954
  const decision = await doConsolidate(trimmed, candidates, consolidateOptions);
17935
- if (decision.action === "create") return null;
17955
+ if (decision.action === "create") return decision.fallbackReason ? null : { create: true };
17936
17956
  if (decision.action === "supersede" && decision.content) {
17937
17957
  const requestedIds = decision.targetIds?.length ? decision.targetIds : decision.targetId ? [decision.targetId] : [];
17938
17958
  if (requestedIds.length === 0) return null;
@@ -17991,7 +18011,8 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
17991
18011
  action: "merge",
17992
18012
  memoryId: decision.targetId,
17993
18013
  targetId: decision.targetId,
17994
- proofCount: updated.proofCount ?? (existing.proofCount ?? 1) + 1
18014
+ proofCount: updated.proofCount ?? (existing.proofCount ?? 1) + 1,
18015
+ consolidation: "noop"
17995
18016
  }
17996
18017
  };
17997
18018
  }
@@ -18044,7 +18065,8 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
18044
18065
  action: "update",
18045
18066
  memoryId: decision.targetId,
18046
18067
  targetId: decision.targetId,
18047
- proofCount: updated.proofCount ?? (existing.proofCount ?? 1) + 1
18068
+ proofCount: updated.proofCount ?? (existing.proofCount ?? 1) + 1,
18069
+ consolidation: "update"
18048
18070
  }
18049
18071
  };
18050
18072
  }
@@ -18186,6 +18208,11 @@ Extract durable user facts.`,
18186
18208
  fallbackSourceId,
18187
18209
  options.userIdentity ?? []
18188
18210
  );
18211
+ const rawCandidates = parsed.candidates;
18212
+ options.onCandidatesParsed?.({
18213
+ rawCount: Array.isArray(rawCandidates) ? rawCandidates.length : 0,
18214
+ validCount: candidates.length
18215
+ });
18189
18216
  if (!redactor) return candidates;
18190
18217
  const restored = restoreCandidates(candidates, redactor, options.userIdentity ?? []);
18191
18218
  if (candidates.length > 0 && restored.length === 0) {
@@ -18222,8 +18249,12 @@ async function extractAndRetain(messages, retainCtx, options) {
18222
18249
  let exhaustedEmpty = false;
18223
18250
  let exhaustedFailure;
18224
18251
  let droppedAfterRedaction = false;
18252
+ let rawCandidateCount = 0;
18253
+ let validCandidateCount = 0;
18225
18254
  const callerOnExhaustedEmpty = options.extract.onExhaustedEmpty;
18226
18255
  const callerOnCandidatesDropped = options.extract.onCandidatesDropped;
18256
+ const callerOnCandidatesParsed = options.extract.onCandidatesParsed;
18257
+ const tExtract = Date.now();
18227
18258
  const candidates = await extractFacts(messages, {
18228
18259
  ...options.extract,
18229
18260
  onExhaustedEmpty: (failure) => {
@@ -18234,8 +18265,14 @@ async function extractAndRetain(messages, retainCtx, options) {
18234
18265
  onCandidatesDropped: () => {
18235
18266
  droppedAfterRedaction = true;
18236
18267
  callerOnCandidatesDropped?.();
18268
+ },
18269
+ onCandidatesParsed: (stats) => {
18270
+ rawCandidateCount = stats.rawCount;
18271
+ validCandidateCount = stats.validCount;
18272
+ callerOnCandidatesParsed?.(stats);
18237
18273
  }
18238
18274
  });
18275
+ const extractMs = Date.now() - tExtract;
18239
18276
  const filtered = candidates.filter((c) => c.confidence >= minConfidence);
18240
18277
  const log = getLogger();
18241
18278
  const screened = screenCandidatesForInjection(filtered);
@@ -18283,6 +18320,7 @@ async function extractAndRetain(messages, retainCtx, options) {
18283
18320
  const results = [];
18284
18321
  const quarantinedInfo = [];
18285
18322
  let failedWrites = 0;
18323
+ const tRetain = Date.now();
18286
18324
  for (const { candidate, isQuarantined, reason, signature } of toRetain) {
18287
18325
  try {
18288
18326
  const result = await retain(candidate.content, retainCtx, {
@@ -18349,6 +18387,21 @@ async function extractAndRetain(messages, retainCtx, options) {
18349
18387
  failedCount: failedWrites,
18350
18388
  outcome,
18351
18389
  quarantined: quarantinedInfo,
18390
+ funnel: {
18391
+ rawCandidateCount,
18392
+ validCandidateCount,
18393
+ afterRedactionCount: candidates.length,
18394
+ aboveConfidenceCount: filtered.length,
18395
+ // The PERSISTED quarantines (`quarantinedInfo`), not the screened list: a
18396
+ // screened candidate whose retain() throws is counted in `failedCount`,
18397
+ // and counting it here too would double it and break the identity below.
18398
+ // This also keeps the count equal to `quarantined.length` on the result.
18399
+ quarantinedCount: quarantinedInfo.length,
18400
+ retainedCount: results.length,
18401
+ failedCount: failedWrites
18402
+ },
18403
+ timings: { extractMs, retainMs: Date.now() - tRetain },
18404
+ model: options.extract.model ?? DEFAULT_EXTRACTION_MODEL,
18352
18405
  // Only meaningful alongside `outcome: "empty-after-retry"`. Returned as well
18353
18406
  // as pushed through the callback so a consumer that only inspects the result
18354
18407
  // (rather than wiring a hook) can still report WHY.
@@ -18604,27 +18657,23 @@ function createAutoExtractor(options) {
18604
18657
  const t0 = Date.now();
18605
18658
  void (async () => {
18606
18659
  try {
18607
- const { candidates, results, failedCount, outcome, failure } = await extractAndRetain(
18608
- window2,
18609
- retainCtx,
18610
- {
18611
- extract,
18612
- ...options.minConfidence !== void 0 && { minConfidence: options.minConfidence },
18613
- ...options.entityCtx !== void 0 && { entityCtx: options.entityCtx },
18614
- ...options.scope !== void 0 && { scope: options.scope },
18615
- ...options.folderId !== void 0 && { folderId: options.folderId },
18616
- ...consolidateOptions !== void 0 && { consolidateOptions },
18617
- ...injectionClassifierOptions !== void 0 && {
18618
- injectionClassifier: injectionClassifierOptions
18619
- },
18620
- ...options.onCandidateFailed && {
18621
- onCandidateFailed: (candidate, error) => options.onCandidateFailed?.({ candidate, error, conversationId })
18622
- },
18623
- ...options.onMemoryQuarantined && {
18624
- onQuarantined: (info) => options.onMemoryQuarantined?.({ ...info, conversationId })
18625
- }
18660
+ const { candidates, results, failedCount, outcome, failure, funnel, timings, model } = await extractAndRetain(window2, retainCtx, {
18661
+ extract,
18662
+ ...options.minConfidence !== void 0 && { minConfidence: options.minConfidence },
18663
+ ...options.entityCtx !== void 0 && { entityCtx: options.entityCtx },
18664
+ ...options.scope !== void 0 && { scope: options.scope },
18665
+ ...options.folderId !== void 0 && { folderId: options.folderId },
18666
+ ...consolidateOptions !== void 0 && { consolidateOptions },
18667
+ ...injectionClassifierOptions !== void 0 && {
18668
+ injectionClassifier: injectionClassifierOptions
18669
+ },
18670
+ ...options.onCandidateFailed && {
18671
+ onCandidateFailed: (candidate, error) => options.onCandidateFailed?.({ candidate, error, conversationId })
18672
+ },
18673
+ ...options.onMemoryQuarantined && {
18674
+ onQuarantined: (info) => options.onMemoryQuarantined?.({ ...info, conversationId })
18626
18675
  }
18627
- );
18676
+ });
18628
18677
  if (outcome !== "empty-after-retry") {
18629
18678
  const advancedTo = window2[window2.length - 1].id;
18630
18679
  stateFor(conversationId).watermark = advancedTo;
@@ -18645,6 +18694,9 @@ function createAutoExtractor(options) {
18645
18694
  durationMs: Date.now() - t0,
18646
18695
  conversationId,
18647
18696
  outcome,
18697
+ funnel,
18698
+ timings,
18699
+ model,
18648
18700
  // Only set alongside `outcome: "empty-after-retry"`. Spread so the key
18649
18701
  // is absent rather than explicitly undefined on a healthy turn —
18650
18702
  // analytics backends store an explicit undefined as a real value.