@anuma/sdk 7.3.1 → 7.3.3

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.
@@ -709,6 +709,13 @@ var FOREIGN_ENDPOINT_SUFFIX = {
709
709
  chat: "/responses",
710
710
  responses: "/chat/completions"
711
711
  };
712
+ function endpointForTransport(endpoint, transport) {
713
+ const { path, rest } = splitEndpoint(endpoint);
714
+ const foreign = FOREIGN_ENDPOINT_SUFFIX[transport];
715
+ if (!path.endsWith(foreign)) return endpoint;
716
+ const own = FOREIGN_ENDPOINT_SUFFIX[transport === "chat" ? "responses" : "chat"];
717
+ return `${path.slice(0, -foreign.length)}${own}${rest}`;
718
+ }
712
719
  function assertTransportMatchesEndpoint(transport, endpoint) {
713
720
  const foreign = FOREIGN_ENDPOINT_SUFFIX[transport];
714
721
  if (!splitEndpoint(endpoint).path.endsWith(foreign)) return;
@@ -766,10 +773,7 @@ async function callPortalJsonCompletion(req) {
766
773
  throw new Error(overrideValidation.message);
767
774
  }
768
775
  endpoint = overrideValidation.endpoint;
769
- const { path, rest } = splitEndpoint(endpoint);
770
- if (autoUpgraded && path.endsWith("/chat/completions")) {
771
- endpoint = `${path.slice(0, -"/chat/completions".length)}/responses${rest}`;
772
- }
776
+ if (autoUpgraded) endpoint = endpointForTransport(endpoint, transport);
773
777
  assertTransportMatchesEndpoint(transport, endpoint);
774
778
  }
775
779
  const maxAttempts = Math.max(1, req.maxAttempts ?? 3);
@@ -1074,6 +1078,7 @@ export {
1074
1078
  withInternalFlowMarker,
1075
1079
  supportsResponseFormat,
1076
1080
  requiresResponsesTransport,
1081
+ endpointForTransport,
1077
1082
  parseRetryAfterMs,
1078
1083
  resolvePortalAuthHeaders,
1079
1084
  callPortalJsonCompletion,
@@ -1,12 +1,14 @@
1
1
  import {
2
2
  callPortalJsonCompletion,
3
+ endpointForTransport,
3
4
  notifyConsolidationFallback,
5
+ requiresResponsesTransport,
4
6
  resolvePiiRedactor
5
- } from "./chunk-AF37N5Z6.mjs";
7
+ } from "./chunk-XW3LYSDA.mjs";
6
8
  import "./chunk-GYKJOWEO.mjs";
7
9
 
8
10
  // src/lib/memory/consolidate.ts
9
- var DEFAULT_CONSOLIDATION_MODEL = "inclusionai/ling-2.6-flash";
11
+ var DEFAULT_CONSOLIDATION_MODEL = "openai/gpt-6-luna";
10
12
  var DEFAULT_CONSOLIDATE_ATTEMPTS = 3;
11
13
  var DEFAULT_CONSOLIDATE_TOTAL_TIMEOUT_MS = 2e4;
12
14
  var SYSTEM_PROMPT = `You consolidate a new memory against existing memories from the same user.
@@ -46,7 +48,11 @@ OUTPUT \u2014 strict JSON, no prose:
46
48
  For "create": content is the new memory verbatim (or a slight refinement); omit targetId/targetIds.
47
49
  For "update": content is the merged richest-version, \u226480 words; targetId is the single memory to update.
48
50
  For "supersede": content is the NEW fact only (\u226480 words); "targetIds" lists EVERY stale memory being retired (all candidates describing the same now-changed attribute). You MUST also name the subjects: "newSubject" for the new memory, and one entry in "targetSubjects" for EACH memory you are retiring (use "targetSubject" as well when you retire a single memory). List a subject ONLY for the memories you are actually retiring, not for every candidate you were shown. Write the plain subject, not a sentence: "the user", "the user's sister", "the user's manager", "Biscuit the dog". Rule 1a means a supersede is only valid when every one of those subjects is the SAME as "newSubject"; name them and check them against each other before you commit to the action. If ANY of them differs, the answer is "create" \u2014 do not retire the ones that match and keep the rest, because a group that mixes subjects was grouped wrongly in the first place. A subjectless fact ("Lives in Denver", "Works at Riverbend") is about "the user" \u2014 the extractor omits the subject when it is the user, so treat the absence of a subject as the user, never as unknown.
49
- For "noop": no content (existing memory is already correct); targetId is that memory.`;
51
+ For "noop": no content (existing memory is already correct); targetId is that memory.
52
+
53
+ ADDING TO AN INTRODUCED ENTITY (check this before choosing "create"): when an existing memory only introduces or names someone or something in the user's life ("User has a cat named Mochi", "User's sister is called Ana") and the new memory gives an attribute of that SAME entity that the existing memory does not state yet (its age, breed, job, home city), choose "update": targetId is that memory and content folds both into one sentence ("User has a cat named Mochi, who is four years old."). The "dog's name vs dog's age" example above means two memories that each already state a different attribute stay separate; a memory that only introduces the entity absorbs the next attribute about it. If the existing memory already states a value and the new memory changes it, that is "supersede" (rule 4), not this.
54
+
55
+ CARRY FORWARD UNCHANGED DETAILS (every "update" and "supersede"): a memory can hold several details at once, such as one folded from an introduction and an attribute ("User's sister Ana lives in Paris"). content replaces ONLY the value that changed and keeps every other detail of the memory it replaces, the subject's relation to the user and name above all: "Ana lives in London" against "User's sister Ana lives in Paris" is "supersede" with content "User's sister Ana lives in London", never just "Ana lives in London". Name the subject the same way on both sides: here "newSubject" and "targetSubjects" are both "the user's sister Ana".`;
50
56
  async function consolidateMemory(newContent, candidates, options) {
51
57
  const fallback = {
52
58
  action: "create",
@@ -69,14 +75,23 @@ async function consolidateMemory(newContent, candidates, options) {
69
75
 
70
76
  Existing memories (top ${candidates.length} by cosine):
71
77
  ${candidateText}`;
78
+ const model = options.model ?? DEFAULT_CONSOLIDATION_MODEL;
79
+ const viaResponses = requiresResponsesTransport(model);
80
+ const endpointOverride = options.endpointOverride === void 0 ? void 0 : endpointForTransport(options.endpointOverride, viaResponses ? "responses" : "chat");
72
81
  let parsed;
82
+ let failure;
73
83
  try {
74
84
  parsed = await callPortalJsonCompletion({
85
+ onFailure: (f) => {
86
+ failure = f;
87
+ },
75
88
  ...options.apiKey !== void 0 && { apiKey: options.apiKey },
76
89
  ...options.getToken !== void 0 && { getToken: options.getToken },
77
90
  ...options.baseUrl !== void 0 && { baseUrl: options.baseUrl },
91
+ ...endpointOverride !== void 0 && { endpointOverride },
78
92
  taskType: "memory_consolidate",
79
- model: options.model ?? DEFAULT_CONSOLIDATION_MODEL,
93
+ model,
94
+ ...viaResponses ? { transport: "responses", reasoning: { effort: "low" } } : { transport: "chat" },
80
95
  systemPrompt: SYSTEM_PROMPT,
81
96
  userMessage,
82
97
  tag: "memory/consolidate",
@@ -88,7 +103,7 @@ ${candidateText}`;
88
103
  } catch (err) {
89
104
  return degrade("llm_error", fallback, options, err);
90
105
  }
91
- if (parsed === null) return degrade("llm_error", fallback, options);
106
+ if (parsed === null) return degrade(fallbackReasonFor(failure), fallback, options, failure);
92
107
  const validIds = new Set(candidates.map((c) => c.id));
93
108
  const result = validate(parsed, trimmed, validIds);
94
109
  if (!result) return degrade("invalid_response", fallback, options);
@@ -101,6 +116,11 @@ ${candidateText}`;
101
116
  }
102
117
  return reportRefusal(result, options);
103
118
  }
119
+ function fallbackReasonFor(failure) {
120
+ if (failure?.reason === "time-budget-exhausted") return "llm_timeout";
121
+ if (failure?.reason === "content-flagged") return "content_flagged";
122
+ return "llm_error";
123
+ }
104
124
  function reportRefusal(result, options) {
105
125
  if (result.fallbackReason) {
106
126
  notifyConsolidationFallback(result.fallbackReason, options.onFallback);
@@ -807,6 +807,13 @@ function splitEndpoint(endpoint) {
807
807
  rest: cut === -1 ? "" : endpoint.slice(cut)
808
808
  };
809
809
  }
810
+ function endpointForTransport(endpoint, transport) {
811
+ const { path, rest } = splitEndpoint(endpoint);
812
+ const foreign = FOREIGN_ENDPOINT_SUFFIX[transport];
813
+ if (!path.endsWith(foreign)) return endpoint;
814
+ const own = FOREIGN_ENDPOINT_SUFFIX[transport === "chat" ? "responses" : "chat"];
815
+ return `${path.slice(0, -foreign.length)}${own}${rest}`;
816
+ }
810
817
  function assertTransportMatchesEndpoint(transport, endpoint) {
811
818
  const foreign = FOREIGN_ENDPOINT_SUFFIX[transport];
812
819
  if (!splitEndpoint(endpoint).path.endsWith(foreign)) return;
@@ -863,10 +870,7 @@ async function callPortalJsonCompletion(req) {
863
870
  throw new Error(overrideValidation.message);
864
871
  }
865
872
  endpoint = overrideValidation.endpoint;
866
- const { path, rest } = splitEndpoint(endpoint);
867
- if (autoUpgraded && path.endsWith("/chat/completions")) {
868
- endpoint = `${path.slice(0, -"/chat/completions".length)}/responses${rest}`;
869
- }
873
+ if (autoUpgraded) endpoint = endpointForTransport(endpoint, transport);
870
874
  assertTransportMatchesEndpoint(transport, endpoint);
871
875
  }
872
876
  const maxAttempts = Math.max(1, req.maxAttempts ?? 3);
@@ -1212,14 +1216,23 @@ async function consolidateMemory(newContent, candidates, options) {
1212
1216
 
1213
1217
  Existing memories (top ${candidates.length} by cosine):
1214
1218
  ${candidateText}`;
1219
+ const model = options.model ?? DEFAULT_CONSOLIDATION_MODEL;
1220
+ const viaResponses = requiresResponsesTransport(model);
1221
+ const endpointOverride = options.endpointOverride === void 0 ? void 0 : endpointForTransport(options.endpointOverride, viaResponses ? "responses" : "chat");
1215
1222
  let parsed;
1223
+ let failure;
1216
1224
  try {
1217
1225
  parsed = await callPortalJsonCompletion({
1226
+ onFailure: (f) => {
1227
+ failure = f;
1228
+ },
1218
1229
  ...options.apiKey !== void 0 && { apiKey: options.apiKey },
1219
1230
  ...options.getToken !== void 0 && { getToken: options.getToken },
1220
1231
  ...options.baseUrl !== void 0 && { baseUrl: options.baseUrl },
1232
+ ...endpointOverride !== void 0 && { endpointOverride },
1221
1233
  taskType: "memory_consolidate",
1222
- model: options.model ?? DEFAULT_CONSOLIDATION_MODEL,
1234
+ model,
1235
+ ...viaResponses ? { transport: "responses", reasoning: { effort: "low" } } : { transport: "chat" },
1223
1236
  systemPrompt: SYSTEM_PROMPT3,
1224
1237
  userMessage,
1225
1238
  tag: "memory/consolidate",
@@ -1231,7 +1244,7 @@ ${candidateText}`;
1231
1244
  } catch (err) {
1232
1245
  return degrade("llm_error", fallback, options, err);
1233
1246
  }
1234
- if (parsed === null) return degrade("llm_error", fallback, options);
1247
+ if (parsed === null) return degrade(fallbackReasonFor(failure), fallback, options, failure);
1235
1248
  const validIds = new Set(candidates.map((c) => c.id));
1236
1249
  const result = validate2(parsed, trimmed, validIds);
1237
1250
  if (!result) return degrade("invalid_response", fallback, options);
@@ -1244,6 +1257,11 @@ ${candidateText}`;
1244
1257
  }
1245
1258
  return reportRefusal(result, options);
1246
1259
  }
1260
+ function fallbackReasonFor(failure) {
1261
+ if (failure?.reason === "time-budget-exhausted") return "llm_timeout";
1262
+ if (failure?.reason === "content-flagged") return "content_flagged";
1263
+ return "llm_error";
1264
+ }
1247
1265
  function reportRefusal(result, options) {
1248
1266
  if (result.fallbackReason) {
1249
1267
  notifyConsolidationFallback(result.fallbackReason, options.onFallback);
@@ -1317,7 +1335,7 @@ var init_consolidate = __esm({
1317
1335
  init_redactor();
1318
1336
  init_consolidationFallback();
1319
1337
  init_portalLlm();
1320
- DEFAULT_CONSOLIDATION_MODEL = "inclusionai/ling-2.6-flash";
1338
+ DEFAULT_CONSOLIDATION_MODEL = "openai/gpt-6-luna";
1321
1339
  DEFAULT_CONSOLIDATE_ATTEMPTS = 3;
1322
1340
  DEFAULT_CONSOLIDATE_TOTAL_TIMEOUT_MS = 2e4;
1323
1341
  SYSTEM_PROMPT3 = `You consolidate a new memory against existing memories from the same user.
@@ -1357,7 +1375,11 @@ OUTPUT \u2014 strict JSON, no prose:
1357
1375
  For "create": content is the new memory verbatim (or a slight refinement); omit targetId/targetIds.
1358
1376
  For "update": content is the merged richest-version, \u226480 words; targetId is the single memory to update.
1359
1377
  For "supersede": content is the NEW fact only (\u226480 words); "targetIds" lists EVERY stale memory being retired (all candidates describing the same now-changed attribute). You MUST also name the subjects: "newSubject" for the new memory, and one entry in "targetSubjects" for EACH memory you are retiring (use "targetSubject" as well when you retire a single memory). List a subject ONLY for the memories you are actually retiring, not for every candidate you were shown. Write the plain subject, not a sentence: "the user", "the user's sister", "the user's manager", "Biscuit the dog". Rule 1a means a supersede is only valid when every one of those subjects is the SAME as "newSubject"; name them and check them against each other before you commit to the action. If ANY of them differs, the answer is "create" \u2014 do not retire the ones that match and keep the rest, because a group that mixes subjects was grouped wrongly in the first place. A subjectless fact ("Lives in Denver", "Works at Riverbend") is about "the user" \u2014 the extractor omits the subject when it is the user, so treat the absence of a subject as the user, never as unknown.
1360
- For "noop": no content (existing memory is already correct); targetId is that memory.`;
1378
+ For "noop": no content (existing memory is already correct); targetId is that memory.
1379
+
1380
+ ADDING TO AN INTRODUCED ENTITY (check this before choosing "create"): when an existing memory only introduces or names someone or something in the user's life ("User has a cat named Mochi", "User's sister is called Ana") and the new memory gives an attribute of that SAME entity that the existing memory does not state yet (its age, breed, job, home city), choose "update": targetId is that memory and content folds both into one sentence ("User has a cat named Mochi, who is four years old."). The "dog's name vs dog's age" example above means two memories that each already state a different attribute stay separate; a memory that only introduces the entity absorbs the next attribute about it. If the existing memory already states a value and the new memory changes it, that is "supersede" (rule 4), not this.
1381
+
1382
+ CARRY FORWARD UNCHANGED DETAILS (every "update" and "supersede"): a memory can hold several details at once, such as one folded from an introduction and an attribute ("User's sister Ana lives in Paris"). content replaces ONLY the value that changed and keeps every other detail of the memory it replaces, the subject's relation to the user and name above all: "Ana lives in London" against "User's sister Ana lives in Paris" is "supersede" with content "User's sister Ana lives in London", never just "Ana lives in London". Name the subject the same way on both sides: here "newSubject" and "targetSubjects" are both "the user's sister Ana".`;
1361
1383
  USER_SUBJECT_ALIASES = /* @__PURE__ */ new Set(["user", "themselves", "themself", "they", "me", "self"]);
1362
1384
  }
1363
1385
  });
@@ -2070,7 +2092,7 @@ function onKeyAvailable(address, callback) {
2070
2092
  callbacks.add(callback);
2071
2093
  return () => {
2072
2094
  callbacks.delete(callback);
2073
- if (callbacks.size === 0) {
2095
+ if (callbacks.size === 0 && keyAvailableCallbacks.get(address) === callbacks) {
2074
2096
  keyAvailableCallbacks.delete(address);
2075
2097
  }
2076
2098
  };
@@ -18455,7 +18477,12 @@ function createAutoExtractor(options) {
18455
18477
  ...baseUrl !== void 0 && { baseUrl }
18456
18478
  };
18457
18479
  };
18458
- const consolidateOptions = options.consolidate ? { ...options.consolidate, ...subPassAuth(options.consolidate.baseUrl) } : void 0;
18480
+ const consolidateEndpoint = options.consolidate?.endpointOverride ?? options.extract.endpointOverride;
18481
+ const consolidateOptions = options.consolidate ? {
18482
+ ...options.consolidate,
18483
+ ...subPassAuth(options.consolidate.baseUrl),
18484
+ ...consolidateEndpoint !== void 0 && { endpointOverride: consolidateEndpoint }
18485
+ } : void 0;
18459
18486
  const injectionClassifierOptions = options.injectionClassifier ? { ...options.injectionClassifier, ...subPassAuth(options.injectionClassifier.baseUrl) } : void 0;
18460
18487
  if (!options.memoryStore && options.retainCtx.vaultCtx.entityCtx && !options.entityCtx) {
18461
18488
  getLogger().warn(
@@ -23816,10 +23843,20 @@ function batchError(result) {
23816
23843
  var AccountFailureError = class extends Error {
23817
23844
  };
23818
23845
  var LockedSourcesError = class extends Error {
23819
- constructor(persistentIds) {
23820
- super("Source messages are locked; retained for retry");
23846
+ constructor(persistentIds, locked) {
23847
+ const counts = /* @__PURE__ */ new Map();
23848
+ for (const message of locked) {
23849
+ const version = /^enc:(v\d+):/.exec(message.content)?.[1] ?? "unknown";
23850
+ const key = `${message.decryptionStatus ?? "no_status"}:${version}`;
23851
+ counts.set(key, (counts.get(key) ?? 0) + 1);
23852
+ }
23853
+ const summary = [...counts].map(([key, count2]) => `${key}=${count2}`).join(", ");
23854
+ super(`Source messages are locked; retained for retry (${summary})`);
23821
23855
  this.persistentIds = persistentIds;
23822
23856
  }
23857
+ get awaitingKey() {
23858
+ return this.persistentIds.length === 0;
23859
+ }
23823
23860
  };
23824
23861
  function splitTurns(messages) {
23825
23862
  const turns = [];
@@ -23873,6 +23910,8 @@ function createDurableAutoExtractor(options) {
23873
23910
  const attempts = /* @__PURE__ */ new Map();
23874
23911
  const sessionFailures = /* @__PURE__ */ new Map();
23875
23912
  const skippedThisSession = /* @__PURE__ */ new Set();
23913
+ const awaitingKey = /* @__PURE__ */ new Set();
23914
+ let keyEpoch = 0;
23876
23915
  const clock = options.now ?? Date.now;
23877
23916
  const reportError = (error, conversationId) => {
23878
23917
  try {
@@ -24066,13 +24105,15 @@ function createDurableAutoExtractor(options) {
24066
24105
  for (const job of pending) {
24067
24106
  if (disposed) break;
24068
24107
  const attempted = attempts.get(job.id) ?? 0;
24069
- if (attempted >= MAX_ATTEMPTS || skippedThisSession.has(job.id)) continue;
24108
+ if (attempted >= MAX_ATTEMPTS || skippedThisSession.has(job.id) || awaitingKey.has(job.id))
24109
+ continue;
24070
24110
  if (processed >= MAX_JOBS_PER_PASS) {
24071
24111
  scheduleAfterSuccess = true;
24072
24112
  break;
24073
24113
  }
24074
24114
  processed++;
24075
24115
  const conversationId = String(job._getRaw("conversation_id"));
24116
+ const epochAtRead = keyEpoch;
24076
24117
  let ids = [];
24077
24118
  let loaded = [];
24078
24119
  try {
@@ -24126,12 +24167,15 @@ function createDurableAutoExtractor(options) {
24126
24167
  );
24127
24168
  continue;
24128
24169
  }
24129
- const locked = loaded.filter((message) => message && isEncrypted(message.content));
24170
+ const locked = loaded.filter(
24171
+ (message) => !!message && isEncrypted(message.content)
24172
+ );
24130
24173
  if (locked.length) {
24131
24174
  throw new LockedSourcesError(
24132
24175
  locked.flatMap(
24133
24176
  (message) => message.decryptionStatus === "auth_mismatch" || message.decryptionStatus === "invalid_payload" ? [message.uniqueId] : []
24134
- )
24177
+ ),
24178
+ locked
24135
24179
  );
24136
24180
  }
24137
24181
  const messages = loaded.flatMap(
@@ -24185,6 +24229,12 @@ function createDurableAutoExtractor(options) {
24185
24229
  scheduleAfterSuccess = true;
24186
24230
  } catch (error) {
24187
24231
  if (disposed) break;
24232
+ if (error instanceof LockedSourcesError && error.awaitingKey) {
24233
+ reportError(error, conversationId);
24234
+ if (keyEpoch === epochAtRead) awaitingKey.add(job.id);
24235
+ else scheduleAfterSuccess = true;
24236
+ continue;
24237
+ }
24188
24238
  attempts.set(job.id, attempted + 1);
24189
24239
  retry = true;
24190
24240
  reportError(error, conversationId);
@@ -24212,7 +24262,18 @@ function createDurableAutoExtractor(options) {
24212
24262
  activeBatch?.abort(options.signal?.reason);
24213
24263
  if (timer !== void 0) clearTimeout(timer);
24214
24264
  options.signal?.removeEventListener("abort", dispose);
24265
+ unsubscribeKey?.();
24215
24266
  }
24267
+ const unsubscribeKey = vault.walletAddress ? onKeyAvailable(vault.walletAddress, () => {
24268
+ keyEpoch++;
24269
+ if (!awaitingKey.size) return;
24270
+ awaitingKey.clear();
24271
+ if (timer !== void 0) {
24272
+ clearTimeout(timer);
24273
+ timer = void 0;
24274
+ }
24275
+ schedule(0);
24276
+ }) : void 0;
24216
24277
  if (options.signal?.aborted) dispose();
24217
24278
  else options.signal?.addEventListener("abort", dispose, { once: true });
24218
24279
  schedule(0);
@@ -24302,6 +24363,7 @@ function createDurableAutoExtractor(options) {
24302
24363
  (r) => r._setRaw("message_ids", JSON.stringify([.../* @__PURE__ */ new Set([...idsOf(job), ...fresh])]))
24303
24364
  );
24304
24365
  attempts.delete(job.id);
24366
+ awaitingKey.delete(job.id);
24305
24367
  } else {
24306
24368
  await jobs.create((r) => {
24307
24369
  r._setRaw("owner_key", owner);
@@ -4821,7 +4821,7 @@ type RetainSource = "manual" | "auto-extracted" | "capsule";
4821
4821
  * rate of exactly zero is as likely to mean "the fields are being omitted" as
4822
4822
  * "the mistake never happens". Check compliance before reading zero as health.
4823
4823
  */
4824
- type ConsolidationFallbackReason = "llm_error" | "invalid_response" | "target_vanished"
4824
+ type ConsolidationFallbackReason = "llm_error" | "llm_timeout" | "content_flagged" | "invalid_response" | "target_vanished"
4825
4825
  /**
4826
4826
  * A `supersede` was downgraded to `create` because the model's own stated
4827
4827
  * subjects disagreed: the new fact is about someone other than the fact it
@@ -4860,6 +4860,7 @@ interface RetainOptions {
4860
4860
  consolidateOptions?: PortalLlmAuth & {
4861
4861
  baseUrl?: string;
4862
4862
  model?: string;
4863
+ endpointOverride?: string;
4863
4864
  /**
4864
4865
  * Invoked when the consolidator degrades to its "create" fallback
4865
4866
  * instead of returning a real decision — `llm_error` for network /
@@ -8967,6 +8968,7 @@ interface CreateAutoExtractorOptions {
8967
8968
  baseUrl?: string;
8968
8969
  /** Override the consolidation model. Default: see `consolidate.ts`. */
8969
8970
  model?: string;
8971
+ endpointOverride?: string;
8970
8972
  /**
8971
8973
  * Notified on each degraded create-fallback (LLM failure or
8972
8974
  * schema-violating response). See
@@ -4821,7 +4821,7 @@ type RetainSource = "manual" | "auto-extracted" | "capsule";
4821
4821
  * rate of exactly zero is as likely to mean "the fields are being omitted" as
4822
4822
  * "the mistake never happens". Check compliance before reading zero as health.
4823
4823
  */
4824
- type ConsolidationFallbackReason = "llm_error" | "invalid_response" | "target_vanished"
4824
+ type ConsolidationFallbackReason = "llm_error" | "llm_timeout" | "content_flagged" | "invalid_response" | "target_vanished"
4825
4825
  /**
4826
4826
  * A `supersede` was downgraded to `create` because the model's own stated
4827
4827
  * subjects disagreed: the new fact is about someone other than the fact it
@@ -4860,6 +4860,7 @@ interface RetainOptions {
4860
4860
  consolidateOptions?: PortalLlmAuth & {
4861
4861
  baseUrl?: string;
4862
4862
  model?: string;
4863
+ endpointOverride?: string;
4863
4864
  /**
4864
4865
  * Invoked when the consolidator degrades to its "create" fallback
4865
4866
  * instead of returning a real decision — `llm_error` for network /
@@ -8967,6 +8968,7 @@ interface CreateAutoExtractorOptions {
8967
8968
  baseUrl?: string;
8968
8969
  /** Override the consolidation model. Default: see `consolidate.ts`. */
8969
8970
  model?: string;
8971
+ endpointOverride?: string;
8970
8972
  /**
8971
8973
  * Notified on each degraded create-fallback (LLM failure or
8972
8974
  * schema-violating response). See
@@ -20,7 +20,7 @@ import {
20
20
  taskTypeHeader,
21
21
  validateEndpointOverride,
22
22
  withInternalFlowMarker
23
- } from "./chunk-AF37N5Z6.mjs";
23
+ } from "./chunk-XW3LYSDA.mjs";
24
24
  import {
25
25
  BASE_URL,
26
26
  MCP_R2_DOMAIN,
@@ -503,7 +503,7 @@ function onKeyAvailable(address, callback) {
503
503
  callbacks.add(callback);
504
504
  return () => {
505
505
  callbacks.delete(callback);
506
- if (callbacks.size === 0) {
506
+ if (callbacks.size === 0 && keyAvailableCallbacks.get(address) === callbacks) {
507
507
  keyAvailableCallbacks.delete(address);
508
508
  }
509
509
  };
@@ -16121,7 +16121,7 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
16121
16121
  content: m.content,
16122
16122
  similarity: m.similarity
16123
16123
  }));
16124
- const { consolidateMemory: doConsolidate } = await import("./consolidate-TOITBD6W.mjs");
16124
+ const { consolidateMemory: doConsolidate } = await import("./consolidate-4CQJYKRK.mjs");
16125
16125
  const decision = await doConsolidate(trimmed, candidates, consolidateOptions);
16126
16126
  if (decision.action === "create") return decision.fallbackReason ? null : { create: true };
16127
16127
  if (decision.action === "supersede" && decision.content) {
@@ -16837,7 +16837,12 @@ function createAutoExtractor(options) {
16837
16837
  ...baseUrl !== void 0 && { baseUrl }
16838
16838
  };
16839
16839
  };
16840
- const consolidateOptions = options.consolidate ? { ...options.consolidate, ...subPassAuth(options.consolidate.baseUrl) } : void 0;
16840
+ const consolidateEndpoint = options.consolidate?.endpointOverride ?? options.extract.endpointOverride;
16841
+ const consolidateOptions = options.consolidate ? {
16842
+ ...options.consolidate,
16843
+ ...subPassAuth(options.consolidate.baseUrl),
16844
+ ...consolidateEndpoint !== void 0 && { endpointOverride: consolidateEndpoint }
16845
+ } : void 0;
16841
16846
  const injectionClassifierOptions = options.injectionClassifier ? { ...options.injectionClassifier, ...subPassAuth(options.injectionClassifier.baseUrl) } : void 0;
16842
16847
  if (!options.memoryStore && options.retainCtx.vaultCtx.entityCtx && !options.entityCtx) {
16843
16848
  getLogger().warn(
@@ -22171,10 +22176,20 @@ function batchError(result) {
22171
22176
  var AccountFailureError = class extends Error {
22172
22177
  };
22173
22178
  var LockedSourcesError = class extends Error {
22174
- constructor(persistentIds) {
22175
- super("Source messages are locked; retained for retry");
22179
+ constructor(persistentIds, locked) {
22180
+ const counts = /* @__PURE__ */ new Map();
22181
+ for (const message of locked) {
22182
+ const version = /^enc:(v\d+):/.exec(message.content)?.[1] ?? "unknown";
22183
+ const key = `${message.decryptionStatus ?? "no_status"}:${version}`;
22184
+ counts.set(key, (counts.get(key) ?? 0) + 1);
22185
+ }
22186
+ const summary = [...counts].map(([key, count2]) => `${key}=${count2}`).join(", ");
22187
+ super(`Source messages are locked; retained for retry (${summary})`);
22176
22188
  this.persistentIds = persistentIds;
22177
22189
  }
22190
+ get awaitingKey() {
22191
+ return this.persistentIds.length === 0;
22192
+ }
22178
22193
  };
22179
22194
  function splitTurns(messages) {
22180
22195
  const turns = [];
@@ -22228,6 +22243,8 @@ function createDurableAutoExtractor(options) {
22228
22243
  const attempts = /* @__PURE__ */ new Map();
22229
22244
  const sessionFailures = /* @__PURE__ */ new Map();
22230
22245
  const skippedThisSession = /* @__PURE__ */ new Set();
22246
+ const awaitingKey = /* @__PURE__ */ new Set();
22247
+ let keyEpoch = 0;
22231
22248
  const clock = options.now ?? Date.now;
22232
22249
  const reportError = (error, conversationId) => {
22233
22250
  try {
@@ -22421,13 +22438,15 @@ function createDurableAutoExtractor(options) {
22421
22438
  for (const job of pending) {
22422
22439
  if (disposed) break;
22423
22440
  const attempted = attempts.get(job.id) ?? 0;
22424
- if (attempted >= MAX_ATTEMPTS || skippedThisSession.has(job.id)) continue;
22441
+ if (attempted >= MAX_ATTEMPTS || skippedThisSession.has(job.id) || awaitingKey.has(job.id))
22442
+ continue;
22425
22443
  if (processed >= MAX_JOBS_PER_PASS) {
22426
22444
  scheduleAfterSuccess = true;
22427
22445
  break;
22428
22446
  }
22429
22447
  processed++;
22430
22448
  const conversationId = String(job._getRaw("conversation_id"));
22449
+ const epochAtRead = keyEpoch;
22431
22450
  let ids = [];
22432
22451
  let loaded = [];
22433
22452
  try {
@@ -22481,12 +22500,15 @@ function createDurableAutoExtractor(options) {
22481
22500
  );
22482
22501
  continue;
22483
22502
  }
22484
- const locked = loaded.filter((message) => message && isEncrypted(message.content));
22503
+ const locked = loaded.filter(
22504
+ (message) => !!message && isEncrypted(message.content)
22505
+ );
22485
22506
  if (locked.length) {
22486
22507
  throw new LockedSourcesError(
22487
22508
  locked.flatMap(
22488
22509
  (message) => message.decryptionStatus === "auth_mismatch" || message.decryptionStatus === "invalid_payload" ? [message.uniqueId] : []
22489
- )
22510
+ ),
22511
+ locked
22490
22512
  );
22491
22513
  }
22492
22514
  const messages = loaded.flatMap(
@@ -22540,6 +22562,12 @@ function createDurableAutoExtractor(options) {
22540
22562
  scheduleAfterSuccess = true;
22541
22563
  } catch (error) {
22542
22564
  if (disposed) break;
22565
+ if (error instanceof LockedSourcesError && error.awaitingKey) {
22566
+ reportError(error, conversationId);
22567
+ if (keyEpoch === epochAtRead) awaitingKey.add(job.id);
22568
+ else scheduleAfterSuccess = true;
22569
+ continue;
22570
+ }
22543
22571
  attempts.set(job.id, attempted + 1);
22544
22572
  retry = true;
22545
22573
  reportError(error, conversationId);
@@ -22567,7 +22595,18 @@ function createDurableAutoExtractor(options) {
22567
22595
  activeBatch?.abort(options.signal?.reason);
22568
22596
  if (timer !== void 0) clearTimeout(timer);
22569
22597
  options.signal?.removeEventListener("abort", dispose);
22598
+ unsubscribeKey?.();
22570
22599
  }
22600
+ const unsubscribeKey = vault.walletAddress ? onKeyAvailable(vault.walletAddress, () => {
22601
+ keyEpoch++;
22602
+ if (!awaitingKey.size) return;
22603
+ awaitingKey.clear();
22604
+ if (timer !== void 0) {
22605
+ clearTimeout(timer);
22606
+ timer = void 0;
22607
+ }
22608
+ schedule(0);
22609
+ }) : void 0;
22571
22610
  if (options.signal?.aborted) dispose();
22572
22611
  else options.signal?.addEventListener("abort", dispose, { once: true });
22573
22612
  schedule(0);
@@ -22657,6 +22696,7 @@ function createDurableAutoExtractor(options) {
22657
22696
  (r) => r._setRaw("message_ids", JSON.stringify([.../* @__PURE__ */ new Set([...idsOf(job), ...fresh])]))
22658
22697
  );
22659
22698
  attempts.delete(job.id);
22699
+ awaitingKey.delete(job.id);
22660
22700
  } else {
22661
22701
  await jobs.create((r) => {
22662
22702
  r._setRaw("owner_key", owner);
@@ -709,6 +709,13 @@ var FOREIGN_ENDPOINT_SUFFIX = {
709
709
  chat: "/responses",
710
710
  responses: "/chat/completions"
711
711
  };
712
+ function endpointForTransport(endpoint, transport) {
713
+ const { path, rest } = splitEndpoint(endpoint);
714
+ const foreign = FOREIGN_ENDPOINT_SUFFIX[transport];
715
+ if (!path.endsWith(foreign)) return endpoint;
716
+ const own = FOREIGN_ENDPOINT_SUFFIX[transport === "chat" ? "responses" : "chat"];
717
+ return `${path.slice(0, -foreign.length)}${own}${rest}`;
718
+ }
712
719
  function assertTransportMatchesEndpoint(transport, endpoint) {
713
720
  const foreign = FOREIGN_ENDPOINT_SUFFIX[transport];
714
721
  if (!splitEndpoint(endpoint).path.endsWith(foreign)) return;
@@ -766,10 +773,7 @@ async function callPortalJsonCompletion(req) {
766
773
  throw new Error(overrideValidation.message);
767
774
  }
768
775
  endpoint = overrideValidation.endpoint;
769
- const { path, rest } = splitEndpoint(endpoint);
770
- if (autoUpgraded && path.endsWith("/chat/completions")) {
771
- endpoint = `${path.slice(0, -"/chat/completions".length)}/responses${rest}`;
772
- }
776
+ if (autoUpgraded) endpoint = endpointForTransport(endpoint, transport);
773
777
  assertTransportMatchesEndpoint(transport, endpoint);
774
778
  }
775
779
  const maxAttempts = Math.max(1, req.maxAttempts ?? 3);
@@ -1073,6 +1077,7 @@ export {
1073
1077
  withInternalFlowMarker,
1074
1078
  supportsResponseFormat,
1075
1079
  requiresResponsesTransport,
1080
+ endpointForTransport,
1076
1081
  parseRetryAfterMs,
1077
1082
  resolvePortalAuthHeaders,
1078
1083
  callPortalJsonCompletion,
@@ -1,12 +1,14 @@
1
1
  import {
2
2
  callPortalJsonCompletion,
3
+ endpointForTransport,
3
4
  notifyConsolidationFallback,
5
+ requiresResponsesTransport,
4
6
  resolvePiiRedactor
5
- } from "./chunk-TGLAOFVA.mjs";
7
+ } from "./chunk-7QX4J7KV.mjs";
6
8
  import "./chunk-GYKJOWEO.mjs";
7
9
 
8
10
  // src/lib/memory/consolidate.ts
9
- var DEFAULT_CONSOLIDATION_MODEL = "inclusionai/ling-2.6-flash";
11
+ var DEFAULT_CONSOLIDATION_MODEL = "openai/gpt-6-luna";
10
12
  var DEFAULT_CONSOLIDATE_ATTEMPTS = 3;
11
13
  var DEFAULT_CONSOLIDATE_TOTAL_TIMEOUT_MS = 2e4;
12
14
  var SYSTEM_PROMPT = `You consolidate a new memory against existing memories from the same user.
@@ -46,7 +48,11 @@ OUTPUT \u2014 strict JSON, no prose:
46
48
  For "create": content is the new memory verbatim (or a slight refinement); omit targetId/targetIds.
47
49
  For "update": content is the merged richest-version, \u226480 words; targetId is the single memory to update.
48
50
  For "supersede": content is the NEW fact only (\u226480 words); "targetIds" lists EVERY stale memory being retired (all candidates describing the same now-changed attribute). You MUST also name the subjects: "newSubject" for the new memory, and one entry in "targetSubjects" for EACH memory you are retiring (use "targetSubject" as well when you retire a single memory). List a subject ONLY for the memories you are actually retiring, not for every candidate you were shown. Write the plain subject, not a sentence: "the user", "the user's sister", "the user's manager", "Biscuit the dog". Rule 1a means a supersede is only valid when every one of those subjects is the SAME as "newSubject"; name them and check them against each other before you commit to the action. If ANY of them differs, the answer is "create" \u2014 do not retire the ones that match and keep the rest, because a group that mixes subjects was grouped wrongly in the first place. A subjectless fact ("Lives in Denver", "Works at Riverbend") is about "the user" \u2014 the extractor omits the subject when it is the user, so treat the absence of a subject as the user, never as unknown.
49
- For "noop": no content (existing memory is already correct); targetId is that memory.`;
51
+ For "noop": no content (existing memory is already correct); targetId is that memory.
52
+
53
+ ADDING TO AN INTRODUCED ENTITY (check this before choosing "create"): when an existing memory only introduces or names someone or something in the user's life ("User has a cat named Mochi", "User's sister is called Ana") and the new memory gives an attribute of that SAME entity that the existing memory does not state yet (its age, breed, job, home city), choose "update": targetId is that memory and content folds both into one sentence ("User has a cat named Mochi, who is four years old."). The "dog's name vs dog's age" example above means two memories that each already state a different attribute stay separate; a memory that only introduces the entity absorbs the next attribute about it. If the existing memory already states a value and the new memory changes it, that is "supersede" (rule 4), not this.
54
+
55
+ CARRY FORWARD UNCHANGED DETAILS (every "update" and "supersede"): a memory can hold several details at once, such as one folded from an introduction and an attribute ("User's sister Ana lives in Paris"). content replaces ONLY the value that changed and keeps every other detail of the memory it replaces, the subject's relation to the user and name above all: "Ana lives in London" against "User's sister Ana lives in Paris" is "supersede" with content "User's sister Ana lives in London", never just "Ana lives in London". Name the subject the same way on both sides: here "newSubject" and "targetSubjects" are both "the user's sister Ana".`;
50
56
  async function consolidateMemory(newContent, candidates, options) {
51
57
  const fallback = {
52
58
  action: "create",
@@ -69,14 +75,23 @@ async function consolidateMemory(newContent, candidates, options) {
69
75
 
70
76
  Existing memories (top ${candidates.length} by cosine):
71
77
  ${candidateText}`;
78
+ const model = options.model ?? DEFAULT_CONSOLIDATION_MODEL;
79
+ const viaResponses = requiresResponsesTransport(model);
80
+ const endpointOverride = options.endpointOverride === void 0 ? void 0 : endpointForTransport(options.endpointOverride, viaResponses ? "responses" : "chat");
72
81
  let parsed;
82
+ let failure;
73
83
  try {
74
84
  parsed = await callPortalJsonCompletion({
85
+ onFailure: (f) => {
86
+ failure = f;
87
+ },
75
88
  ...options.apiKey !== void 0 && { apiKey: options.apiKey },
76
89
  ...options.getToken !== void 0 && { getToken: options.getToken },
77
90
  ...options.baseUrl !== void 0 && { baseUrl: options.baseUrl },
91
+ ...endpointOverride !== void 0 && { endpointOverride },
78
92
  taskType: "memory_consolidate",
79
- model: options.model ?? DEFAULT_CONSOLIDATION_MODEL,
93
+ model,
94
+ ...viaResponses ? { transport: "responses", reasoning: { effort: "low" } } : { transport: "chat" },
80
95
  systemPrompt: SYSTEM_PROMPT,
81
96
  userMessage,
82
97
  tag: "memory/consolidate",
@@ -88,7 +103,7 @@ ${candidateText}`;
88
103
  } catch (err) {
89
104
  return degrade("llm_error", fallback, options, err);
90
105
  }
91
- if (parsed === null) return degrade("llm_error", fallback, options);
106
+ if (parsed === null) return degrade(fallbackReasonFor(failure), fallback, options, failure);
92
107
  const validIds = new Set(candidates.map((c) => c.id));
93
108
  const result = validate(parsed, trimmed, validIds);
94
109
  if (!result) return degrade("invalid_response", fallback, options);
@@ -101,6 +116,11 @@ ${candidateText}`;
101
116
  }
102
117
  return reportRefusal(result, options);
103
118
  }
119
+ function fallbackReasonFor(failure) {
120
+ if (failure?.reason === "time-budget-exhausted") return "llm_timeout";
121
+ if (failure?.reason === "content-flagged") return "content_flagged";
122
+ return "llm_error";
123
+ }
104
124
  function reportRefusal(result, options) {
105
125
  if (result.fallbackReason) {
106
126
  notifyConsolidationFallback(result.fallbackReason, options.onFallback);