@anuma/sdk 3.6.3 → 3.8.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.
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  callPortalJsonCompletion,
3
3
  getLogger
4
- } from "./chunk-AHJT66EO.mjs";
4
+ } from "./chunk-MIXEASUP.mjs";
5
5
  import {
6
6
  BASE_URL,
7
7
  __commonJS,
@@ -19554,7 +19554,7 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
19554
19554
  legacy.map((r) => ({ id: r.uniqueId, content: r.content, score: r.similarity }))
19555
19555
  );
19556
19556
  }
19557
- const { recall: recall2 } = await import("./recall-UTVH2XPK.mjs");
19557
+ const { recall: recall2 } = await import("./recall-XEOLXA6P.mjs");
19558
19558
  const result = await recall2(
19559
19559
  query,
19560
19560
  { vaultCtx, embeddingOptions, vaultCache: cache },
@@ -14,6 +14,24 @@ function getLogger() {
14
14
  return currentLogger;
15
15
  }
16
16
 
17
+ // src/lib/chat/endpointOverride.ts
18
+ function validateEndpointOverride(override) {
19
+ const trimmed = override.trim();
20
+ if (trimmed === "") {
21
+ return {
22
+ valid: false,
23
+ message: 'endpointOverride must be a non-empty, root-relative path (e.g. "/api/v1/utility/responses"); received an empty or whitespace-only string'
24
+ };
25
+ }
26
+ if (trimmed.startsWith("//") || trimmed.includes("://")) {
27
+ return {
28
+ valid: false,
29
+ message: `endpointOverride must be a root-relative path (e.g. "/api/v1/utility/responses"), not a protocol-relative or absolute URL; received "${trimmed}"`
30
+ };
31
+ }
32
+ return { valid: true, endpoint: trimmed.startsWith("/") ? trimmed : `/${trimmed}` };
33
+ }
34
+
17
35
  // src/lib/memory/portalLlm.ts
18
36
  function defaultBaseUrl() {
19
37
  return typeof process !== "undefined" && process.env?.ANUMA_PORTAL_BASE_URL || BASE_URL;
@@ -65,6 +83,14 @@ async function resolvePortalAuthHeaders(auth, tag) {
65
83
  }
66
84
  async function callPortalJsonCompletion(req) {
67
85
  const log = getLogger();
86
+ let endpoint = "/api/v1/chat/completions";
87
+ if (req.endpointOverride !== void 0) {
88
+ const overrideValidation = validateEndpointOverride(req.endpointOverride);
89
+ if (!overrideValidation.valid) {
90
+ throw new Error(overrideValidation.message);
91
+ }
92
+ endpoint = overrideValidation.endpoint;
93
+ }
68
94
  const maxAttempts = Math.max(1, req.maxAttempts ?? 3);
69
95
  const startedAt = Date.now();
70
96
  const overBudget = () => req.totalTimeoutMs !== void 0 && Date.now() - startedAt >= req.totalTimeoutMs;
@@ -80,7 +106,7 @@ async function callPortalJsonCompletion(req) {
80
106
  Math.min(req.timeoutMs ?? 6e4, req.totalTimeoutMs - (Date.now() - startedAt))
81
107
  )
82
108
  } : req;
83
- const outcome = await attemptPortalJson(attemptReq);
109
+ const outcome = await attemptPortalJson(attemptReq, endpoint);
84
110
  if (outcome.kind === "ok") return outcome.value;
85
111
  if (outcome.kind === "terminal") {
86
112
  log.warn(`[${req.tag}] ${outcome.reason}`);
@@ -103,7 +129,7 @@ async function callPortalJsonCompletion(req) {
103
129
  }
104
130
  return null;
105
131
  }
106
- async function attemptPortalJson(req) {
132
+ async function attemptPortalJson(req, endpoint) {
107
133
  const baseUrl = req.baseUrl ?? defaultBaseUrl();
108
134
  const fetchImpl = req.fetchFn ?? fetch;
109
135
  const timeoutMs = req.timeoutMs ?? 6e4;
@@ -128,7 +154,7 @@ async function attemptPortalJson(req) {
128
154
  if (!supportsJsonObjectFormat) delete requestBody.response_format;
129
155
  let response;
130
156
  try {
131
- response = await fetchImpl(`${baseUrl}/api/v1/chat/completions`, {
157
+ response = await fetchImpl(`${baseUrl}${endpoint}`, {
132
158
  method: "POST",
133
159
  headers: { ...authHeaders, "Content-Type": "application/json" },
134
160
  body: JSON.stringify(requestBody),
@@ -223,6 +249,7 @@ function extractCompletionContent(body) {
223
249
 
224
250
  export {
225
251
  getLogger,
252
+ validateEndpointOverride,
226
253
  supportsResponseFormat,
227
254
  resolvePortalAuthHeaders,
228
255
  callPortalJsonCompletion,
@@ -4,7 +4,7 @@ import {
4
4
  import {
5
5
  callPortalJsonCompletion,
6
6
  getLogger
7
- } from "./chunk-AHJT66EO.mjs";
7
+ } from "./chunk-MIXEASUP.mjs";
8
8
  import "./chunk-3RPGXXPZ.mjs";
9
9
 
10
10
  // src/lib/memory/consolidate.ts
@@ -19,7 +19,7 @@ Decide one of four actions:
19
19
 
20
20
  - "create": the new memory describes a distinct fact not covered by any existing memory. Write it as new.
21
21
  - "update": the new memory describes the SAME FACET as exactly one existing memory and is COMPATIBLE with it \u2014 it adds detail, fixes an incomplete value, or rephrases the same still-true fact. Return targetId of that existing memory + the consolidated content (richest version of the two).
22
- - "supersede": the new memory REPLACES a standing fact whose value has CHANGED and is now INCOMPATIBLE with an existing memory \u2014 the old fact was true before but is false now (a state change). Return targetId of the STALE existing memory to retire + content = the NEW fact. The old memory is kept as history, not overwritten or deleted; do not re-state it.
22
+ - "supersede": the new memory REPLACES a standing fact whose value has CHANGED and is now INCOMPATIBLE with an existing memory \u2014 the old fact was true before but is false now (a state change). Return "targetIds": an array of the ids of EVERY existing candidate that describes the SAME standing attribute now being changed \u2014 retire ALL stale duplicates of the old value, not just one (the candidate list may contain several paraphrases of the same old fact) + content = the NEW fact. The old memories are kept as history, not overwritten or deleted; do not re-state them.
23
23
  - "noop": the new memory is already fully captured by an existing memory \u2014 same facet, no new information. Skip the write.
24
24
 
25
25
  RULES (carry these strictly):
@@ -35,14 +35,15 @@ RULES (carry these strictly):
35
35
  OUTPUT \u2014 strict JSON, no prose:
36
36
  {
37
37
  "action": "create" | "update" | "supersede" | "noop",
38
- "targetId": "<existing memory id \u2014 required for update/supersede/noop, omit for create>",
38
+ "targetId": "<existing memory id \u2014 required for update/noop, omit for create/supersede>",
39
+ "targetIds": ["<id>", ...],
39
40
  "content": "<content \u2014 required for create/update/supersede, omit for noop>"
40
41
  }
41
42
 
42
- For "create": content is the new memory verbatim (or a slight refinement).
43
- For "update": content is the merged richest-version, \u226480 words.
44
- For "supersede": content is the NEW fact only (\u226480 words); targetId is the stale memory being retired.
45
- For "noop": no content (existing memory is already correct).`;
43
+ For "create": content is the new memory verbatim (or a slight refinement); omit targetId/targetIds.
44
+ For "update": content is the merged richest-version, \u226480 words; targetId is the single memory to update.
45
+ 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).
46
+ For "noop": no content (existing memory is already correct); targetId is that memory.`;
46
47
  async function consolidateMemory(newContent, candidates, options) {
47
48
  const fallback = {
48
49
  action: "create",
@@ -123,14 +124,27 @@ function validate(parsed, newContent, validIds) {
123
124
  const c2 = typeof obj.content === "string" ? obj.content.trim() : "";
124
125
  return { action: "create", content: c2.length > 0 ? c2 : newContent };
125
126
  }
126
- const targetId = typeof obj.targetId === "string" ? obj.targetId : null;
127
- if (!targetId || !validIds.has(targetId)) return null;
128
127
  if (action === "noop") {
129
- return { action: "noop", targetId };
128
+ const targetId2 = typeof obj.targetId === "string" ? obj.targetId : null;
129
+ if (!targetId2 || !validIds.has(targetId2)) return null;
130
+ return { action: "noop", targetId: targetId2 };
130
131
  }
131
132
  const c = typeof obj.content === "string" ? obj.content.trim() : "";
132
133
  if (c.length === 0) return null;
133
- return { action, targetId, content: c };
134
+ if (action === "supersede") {
135
+ const rawIds = [
136
+ ...Array.isArray(obj.targetIds) ? obj.targetIds : [],
137
+ ...typeof obj.targetId === "string" ? [obj.targetId] : []
138
+ ];
139
+ const targetIds = [
140
+ ...new Set(rawIds.filter((id) => typeof id === "string" && validIds.has(id)))
141
+ ];
142
+ if (targetIds.length === 0) return null;
143
+ return { action: "supersede", targetId: targetIds[0], targetIds, content: c };
144
+ }
145
+ const targetId = typeof obj.targetId === "string" ? obj.targetId : null;
146
+ if (!targetId || !validIds.has(targetId)) return null;
147
+ return { action: "update", targetId, content: c };
134
148
  }
135
149
  export {
136
150
  consolidateMemory
@@ -19549,6 +19549,29 @@ var init_bm25 = __esm({
19549
19549
  }
19550
19550
  });
19551
19551
 
19552
+ // src/lib/chat/endpointOverride.ts
19553
+ function validateEndpointOverride(override) {
19554
+ const trimmed = override.trim();
19555
+ if (trimmed === "") {
19556
+ return {
19557
+ valid: false,
19558
+ message: 'endpointOverride must be a non-empty, root-relative path (e.g. "/api/v1/utility/responses"); received an empty or whitespace-only string'
19559
+ };
19560
+ }
19561
+ if (trimmed.startsWith("//") || trimmed.includes("://")) {
19562
+ return {
19563
+ valid: false,
19564
+ message: `endpointOverride must be a root-relative path (e.g. "/api/v1/utility/responses"), not a protocol-relative or absolute URL; received "${trimmed}"`
19565
+ };
19566
+ }
19567
+ return { valid: true, endpoint: trimmed.startsWith("/") ? trimmed : `/${trimmed}` };
19568
+ }
19569
+ var init_endpointOverride = __esm({
19570
+ "src/lib/chat/endpointOverride.ts"() {
19571
+ "use strict";
19572
+ }
19573
+ });
19574
+
19552
19575
  // src/lib/memory/portalLlm.ts
19553
19576
  function defaultBaseUrl() {
19554
19577
  return typeof process !== "undefined" && process.env?.ANUMA_PORTAL_BASE_URL || BASE_URL;
@@ -19597,6 +19620,14 @@ async function resolvePortalAuthHeaders(auth, tag) {
19597
19620
  }
19598
19621
  async function callPortalJsonCompletion(req) {
19599
19622
  const log = getLogger();
19623
+ let endpoint = "/api/v1/chat/completions";
19624
+ if (req.endpointOverride !== void 0) {
19625
+ const overrideValidation = validateEndpointOverride(req.endpointOverride);
19626
+ if (!overrideValidation.valid) {
19627
+ throw new Error(overrideValidation.message);
19628
+ }
19629
+ endpoint = overrideValidation.endpoint;
19630
+ }
19600
19631
  const maxAttempts = Math.max(1, req.maxAttempts ?? 3);
19601
19632
  const startedAt = Date.now();
19602
19633
  const overBudget = () => req.totalTimeoutMs !== void 0 && Date.now() - startedAt >= req.totalTimeoutMs;
@@ -19612,7 +19643,7 @@ async function callPortalJsonCompletion(req) {
19612
19643
  Math.min(req.timeoutMs ?? 6e4, req.totalTimeoutMs - (Date.now() - startedAt))
19613
19644
  )
19614
19645
  } : req;
19615
- const outcome = await attemptPortalJson(attemptReq);
19646
+ const outcome = await attemptPortalJson(attemptReq, endpoint);
19616
19647
  if (outcome.kind === "ok") return outcome.value;
19617
19648
  if (outcome.kind === "terminal") {
19618
19649
  log.warn(`[${req.tag}] ${outcome.reason}`);
@@ -19635,7 +19666,7 @@ async function callPortalJsonCompletion(req) {
19635
19666
  }
19636
19667
  return null;
19637
19668
  }
19638
- async function attemptPortalJson(req) {
19669
+ async function attemptPortalJson(req, endpoint) {
19639
19670
  const baseUrl = req.baseUrl ?? defaultBaseUrl();
19640
19671
  const fetchImpl = req.fetchFn ?? fetch;
19641
19672
  const timeoutMs = req.timeoutMs ?? 6e4;
@@ -19660,7 +19691,7 @@ async function attemptPortalJson(req) {
19660
19691
  if (!supportsJsonObjectFormat) delete requestBody.response_format;
19661
19692
  let response;
19662
19693
  try {
19663
- response = await fetchImpl(`${baseUrl}/api/v1/chat/completions`, {
19694
+ response = await fetchImpl(`${baseUrl}${endpoint}`, {
19664
19695
  method: "POST",
19665
19696
  headers: { ...authHeaders, "Content-Type": "application/json" },
19666
19697
  body: JSON.stringify(requestBody),
@@ -19757,6 +19788,7 @@ var init_portalLlm = __esm({
19757
19788
  "src/lib/memory/portalLlm.ts"() {
19758
19789
  "use strict";
19759
19790
  init_clientConfig();
19791
+ init_endpointOverride();
19760
19792
  init_logger();
19761
19793
  RESPONSE_FORMAT_OK = /* @__PURE__ */ new Set(["openai", "inclusionai", "deepseek"]);
19762
19794
  RESPONSE_SCHEMA_OK = /* @__PURE__ */ new Set(["openai"]);
@@ -22426,14 +22458,27 @@ function validate2(parsed, newContent, validIds) {
22426
22458
  const c2 = typeof obj.content === "string" ? obj.content.trim() : "";
22427
22459
  return { action: "create", content: c2.length > 0 ? c2 : newContent };
22428
22460
  }
22429
- const targetId = typeof obj.targetId === "string" ? obj.targetId : null;
22430
- if (!targetId || !validIds.has(targetId)) return null;
22431
22461
  if (action === "noop") {
22432
- return { action: "noop", targetId };
22462
+ const targetId2 = typeof obj.targetId === "string" ? obj.targetId : null;
22463
+ if (!targetId2 || !validIds.has(targetId2)) return null;
22464
+ return { action: "noop", targetId: targetId2 };
22433
22465
  }
22434
22466
  const c = typeof obj.content === "string" ? obj.content.trim() : "";
22435
22467
  if (c.length === 0) return null;
22436
- return { action, targetId, content: c };
22468
+ if (action === "supersede") {
22469
+ const rawIds = [
22470
+ ...Array.isArray(obj.targetIds) ? obj.targetIds : [],
22471
+ ...typeof obj.targetId === "string" ? [obj.targetId] : []
22472
+ ];
22473
+ const targetIds = [
22474
+ ...new Set(rawIds.filter((id) => typeof id === "string" && validIds.has(id)))
22475
+ ];
22476
+ if (targetIds.length === 0) return null;
22477
+ return { action: "supersede", targetId: targetIds[0], targetIds, content: c };
22478
+ }
22479
+ const targetId = typeof obj.targetId === "string" ? obj.targetId : null;
22480
+ if (!targetId || !validIds.has(targetId)) return null;
22481
+ return { action: "update", targetId, content: c };
22437
22482
  }
22438
22483
  var DEFAULT_MODEL3, DEFAULT_CONSOLIDATE_ATTEMPTS, DEFAULT_CONSOLIDATE_TOTAL_TIMEOUT_MS, SYSTEM_PROMPT3;
22439
22484
  var init_consolidate = __esm({
@@ -22453,7 +22498,7 @@ Decide one of four actions:
22453
22498
 
22454
22499
  - "create": the new memory describes a distinct fact not covered by any existing memory. Write it as new.
22455
22500
  - "update": the new memory describes the SAME FACET as exactly one existing memory and is COMPATIBLE with it \u2014 it adds detail, fixes an incomplete value, or rephrases the same still-true fact. Return targetId of that existing memory + the consolidated content (richest version of the two).
22456
- - "supersede": the new memory REPLACES a standing fact whose value has CHANGED and is now INCOMPATIBLE with an existing memory \u2014 the old fact was true before but is false now (a state change). Return targetId of the STALE existing memory to retire + content = the NEW fact. The old memory is kept as history, not overwritten or deleted; do not re-state it.
22501
+ - "supersede": the new memory REPLACES a standing fact whose value has CHANGED and is now INCOMPATIBLE with an existing memory \u2014 the old fact was true before but is false now (a state change). Return "targetIds": an array of the ids of EVERY existing candidate that describes the SAME standing attribute now being changed \u2014 retire ALL stale duplicates of the old value, not just one (the candidate list may contain several paraphrases of the same old fact) + content = the NEW fact. The old memories are kept as history, not overwritten or deleted; do not re-state them.
22457
22502
  - "noop": the new memory is already fully captured by an existing memory \u2014 same facet, no new information. Skip the write.
22458
22503
 
22459
22504
  RULES (carry these strictly):
@@ -22469,14 +22514,15 @@ RULES (carry these strictly):
22469
22514
  OUTPUT \u2014 strict JSON, no prose:
22470
22515
  {
22471
22516
  "action": "create" | "update" | "supersede" | "noop",
22472
- "targetId": "<existing memory id \u2014 required for update/supersede/noop, omit for create>",
22517
+ "targetId": "<existing memory id \u2014 required for update/noop, omit for create/supersede>",
22518
+ "targetIds": ["<id>", ...],
22473
22519
  "content": "<content \u2014 required for create/update/supersede, omit for noop>"
22474
22520
  }
22475
22521
 
22476
- For "create": content is the new memory verbatim (or a slight refinement).
22477
- For "update": content is the merged richest-version, \u226480 words.
22478
- For "supersede": content is the NEW fact only (\u226480 words); targetId is the stale memory being retired.
22479
- For "noop": no content (existing memory is already correct).`;
22522
+ For "create": content is the new memory verbatim (or a slight refinement); omit targetId/targetIds.
22523
+ For "update": content is the merged richest-version, \u226480 words; targetId is the single memory to update.
22524
+ 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).
22525
+ For "noop": no content (existing memory is already correct); targetId is that memory.`;
22480
22526
  }
22481
22527
  });
22482
22528
 
@@ -27125,14 +27171,15 @@ init_portalLlm();
27125
27171
 
27126
27172
  // src/lib/memory/retain.ts
27127
27173
  init_operations3();
27174
+ init_logger();
27128
27175
  init_constants();
27129
27176
  init_embeddings();
27130
27177
  init_vector();
27131
27178
  init_searchTool();
27132
- var DEFAULT_AUTO_MERGE_THRESHOLD = 0.85;
27179
+ var DEFAULT_AUTO_MERGE_THRESHOLD = 0.8;
27133
27180
  var DEFAULT_SCOPE = "private";
27134
- var DEFAULT_CONSOLIDATE_THRESHOLD = 0.65;
27135
- var DEFAULT_CONSOLIDATE_TOP_K = 5;
27181
+ var DEFAULT_CONSOLIDATE_THRESHOLD = 0.55;
27182
+ var DEFAULT_CONSOLIDATE_TOP_K = 20;
27136
27183
  async function retain(content, ctx, options = {}) {
27137
27184
  const trimmed = content.trim();
27138
27185
  if (trimmed.length === 0) {
@@ -27141,18 +27188,18 @@ async function retain(content, ctx, options = {}) {
27141
27188
  const enableAutoMerge = options.enableAutoMerge ?? true;
27142
27189
  const threshold = options.autoMergeThreshold ?? DEFAULT_AUTO_MERGE_THRESHOLD;
27143
27190
  const resolvedScope = options.scope ?? DEFAULT_SCOPE;
27144
- let supersedeTargetId;
27191
+ let supersedeTargetIds = [];
27145
27192
  let supersedeContent;
27146
27193
  if (enableAutoMerge) {
27147
27194
  if (options.consolidateOptions) {
27148
27195
  const outcome = await tryConsolidate(trimmed, ctx, options);
27149
27196
  if (outcome) {
27150
27197
  if ("done" in outcome) return outcome.done;
27151
- supersedeTargetId = outcome.supersede;
27198
+ supersedeTargetIds = outcome.supersede;
27152
27199
  supersedeContent = outcome.content;
27153
27200
  }
27154
27201
  }
27155
- if (!supersedeTargetId) {
27202
+ if (supersedeTargetIds.length === 0) {
27156
27203
  const matches = await searchVaultMemories(
27157
27204
  trimmed,
27158
27205
  ctx.vaultCtx,
@@ -27256,18 +27303,42 @@ async function retain(content, ctx, options = {}) {
27256
27303
  // memory's tier. The DB op re-validates against the known set.
27257
27304
  ...options.trustTier !== void 0 && { trustTier: options.trustTier }
27258
27305
  };
27259
- if (supersedeTargetId) {
27306
+ if (supersedeTargetIds.length > 0) {
27307
+ const [primaryTargetId, ...restTargetIds] = supersedeTargetIds;
27260
27308
  const { created: created2, retired } = await createSupersedingMemoryOp(
27261
27309
  ctx.vaultCtx,
27262
27310
  createOpts,
27263
- supersedeTargetId
27311
+ primaryTargetId
27264
27312
  );
27265
27313
  if (created2 && retired) {
27266
27314
  ctx.vaultCache.set(created2.uniqueId, Float32Array.from(embedding));
27315
+ const liveLeftovers = [];
27316
+ for (const staleId of restTargetIds) {
27317
+ let ok = false;
27318
+ try {
27319
+ ok = await supersedeVaultMemoryOp(ctx.vaultCtx, staleId, created2.uniqueId) === true;
27320
+ } catch {
27321
+ }
27322
+ if (ok) continue;
27323
+ const stillLive = await getVaultMemoryOp(ctx.vaultCtx, staleId).catch(() => null);
27324
+ if (stillLive && !stillLive.supersededBy) liveLeftovers.push(staleId);
27325
+ }
27326
+ if (liveLeftovers.length > 0) {
27327
+ getLogger().warn(
27328
+ "[memory/retain] supersede left duplicate row(s) live \u2014 will reconcile at next consolidation",
27329
+ {
27330
+ newMemoryId: created2.uniqueId,
27331
+ leftover: liveLeftovers.length,
27332
+ // Log the specific ids (opaque record ids, not content) so an
27333
+ // operator can identify exactly which rows are still active.
27334
+ leftoverIds: liveLeftovers
27335
+ }
27336
+ );
27337
+ }
27267
27338
  return {
27268
27339
  action: "supersede",
27269
27340
  memoryId: created2.uniqueId,
27270
- targetId: supersedeTargetId,
27341
+ targetId: primaryTargetId,
27271
27342
  proofCount: 1
27272
27343
  };
27273
27344
  }
@@ -27353,10 +27424,16 @@ async function tryConsolidate(trimmed, ctx, options) {
27353
27424
  const { consolidateMemory: doConsolidate } = await Promise.resolve().then(() => (init_consolidate(), consolidate_exports));
27354
27425
  const decision = await doConsolidate(trimmed, candidates, consolidateOptions);
27355
27426
  if (decision.action === "create") return null;
27356
- if (decision.action === "supersede" && decision.targetId && decision.content) {
27357
- const existing = await getVaultMemoryOp(ctx.vaultCtx, decision.targetId);
27358
- if (!existing || existing.supersededBy) return null;
27359
- return { supersede: decision.targetId, content: decision.content };
27427
+ if (decision.action === "supersede" && decision.content) {
27428
+ const requestedIds = decision.targetIds?.length ? decision.targetIds : decision.targetId ? [decision.targetId] : [];
27429
+ if (requestedIds.length === 0) return null;
27430
+ const valid = [];
27431
+ for (const id of requestedIds) {
27432
+ const existing = await getVaultMemoryOp(ctx.vaultCtx, id);
27433
+ if (existing && !existing.supersededBy) valid.push(id);
27434
+ }
27435
+ if (valid.length === 0) return null;
27436
+ return { supersede: valid, content: decision.content };
27360
27437
  }
27361
27438
  if (decision.action === "noop" && decision.targetId) {
27362
27439
  const existing = await getVaultMemoryOp(ctx.vaultCtx, decision.targetId);
@@ -29132,6 +29209,9 @@ When a memory refers to one of these, reuse the EXACT existing name (same spelli
29132
29209
  ...options.apiKey !== void 0 && { apiKey: options.apiKey },
29133
29210
  ...options.getToken !== void 0 && { getToken: options.getToken },
29134
29211
  ...options.baseUrl !== void 0 && { baseUrl: options.baseUrl },
29212
+ ...options.endpointOverride !== void 0 && {
29213
+ endpointOverride: options.endpointOverride
29214
+ },
29135
29215
  model: options.model ?? DEFAULT_EXTRACTION_MODEL,
29136
29216
  systemPrompt: SYSTEM_PROMPT6,
29137
29217
  userMessage: `${vocabularyNote}Memories:
@@ -31283,6 +31363,7 @@ init_serverSentEvents_gen();
31283
31363
  init_clientConfig();
31284
31364
  init_embeddings();
31285
31365
  init_redactor();
31366
+ init_endpointOverride();
31286
31367
 
31287
31368
  // src/lib/chat/runHooks.ts
31288
31369
  function composeHooks(hooks) {
@@ -33433,22 +33514,6 @@ function linkAbortSignals(a, b) {
33433
33514
  }
33434
33515
  };
33435
33516
  }
33436
- function validateEndpointOverride(override) {
33437
- const trimmed = override.trim();
33438
- if (trimmed === "") {
33439
- return {
33440
- valid: false,
33441
- message: 'endpointOverride must be a non-empty, root-relative path (e.g. "/api/v1/utility/responses"); received an empty or whitespace-only string'
33442
- };
33443
- }
33444
- if (trimmed.startsWith("//") || trimmed.includes("://")) {
33445
- return {
33446
- valid: false,
33447
- message: `endpointOverride must be a root-relative path (e.g. "/api/v1/utility/responses"), not a protocol-relative or absolute URL; received "${trimmed}"`
33448
- };
33449
- }
33450
- return { valid: true, endpoint: trimmed.startsWith("/") ? trimmed : `/${trimmed}` };
33451
- }
33452
33517
  async function runToolLoop(options) {
33453
33518
  const {
33454
33519
  model,
@@ -2516,6 +2516,33 @@ type StreamingTransportResult = {
2516
2516
  * transport both honor this contract.
2517
2517
  */
2518
2518
  type StreamingTransport = (options: StreamingTransportOptions) => StreamingTransportResult;
2519
+ /**
2520
+ * Framework-agnostic tool execution loop.
2521
+ *
2522
+ * Sends a streaming completion request, auto-executes tools that have executors,
2523
+ * feeds results back to the model, and repeats until the model stops emitting
2524
+ * tool calls or hits the iteration limit.
2525
+ *
2526
+ * This is the same loop that powers `useChat` in the React SDK, extracted so
2527
+ * it can be used from Node.js servers, CLI tools, and background workers.
2528
+ *
2529
+ * @example
2530
+ * ```ts
2531
+ * import { runToolLoop } from "@anuma/sdk/server";
2532
+ *
2533
+ * const result = await runToolLoop({
2534
+ * messages: [{ role: "user", content: [{ type: "text", text: "What's the weather?" }] }],
2535
+ * model: "fireworks/accounts/fireworks/models/kimi-k2p5",
2536
+ * token: "my-api-token",
2537
+ * tools: [{
2538
+ * type: "function",
2539
+ * function: { name: "get_weather", parameters: { type: "object", properties: { city: { type: "string" } } } },
2540
+ * executor: async ({ city }) => fetchWeather(city),
2541
+ * }],
2542
+ * onData: (chunk) => process.stdout.write(chunk),
2543
+ * });
2544
+ * ```
2545
+ */
2519
2546
  declare function runToolLoop(options: RunToolLoopOptions): Promise<RunToolLoopResult>;
2520
2547
 
2521
2548
  /**
@@ -8146,6 +8173,15 @@ interface TopicExtractionInput {
8146
8173
  */
8147
8174
  interface TopicExtractOptions extends PortalLlmAuth {
8148
8175
  baseUrl?: string;
8176
+ /**
8177
+ * Optional per-call request path override, forwarded to
8178
+ * {@link callPortalJsonCompletion}. When set, topic extraction POSTs to
8179
+ * `baseUrl + endpointOverride` instead of the default
8180
+ * `/api/v1/chat/completions` — path only, body unchanged. Lets callers route
8181
+ * this internal-utility pass to a dedicated endpoint. Invalid values throw at
8182
+ * call time (see {@link validateEndpointOverride}).
8183
+ */
8184
+ endpointOverride?: string;
8149
8185
  /** Defaults to {@link DEFAULT_EXTRACTION_MODEL} — the sanctioned extraction
8150
8186
  * model. Don't point this at a second model without an eval. */
8151
8187
  model?: string;
@@ -2516,6 +2516,33 @@ type StreamingTransportResult = {
2516
2516
  * transport both honor this contract.
2517
2517
  */
2518
2518
  type StreamingTransport = (options: StreamingTransportOptions) => StreamingTransportResult;
2519
+ /**
2520
+ * Framework-agnostic tool execution loop.
2521
+ *
2522
+ * Sends a streaming completion request, auto-executes tools that have executors,
2523
+ * feeds results back to the model, and repeats until the model stops emitting
2524
+ * tool calls or hits the iteration limit.
2525
+ *
2526
+ * This is the same loop that powers `useChat` in the React SDK, extracted so
2527
+ * it can be used from Node.js servers, CLI tools, and background workers.
2528
+ *
2529
+ * @example
2530
+ * ```ts
2531
+ * import { runToolLoop } from "@anuma/sdk/server";
2532
+ *
2533
+ * const result = await runToolLoop({
2534
+ * messages: [{ role: "user", content: [{ type: "text", text: "What's the weather?" }] }],
2535
+ * model: "fireworks/accounts/fireworks/models/kimi-k2p5",
2536
+ * token: "my-api-token",
2537
+ * tools: [{
2538
+ * type: "function",
2539
+ * function: { name: "get_weather", parameters: { type: "object", properties: { city: { type: "string" } } } },
2540
+ * executor: async ({ city }) => fetchWeather(city),
2541
+ * }],
2542
+ * onData: (chunk) => process.stdout.write(chunk),
2543
+ * });
2544
+ * ```
2545
+ */
2519
2546
  declare function runToolLoop(options: RunToolLoopOptions): Promise<RunToolLoopResult>;
2520
2547
 
2521
2548
  /**
@@ -8146,6 +8173,15 @@ interface TopicExtractionInput {
8146
8173
  */
8147
8174
  interface TopicExtractOptions extends PortalLlmAuth {
8148
8175
  baseUrl?: string;
8176
+ /**
8177
+ * Optional per-call request path override, forwarded to
8178
+ * {@link callPortalJsonCompletion}. When set, topic extraction POSTs to
8179
+ * `baseUrl + endpointOverride` instead of the default
8180
+ * `/api/v1/chat/completions` — path only, body unchanged. Lets callers route
8181
+ * this internal-utility pass to a dedicated endpoint. Invalid values throw at
8182
+ * call time (see {@link validateEndpointOverride}).
8183
+ */
8184
+ endpointOverride?: string;
8149
8185
  /** Defaults to {@link DEFAULT_EXTRACTION_MODEL} — the sanctioned extraction
8150
8186
  * model. Don't point this at a second model without an eval. */
8151
8187
  model?: string;