@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.
@@ -7464,6 +7464,7 @@ async function retain(content, ctx, options = {}) {
7464
7464
  const threshold = options.autoMergeThreshold ?? DEFAULT_AUTO_MERGE_THRESHOLD;
7465
7465
  const resolvedScope = options.scope ?? DEFAULT_SCOPE;
7466
7466
  let supersedeTargetIds = [];
7467
+ let consolidationDecidedCreate = false;
7467
7468
  let supersedeContent;
7468
7469
  let prepared;
7469
7470
  if (enableAutoMerge) {
@@ -7491,8 +7492,12 @@ async function retain(content, ctx, options = {}) {
7491
7492
  const outcome = await tryConsolidate(trimmed, ctx, options, prepared);
7492
7493
  if (outcome) {
7493
7494
  if ("done" in outcome) return outcome.done;
7494
- supersedeTargetIds = outcome.supersede;
7495
- supersedeContent = outcome.content;
7495
+ if ("create" in outcome) {
7496
+ consolidationDecidedCreate = true;
7497
+ } else {
7498
+ supersedeTargetIds = outcome.supersede;
7499
+ supersedeContent = outcome.content;
7500
+ }
7496
7501
  }
7497
7502
  }
7498
7503
  if (supersedeTargetIds.length === 0) {
@@ -7542,7 +7547,14 @@ async function retain(content, ctx, options = {}) {
7542
7547
  action: "merge",
7543
7548
  memoryId: targetId,
7544
7549
  targetId,
7545
- proofCount: updated.proofCount ?? (existing.proofCount ?? 1) + 1
7550
+ proofCount: updated.proofCount ?? (existing.proofCount ?? 1) + 1,
7551
+ similarity: matches[0].similarity,
7552
+ // Stage 1 may have run and explicitly said `create`, and Stage 2
7553
+ // then found a strict-cosine match anyway. Reporting the decision
7554
+ // here is what makes that DISAGREEMENT visible — without it the
7555
+ // model's create silently vanished from the distribution whenever
7556
+ // the cosine stage won.
7557
+ ...consolidationDecidedCreate && { consolidation: "create" }
7546
7558
  };
7547
7559
  }
7548
7560
  await assertMergeTargetGoneOrThrow(ctx, targetId);
@@ -7555,12 +7567,18 @@ async function retain(content, ctx, options = {}) {
7555
7567
  const embedding = reusableQueryEmbedding ?? await generateEmbedding(contentToWrite, ctx.embeddingOptions);
7556
7568
  const embeddingModel = ctx.embeddingOptions.model ?? DEFAULT_API_EMBEDDING_MODEL;
7557
7569
  if (options.respectTombstones) {
7558
- const tombstoneId = await findTombstoneMatch(embedding, embeddingModel, resolvedScope, ctx, {
7570
+ const tombstone = await findTombstoneMatch(embedding, embeddingModel, resolvedScope, ctx, {
7559
7571
  threshold,
7560
7572
  folderId: options.folderId
7561
7573
  });
7562
- if (tombstoneId) {
7563
- return { action: "suppressed", memoryId: tombstoneId, tombstoneId, proofCount: 0 };
7574
+ if (tombstone) {
7575
+ return {
7576
+ action: "suppressed",
7577
+ memoryId: tombstone.id,
7578
+ tombstoneId: tombstone.id,
7579
+ proofCount: 0,
7580
+ similarity: tombstone.similarity
7581
+ };
7564
7582
  }
7565
7583
  }
7566
7584
  const createOpts = {
@@ -7627,7 +7645,8 @@ async function retain(content, ctx, options = {}) {
7627
7645
  action: "supersede",
7628
7646
  memoryId: created2.uniqueId,
7629
7647
  targetId: primaryTargetId,
7630
- proofCount: 1
7648
+ proofCount: 1,
7649
+ consolidation: "supersede"
7631
7650
  };
7632
7651
  }
7633
7652
  notifyConsolidationFallback(
@@ -7641,7 +7660,8 @@ async function retain(content, ctx, options = {}) {
7641
7660
  return {
7642
7661
  action: "create",
7643
7662
  memoryId: created.uniqueId,
7644
- proofCount: 1
7663
+ proofCount: 1,
7664
+ ...consolidationDecidedCreate && { consolidation: "create" }
7645
7665
  };
7646
7666
  }
7647
7667
  function unionStrings(a, b) {
@@ -7671,7 +7691,7 @@ async function findTombstoneMatch(embedding, embeddingModel, scope, ctx, opts) {
7671
7691
  bestId = row.uniqueId;
7672
7692
  }
7673
7693
  }
7674
- return bestId;
7694
+ return bestId === null ? null : { id: bestId, similarity: bestSim };
7675
7695
  }
7676
7696
  function pickEventTimeUpdate(existing, incoming) {
7677
7697
  if (incoming === void 0 || incoming === null) return void 0;
@@ -7713,7 +7733,7 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
7713
7733
  }));
7714
7734
  const { consolidateMemory: doConsolidate } = await import("./consolidate-URHGJT7I.mjs");
7715
7735
  const decision = await doConsolidate(trimmed, candidates, consolidateOptions);
7716
- if (decision.action === "create") return null;
7736
+ if (decision.action === "create") return decision.fallbackReason ? null : { create: true };
7717
7737
  if (decision.action === "supersede" && decision.content) {
7718
7738
  const requestedIds = decision.targetIds?.length ? decision.targetIds : decision.targetId ? [decision.targetId] : [];
7719
7739
  if (requestedIds.length === 0) return null;
@@ -7772,7 +7792,8 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
7772
7792
  action: "merge",
7773
7793
  memoryId: decision.targetId,
7774
7794
  targetId: decision.targetId,
7775
- proofCount: updated.proofCount ?? (existing.proofCount ?? 1) + 1
7795
+ proofCount: updated.proofCount ?? (existing.proofCount ?? 1) + 1,
7796
+ consolidation: "noop"
7776
7797
  }
7777
7798
  };
7778
7799
  }
@@ -7825,7 +7846,8 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
7825
7846
  action: "update",
7826
7847
  memoryId: decision.targetId,
7827
7848
  targetId: decision.targetId,
7828
- proofCount: updated.proofCount ?? (existing.proofCount ?? 1) + 1
7849
+ proofCount: updated.proofCount ?? (existing.proofCount ?? 1) + 1,
7850
+ consolidation: "update"
7829
7851
  }
7830
7852
  };
7831
7853
  }
@@ -7967,6 +7989,11 @@ Extract durable user facts.`,
7967
7989
  fallbackSourceId,
7968
7990
  options.userIdentity ?? []
7969
7991
  );
7992
+ const rawCandidates = parsed.candidates;
7993
+ options.onCandidatesParsed?.({
7994
+ rawCount: Array.isArray(rawCandidates) ? rawCandidates.length : 0,
7995
+ validCount: candidates.length
7996
+ });
7970
7997
  if (!redactor) return candidates;
7971
7998
  const restored = restoreCandidates(candidates, redactor, options.userIdentity ?? []);
7972
7999
  if (candidates.length > 0 && restored.length === 0) {
@@ -8003,8 +8030,12 @@ async function extractAndRetain(messages, retainCtx, options) {
8003
8030
  let exhaustedEmpty = false;
8004
8031
  let exhaustedFailure;
8005
8032
  let droppedAfterRedaction = false;
8033
+ let rawCandidateCount = 0;
8034
+ let validCandidateCount = 0;
8006
8035
  const callerOnExhaustedEmpty = options.extract.onExhaustedEmpty;
8007
8036
  const callerOnCandidatesDropped = options.extract.onCandidatesDropped;
8037
+ const callerOnCandidatesParsed = options.extract.onCandidatesParsed;
8038
+ const tExtract = Date.now();
8008
8039
  const candidates = await extractFacts(messages, {
8009
8040
  ...options.extract,
8010
8041
  onExhaustedEmpty: (failure) => {
@@ -8015,8 +8046,14 @@ async function extractAndRetain(messages, retainCtx, options) {
8015
8046
  onCandidatesDropped: () => {
8016
8047
  droppedAfterRedaction = true;
8017
8048
  callerOnCandidatesDropped?.();
8049
+ },
8050
+ onCandidatesParsed: (stats) => {
8051
+ rawCandidateCount = stats.rawCount;
8052
+ validCandidateCount = stats.validCount;
8053
+ callerOnCandidatesParsed?.(stats);
8018
8054
  }
8019
8055
  });
8056
+ const extractMs = Date.now() - tExtract;
8020
8057
  const filtered = candidates.filter((c) => c.confidence >= minConfidence);
8021
8058
  const log = getLogger();
8022
8059
  const screened = screenCandidatesForInjection(filtered);
@@ -8064,6 +8101,7 @@ async function extractAndRetain(messages, retainCtx, options) {
8064
8101
  const results = [];
8065
8102
  const quarantinedInfo = [];
8066
8103
  let failedWrites = 0;
8104
+ const tRetain = Date.now();
8067
8105
  for (const { candidate, isQuarantined, reason, signature } of toRetain) {
8068
8106
  try {
8069
8107
  const result = await retain(candidate.content, retainCtx, {
@@ -8130,6 +8168,21 @@ async function extractAndRetain(messages, retainCtx, options) {
8130
8168
  failedCount: failedWrites,
8131
8169
  outcome,
8132
8170
  quarantined: quarantinedInfo,
8171
+ funnel: {
8172
+ rawCandidateCount,
8173
+ validCandidateCount,
8174
+ afterRedactionCount: candidates.length,
8175
+ aboveConfidenceCount: filtered.length,
8176
+ // The PERSISTED quarantines (`quarantinedInfo`), not the screened list: a
8177
+ // screened candidate whose retain() throws is counted in `failedCount`,
8178
+ // and counting it here too would double it and break the identity below.
8179
+ // This also keeps the count equal to `quarantined.length` on the result.
8180
+ quarantinedCount: quarantinedInfo.length,
8181
+ retainedCount: results.length,
8182
+ failedCount: failedWrites
8183
+ },
8184
+ timings: { extractMs, retainMs: Date.now() - tRetain },
8185
+ model: options.extract.model ?? DEFAULT_EXTRACTION_MODEL,
8133
8186
  // Only meaningful alongside `outcome: "empty-after-retry"`. Returned as well
8134
8187
  // as pushed through the callback so a consumer that only inspects the result
8135
8188
  // (rather than wiring a hook) can still report WHY.
@@ -8383,27 +8436,23 @@ function createAutoExtractor(options) {
8383
8436
  const t0 = Date.now();
8384
8437
  void (async () => {
8385
8438
  try {
8386
- const { candidates, results, failedCount, outcome, failure } = await extractAndRetain(
8387
- window2,
8388
- retainCtx,
8389
- {
8390
- extract,
8391
- ...options.minConfidence !== void 0 && { minConfidence: options.minConfidence },
8392
- ...options.entityCtx !== void 0 && { entityCtx: options.entityCtx },
8393
- ...options.scope !== void 0 && { scope: options.scope },
8394
- ...options.folderId !== void 0 && { folderId: options.folderId },
8395
- ...consolidateOptions !== void 0 && { consolidateOptions },
8396
- ...injectionClassifierOptions !== void 0 && {
8397
- injectionClassifier: injectionClassifierOptions
8398
- },
8399
- ...options.onCandidateFailed && {
8400
- onCandidateFailed: (candidate, error) => options.onCandidateFailed?.({ candidate, error, conversationId })
8401
- },
8402
- ...options.onMemoryQuarantined && {
8403
- onQuarantined: (info) => options.onMemoryQuarantined?.({ ...info, conversationId })
8404
- }
8439
+ const { candidates, results, failedCount, outcome, failure, funnel, timings, model } = await extractAndRetain(window2, retainCtx, {
8440
+ extract,
8441
+ ...options.minConfidence !== void 0 && { minConfidence: options.minConfidence },
8442
+ ...options.entityCtx !== void 0 && { entityCtx: options.entityCtx },
8443
+ ...options.scope !== void 0 && { scope: options.scope },
8444
+ ...options.folderId !== void 0 && { folderId: options.folderId },
8445
+ ...consolidateOptions !== void 0 && { consolidateOptions },
8446
+ ...injectionClassifierOptions !== void 0 && {
8447
+ injectionClassifier: injectionClassifierOptions
8448
+ },
8449
+ ...options.onCandidateFailed && {
8450
+ onCandidateFailed: (candidate, error) => options.onCandidateFailed?.({ candidate, error, conversationId })
8451
+ },
8452
+ ...options.onMemoryQuarantined && {
8453
+ onQuarantined: (info) => options.onMemoryQuarantined?.({ ...info, conversationId })
8405
8454
  }
8406
- );
8455
+ });
8407
8456
  if (outcome !== "empty-after-retry") {
8408
8457
  const advancedTo = window2[window2.length - 1].id;
8409
8458
  stateFor(conversationId).watermark = advancedTo;
@@ -8424,6 +8473,9 @@ function createAutoExtractor(options) {
8424
8473
  durationMs: Date.now() - t0,
8425
8474
  conversationId,
8426
8475
  outcome,
8476
+ funnel,
8477
+ timings,
8478
+ model,
8427
8479
  // Only set alongside `outcome: "empty-after-retry"`. Spread so the key
8428
8480
  // is absent rather than explicitly undefined on a healthy turn —
8429
8481
  // analytics backends store an explicit undefined as a real value.
@@ -29296,6 +29296,7 @@ async function retain(content, ctx, options = {}) {
29296
29296
  const threshold = options.autoMergeThreshold ?? DEFAULT_AUTO_MERGE_THRESHOLD;
29297
29297
  const resolvedScope = options.scope ?? DEFAULT_SCOPE;
29298
29298
  let supersedeTargetIds = [];
29299
+ let consolidationDecidedCreate = false;
29299
29300
  let supersedeContent;
29300
29301
  let prepared;
29301
29302
  if (enableAutoMerge) {
@@ -29323,8 +29324,12 @@ async function retain(content, ctx, options = {}) {
29323
29324
  const outcome = await tryConsolidate(trimmed, ctx, options, prepared);
29324
29325
  if (outcome) {
29325
29326
  if ("done" in outcome) return outcome.done;
29326
- supersedeTargetIds = outcome.supersede;
29327
- supersedeContent = outcome.content;
29327
+ if ("create" in outcome) {
29328
+ consolidationDecidedCreate = true;
29329
+ } else {
29330
+ supersedeTargetIds = outcome.supersede;
29331
+ supersedeContent = outcome.content;
29332
+ }
29328
29333
  }
29329
29334
  }
29330
29335
  if (supersedeTargetIds.length === 0) {
@@ -29374,7 +29379,14 @@ async function retain(content, ctx, options = {}) {
29374
29379
  action: "merge",
29375
29380
  memoryId: targetId,
29376
29381
  targetId,
29377
- proofCount: updated.proofCount ?? (existing.proofCount ?? 1) + 1
29382
+ proofCount: updated.proofCount ?? (existing.proofCount ?? 1) + 1,
29383
+ similarity: matches[0].similarity,
29384
+ // Stage 1 may have run and explicitly said `create`, and Stage 2
29385
+ // then found a strict-cosine match anyway. Reporting the decision
29386
+ // here is what makes that DISAGREEMENT visible — without it the
29387
+ // model's create silently vanished from the distribution whenever
29388
+ // the cosine stage won.
29389
+ ...consolidationDecidedCreate && { consolidation: "create" }
29378
29390
  };
29379
29391
  }
29380
29392
  await assertMergeTargetGoneOrThrow(ctx, targetId);
@@ -29387,12 +29399,18 @@ async function retain(content, ctx, options = {}) {
29387
29399
  const embedding = reusableQueryEmbedding ?? await generateEmbedding(contentToWrite, ctx.embeddingOptions);
29388
29400
  const embeddingModel = ctx.embeddingOptions.model ?? DEFAULT_API_EMBEDDING_MODEL;
29389
29401
  if (options.respectTombstones) {
29390
- const tombstoneId = await findTombstoneMatch(embedding, embeddingModel, resolvedScope, ctx, {
29402
+ const tombstone = await findTombstoneMatch(embedding, embeddingModel, resolvedScope, ctx, {
29391
29403
  threshold,
29392
29404
  folderId: options.folderId
29393
29405
  });
29394
- if (tombstoneId) {
29395
- return { action: "suppressed", memoryId: tombstoneId, tombstoneId, proofCount: 0 };
29406
+ if (tombstone) {
29407
+ return {
29408
+ action: "suppressed",
29409
+ memoryId: tombstone.id,
29410
+ tombstoneId: tombstone.id,
29411
+ proofCount: 0,
29412
+ similarity: tombstone.similarity
29413
+ };
29396
29414
  }
29397
29415
  }
29398
29416
  const createOpts = {
@@ -29459,7 +29477,8 @@ async function retain(content, ctx, options = {}) {
29459
29477
  action: "supersede",
29460
29478
  memoryId: created2.uniqueId,
29461
29479
  targetId: primaryTargetId,
29462
- proofCount: 1
29480
+ proofCount: 1,
29481
+ consolidation: "supersede"
29463
29482
  };
29464
29483
  }
29465
29484
  notifyConsolidationFallback(
@@ -29473,7 +29492,8 @@ async function retain(content, ctx, options = {}) {
29473
29492
  return {
29474
29493
  action: "create",
29475
29494
  memoryId: created.uniqueId,
29476
- proofCount: 1
29495
+ proofCount: 1,
29496
+ ...consolidationDecidedCreate && { consolidation: "create" }
29477
29497
  };
29478
29498
  }
29479
29499
  function unionStrings(a, b) {
@@ -29503,7 +29523,7 @@ async function findTombstoneMatch(embedding, embeddingModel, scope, ctx, opts) {
29503
29523
  bestId = row.uniqueId;
29504
29524
  }
29505
29525
  }
29506
- return bestId;
29526
+ return bestId === null ? null : { id: bestId, similarity: bestSim };
29507
29527
  }
29508
29528
  function pickEventTimeUpdate(existing, incoming) {
29509
29529
  if (incoming === void 0 || incoming === null) return void 0;
@@ -29545,7 +29565,7 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
29545
29565
  }));
29546
29566
  const { consolidateMemory: doConsolidate } = await Promise.resolve().then(() => (init_consolidate(), consolidate_exports));
29547
29567
  const decision = await doConsolidate(trimmed, candidates, consolidateOptions);
29548
- if (decision.action === "create") return null;
29568
+ if (decision.action === "create") return decision.fallbackReason ? null : { create: true };
29549
29569
  if (decision.action === "supersede" && decision.content) {
29550
29570
  const requestedIds = decision.targetIds?.length ? decision.targetIds : decision.targetId ? [decision.targetId] : [];
29551
29571
  if (requestedIds.length === 0) return null;
@@ -29604,7 +29624,8 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
29604
29624
  action: "merge",
29605
29625
  memoryId: decision.targetId,
29606
29626
  targetId: decision.targetId,
29607
- proofCount: updated.proofCount ?? (existing.proofCount ?? 1) + 1
29627
+ proofCount: updated.proofCount ?? (existing.proofCount ?? 1) + 1,
29628
+ consolidation: "noop"
29608
29629
  }
29609
29630
  };
29610
29631
  }
@@ -29657,7 +29678,8 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
29657
29678
  action: "update",
29658
29679
  memoryId: decision.targetId,
29659
29680
  targetId: decision.targetId,
29660
- proofCount: updated.proofCount ?? (existing.proofCount ?? 1) + 1
29681
+ proofCount: updated.proofCount ?? (existing.proofCount ?? 1) + 1,
29682
+ consolidation: "update"
29661
29683
  }
29662
29684
  };
29663
29685
  }
@@ -29799,6 +29821,11 @@ Extract durable user facts.`,
29799
29821
  fallbackSourceId,
29800
29822
  options.userIdentity ?? []
29801
29823
  );
29824
+ const rawCandidates = parsed.candidates;
29825
+ options.onCandidatesParsed?.({
29826
+ rawCount: Array.isArray(rawCandidates) ? rawCandidates.length : 0,
29827
+ validCount: candidates.length
29828
+ });
29802
29829
  if (!redactor) return candidates;
29803
29830
  const restored = restoreCandidates(candidates, redactor, options.userIdentity ?? []);
29804
29831
  if (candidates.length > 0 && restored.length === 0) {
@@ -29835,8 +29862,12 @@ async function extractAndRetain(messages, retainCtx, options) {
29835
29862
  let exhaustedEmpty = false;
29836
29863
  let exhaustedFailure;
29837
29864
  let droppedAfterRedaction = false;
29865
+ let rawCandidateCount = 0;
29866
+ let validCandidateCount = 0;
29838
29867
  const callerOnExhaustedEmpty = options.extract.onExhaustedEmpty;
29839
29868
  const callerOnCandidatesDropped = options.extract.onCandidatesDropped;
29869
+ const callerOnCandidatesParsed = options.extract.onCandidatesParsed;
29870
+ const tExtract = Date.now();
29840
29871
  const candidates = await extractFacts(messages, {
29841
29872
  ...options.extract,
29842
29873
  onExhaustedEmpty: (failure) => {
@@ -29847,8 +29878,14 @@ async function extractAndRetain(messages, retainCtx, options) {
29847
29878
  onCandidatesDropped: () => {
29848
29879
  droppedAfterRedaction = true;
29849
29880
  callerOnCandidatesDropped?.();
29881
+ },
29882
+ onCandidatesParsed: (stats) => {
29883
+ rawCandidateCount = stats.rawCount;
29884
+ validCandidateCount = stats.validCount;
29885
+ callerOnCandidatesParsed?.(stats);
29850
29886
  }
29851
29887
  });
29888
+ const extractMs = Date.now() - tExtract;
29852
29889
  const filtered = candidates.filter((c) => c.confidence >= minConfidence);
29853
29890
  const log = getLogger();
29854
29891
  const screened = screenCandidatesForInjection(filtered);
@@ -29896,6 +29933,7 @@ async function extractAndRetain(messages, retainCtx, options) {
29896
29933
  const results = [];
29897
29934
  const quarantinedInfo = [];
29898
29935
  let failedWrites = 0;
29936
+ const tRetain = Date.now();
29899
29937
  for (const { candidate, isQuarantined, reason, signature } of toRetain) {
29900
29938
  try {
29901
29939
  const result = await retain(candidate.content, retainCtx, {
@@ -29962,6 +30000,21 @@ async function extractAndRetain(messages, retainCtx, options) {
29962
30000
  failedCount: failedWrites,
29963
30001
  outcome,
29964
30002
  quarantined: quarantinedInfo,
30003
+ funnel: {
30004
+ rawCandidateCount,
30005
+ validCandidateCount,
30006
+ afterRedactionCount: candidates.length,
30007
+ aboveConfidenceCount: filtered.length,
30008
+ // The PERSISTED quarantines (`quarantinedInfo`), not the screened list: a
30009
+ // screened candidate whose retain() throws is counted in `failedCount`,
30010
+ // and counting it here too would double it and break the identity below.
30011
+ // This also keeps the count equal to `quarantined.length` on the result.
30012
+ quarantinedCount: quarantinedInfo.length,
30013
+ retainedCount: results.length,
30014
+ failedCount: failedWrites
30015
+ },
30016
+ timings: { extractMs, retainMs: Date.now() - tRetain },
30017
+ model: options.extract.model ?? DEFAULT_EXTRACTION_MODEL,
29965
30018
  // Only meaningful alongside `outcome: "empty-after-retry"`. Returned as well
29966
30019
  // as pushed through the callback so a consumer that only inspects the result
29967
30020
  // (rather than wiring a hook) can still report WHY.
@@ -30217,27 +30270,23 @@ function createAutoExtractor(options) {
30217
30270
  const t0 = Date.now();
30218
30271
  void (async () => {
30219
30272
  try {
30220
- const { candidates, results, failedCount, outcome, failure } = await extractAndRetain(
30221
- window2,
30222
- retainCtx,
30223
- {
30224
- extract,
30225
- ...options.minConfidence !== void 0 && { minConfidence: options.minConfidence },
30226
- ...options.entityCtx !== void 0 && { entityCtx: options.entityCtx },
30227
- ...options.scope !== void 0 && { scope: options.scope },
30228
- ...options.folderId !== void 0 && { folderId: options.folderId },
30229
- ...consolidateOptions !== void 0 && { consolidateOptions },
30230
- ...injectionClassifierOptions !== void 0 && {
30231
- injectionClassifier: injectionClassifierOptions
30232
- },
30233
- ...options.onCandidateFailed && {
30234
- onCandidateFailed: (candidate, error) => options.onCandidateFailed?.({ candidate, error, conversationId })
30235
- },
30236
- ...options.onMemoryQuarantined && {
30237
- onQuarantined: (info) => options.onMemoryQuarantined?.({ ...info, conversationId })
30238
- }
30273
+ const { candidates, results, failedCount, outcome, failure, funnel, timings, model } = await extractAndRetain(window2, retainCtx, {
30274
+ extract,
30275
+ ...options.minConfidence !== void 0 && { minConfidence: options.minConfidence },
30276
+ ...options.entityCtx !== void 0 && { entityCtx: options.entityCtx },
30277
+ ...options.scope !== void 0 && { scope: options.scope },
30278
+ ...options.folderId !== void 0 && { folderId: options.folderId },
30279
+ ...consolidateOptions !== void 0 && { consolidateOptions },
30280
+ ...injectionClassifierOptions !== void 0 && {
30281
+ injectionClassifier: injectionClassifierOptions
30282
+ },
30283
+ ...options.onCandidateFailed && {
30284
+ onCandidateFailed: (candidate, error) => options.onCandidateFailed?.({ candidate, error, conversationId })
30285
+ },
30286
+ ...options.onMemoryQuarantined && {
30287
+ onQuarantined: (info) => options.onMemoryQuarantined?.({ ...info, conversationId })
30239
30288
  }
30240
- );
30289
+ });
30241
30290
  if (outcome !== "empty-after-retry") {
30242
30291
  const advancedTo = window2[window2.length - 1].id;
30243
30292
  stateFor(conversationId).watermark = advancedTo;
@@ -30258,6 +30307,9 @@ function createAutoExtractor(options) {
30258
30307
  durationMs: Date.now() - t0,
30259
30308
  conversationId,
30260
30309
  outcome,
30310
+ funnel,
30311
+ timings,
30312
+ model,
30261
30313
  // Only set alongside `outcome: "empty-after-retry"`. Spread so the key
30262
30314
  // is absent rather than explicitly undefined on a healthy turn —
30263
30315
  // analytics backends store an explicit undefined as a real value.