@anuma/sdk 6.30.1 → 6.31.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.
@@ -19,12 +19,28 @@ var noopLogger = {
19
19
  error: () => {
20
20
  }
21
21
  };
22
- var currentLogger = consoleLogger;
22
+ var LOGGER_STATE_KEY = /* @__PURE__ */ Symbol.for("@anuma/sdk/logger/v1");
23
+ var loggerGlobal = globalThis;
24
+ var fallbackLoggerState = { logger: consoleLogger };
25
+ var fallbackWarned = false;
26
+ function getLoggerState() {
27
+ try {
28
+ return loggerGlobal[LOGGER_STATE_KEY] ?? (loggerGlobal[LOGGER_STATE_KEY] = fallbackLoggerState);
29
+ } catch {
30
+ if (!fallbackWarned) {
31
+ fallbackWarned = true;
32
+ fallbackLoggerState.logger.warn(
33
+ "@anuma/sdk: the global object rejects new properties. This entrypoint keeps its own logger, so setLogger does not reach other entrypoints."
34
+ );
35
+ }
36
+ return fallbackLoggerState;
37
+ }
38
+ }
23
39
  function setLogger(logger) {
24
- currentLogger = logger;
40
+ getLoggerState().logger = logger;
25
41
  }
26
42
  function getLogger() {
27
- return currentLogger;
43
+ return getLoggerState().logger;
28
44
  }
29
45
 
30
46
  // src/lib/pii/patterns.ts
@@ -726,13 +742,21 @@ function responsesExtra(extra) {
726
742
  }
727
743
  return out;
728
744
  }
745
+ function splitEndpoint(endpoint) {
746
+ const cut = endpoint.search(/[?#]/);
747
+ const rawPath = cut === -1 ? endpoint : endpoint.slice(0, cut);
748
+ return {
749
+ path: rawPath.replace(/\/+$/, ""),
750
+ rest: cut === -1 ? "" : endpoint.slice(cut)
751
+ };
752
+ }
729
753
  var FOREIGN_ENDPOINT_SUFFIX = {
730
754
  chat: "/responses",
731
755
  responses: "/chat/completions"
732
756
  };
733
757
  function assertTransportMatchesEndpoint(transport, endpoint) {
734
758
  const foreign = FOREIGN_ENDPOINT_SUFFIX[transport];
735
- if (!endpoint.endsWith(foreign)) return;
759
+ if (!splitEndpoint(endpoint).path.endsWith(foreign)) return;
736
760
  throw new Error(
737
761
  `endpointOverride "${endpoint}" is the other transport's endpoint, but transport is "${transport}". The request body is built for the transport, so this sends the wrong shape and 400s without retry.`
738
762
  );
@@ -778,14 +802,20 @@ async function resolvePortalAuthHeaders(auth, tag) {
778
802
  }
779
803
  async function callPortalJsonCompletion(req) {
780
804
  const log = getLogger();
781
- let endpoint = req.transport === "responses" ? "/api/v1/responses" : "/api/v1/chat/completions";
805
+ const transport = req.transport ?? (requiresResponsesTransport(req.model) ? "responses" : "chat");
806
+ const autoUpgraded = req.transport === void 0 && transport === "responses";
807
+ let endpoint = transport === "responses" ? "/api/v1/responses" : "/api/v1/chat/completions";
782
808
  if (req.endpointOverride !== void 0) {
783
809
  const overrideValidation = validateEndpointOverride(req.endpointOverride);
784
810
  if (!overrideValidation.valid) {
785
811
  throw new Error(overrideValidation.message);
786
812
  }
787
813
  endpoint = overrideValidation.endpoint;
788
- assertTransportMatchesEndpoint(req.transport ?? "chat", endpoint);
814
+ const { path, rest } = splitEndpoint(endpoint);
815
+ if (autoUpgraded && path.endsWith("/chat/completions")) {
816
+ endpoint = `${path.slice(0, -"/chat/completions".length)}/responses${rest}`;
817
+ }
818
+ assertTransportMatchesEndpoint(transport, endpoint);
789
819
  }
790
820
  const maxAttempts = Math.max(1, req.maxAttempts ?? 3);
791
821
  const startedAt = Date.now();
@@ -810,7 +840,10 @@ async function callPortalJsonCompletion(req) {
810
840
  ),
811
841
  ...reinforce && { reinforceJsonContract: true }
812
842
  } : reinforce ? { ...req, reinforceJsonContract: true } : req;
813
- const outcome = await attemptPortalJson(attemptReq, endpoint);
843
+ const outcome = await attemptPortalJson(
844
+ { ...attemptReq, transport },
845
+ endpoint
846
+ );
814
847
  if (outcome.kind === "ok") {
815
848
  req.onAttempt?.({ attempt, ok: true });
816
849
  return outcome.value;
@@ -962,6 +995,13 @@ async function attemptPortalJson(req, endpoint) {
962
995
  }
963
996
  clearTimeout(timer);
964
997
  logPortalUsage(body, req.tag);
998
+ if (isModerationResponse(body)) {
999
+ return {
1000
+ kind: "terminal",
1001
+ code: "content-flagged",
1002
+ reason: "portal moderation refused the request"
1003
+ };
1004
+ }
965
1005
  const rawContent = extractCompletionContent(body);
966
1006
  if (!rawContent) {
967
1007
  return {
@@ -1047,6 +1087,9 @@ function logPortalUsage(body, tag) {
1047
1087
  } catch {
1048
1088
  }
1049
1089
  }
1090
+ function isModerationResponse(body) {
1091
+ return typeof body === "object" && body !== null && body.id === "moderation";
1092
+ }
1050
1093
  function extractCompletionContent(body) {
1051
1094
  if (typeof body !== "object" || body === null) return null;
1052
1095
  const responsesText = extractResponsesContent(body);
@@ -2,7 +2,7 @@ import {
2
2
  callPortalJsonCompletion,
3
3
  notifyConsolidationFallback,
4
4
  resolvePiiRedactor
5
- } from "./chunk-LBIKGBXE.mjs";
5
+ } from "./chunk-XNQRJBIH.mjs";
6
6
  import "./chunk-GYKJOWEO.mjs";
7
7
 
8
8
  // src/lib/memory/consolidate.ts
@@ -76,13 +76,26 @@ var init_clientConfig = __esm({
76
76
  });
77
77
 
78
78
  // src/lib/logger.ts
79
+ function getLoggerState() {
80
+ try {
81
+ return loggerGlobal[LOGGER_STATE_KEY] ?? (loggerGlobal[LOGGER_STATE_KEY] = fallbackLoggerState);
82
+ } catch {
83
+ if (!fallbackWarned) {
84
+ fallbackWarned = true;
85
+ fallbackLoggerState.logger.warn(
86
+ "@anuma/sdk: the global object rejects new properties. This entrypoint keeps its own logger, so setLogger does not reach other entrypoints."
87
+ );
88
+ }
89
+ return fallbackLoggerState;
90
+ }
91
+ }
79
92
  function setLogger(logger) {
80
- currentLogger = logger;
93
+ getLoggerState().logger = logger;
81
94
  }
82
95
  function getLogger() {
83
- return currentLogger;
96
+ return getLoggerState().logger;
84
97
  }
85
- var consoleLogger, noopLogger, currentLogger;
98
+ var consoleLogger, noopLogger, LOGGER_STATE_KEY, loggerGlobal, fallbackLoggerState, fallbackWarned;
86
99
  var init_logger = __esm({
87
100
  "src/lib/logger.ts"() {
88
101
  "use strict";
@@ -102,7 +115,10 @@ var init_logger = __esm({
102
115
  error: () => {
103
116
  }
104
117
  };
105
- currentLogger = consoleLogger;
118
+ LOGGER_STATE_KEY = /* @__PURE__ */ Symbol.for("@anuma/sdk/logger/v1");
119
+ loggerGlobal = globalThis;
120
+ fallbackLoggerState = { logger: consoleLogger };
121
+ fallbackWarned = false;
106
122
  }
107
123
  });
108
124
 
@@ -828,9 +844,17 @@ function responsesExtra(extra) {
828
844
  }
829
845
  return out;
830
846
  }
847
+ function splitEndpoint(endpoint) {
848
+ const cut = endpoint.search(/[?#]/);
849
+ const rawPath = cut === -1 ? endpoint : endpoint.slice(0, cut);
850
+ return {
851
+ path: rawPath.replace(/\/+$/, ""),
852
+ rest: cut === -1 ? "" : endpoint.slice(cut)
853
+ };
854
+ }
831
855
  function assertTransportMatchesEndpoint(transport, endpoint) {
832
856
  const foreign = FOREIGN_ENDPOINT_SUFFIX[transport];
833
- if (!endpoint.endsWith(foreign)) return;
857
+ if (!splitEndpoint(endpoint).path.endsWith(foreign)) return;
834
858
  throw new Error(
835
859
  `endpointOverride "${endpoint}" is the other transport's endpoint, but transport is "${transport}". The request body is built for the transport, so this sends the wrong shape and 400s without retry.`
836
860
  );
@@ -875,14 +899,20 @@ async function resolvePortalAuthHeaders(auth, tag) {
875
899
  }
876
900
  async function callPortalJsonCompletion(req) {
877
901
  const log = getLogger();
878
- let endpoint = req.transport === "responses" ? "/api/v1/responses" : "/api/v1/chat/completions";
902
+ const transport = req.transport ?? (requiresResponsesTransport(req.model) ? "responses" : "chat");
903
+ const autoUpgraded = req.transport === void 0 && transport === "responses";
904
+ let endpoint = transport === "responses" ? "/api/v1/responses" : "/api/v1/chat/completions";
879
905
  if (req.endpointOverride !== void 0) {
880
906
  const overrideValidation = validateEndpointOverride(req.endpointOverride);
881
907
  if (!overrideValidation.valid) {
882
908
  throw new Error(overrideValidation.message);
883
909
  }
884
910
  endpoint = overrideValidation.endpoint;
885
- assertTransportMatchesEndpoint(req.transport ?? "chat", endpoint);
911
+ const { path, rest } = splitEndpoint(endpoint);
912
+ if (autoUpgraded && path.endsWith("/chat/completions")) {
913
+ endpoint = `${path.slice(0, -"/chat/completions".length)}/responses${rest}`;
914
+ }
915
+ assertTransportMatchesEndpoint(transport, endpoint);
886
916
  }
887
917
  const maxAttempts = Math.max(1, req.maxAttempts ?? 3);
888
918
  const startedAt = Date.now();
@@ -907,7 +937,10 @@ async function callPortalJsonCompletion(req) {
907
937
  ),
908
938
  ...reinforce && { reinforceJsonContract: true }
909
939
  } : reinforce ? { ...req, reinforceJsonContract: true } : req;
910
- const outcome = await attemptPortalJson(attemptReq, endpoint);
940
+ const outcome = await attemptPortalJson(
941
+ { ...attemptReq, transport },
942
+ endpoint
943
+ );
911
944
  if (outcome.kind === "ok") {
912
945
  req.onAttempt?.({ attempt, ok: true });
913
946
  return outcome.value;
@@ -1059,6 +1092,13 @@ async function attemptPortalJson(req, endpoint) {
1059
1092
  }
1060
1093
  clearTimeout(timer);
1061
1094
  logPortalUsage(body, req.tag);
1095
+ if (isModerationResponse(body)) {
1096
+ return {
1097
+ kind: "terminal",
1098
+ code: "content-flagged",
1099
+ reason: "portal moderation refused the request"
1100
+ };
1101
+ }
1062
1102
  const rawContent = extractCompletionContent(body);
1063
1103
  if (!rawContent) {
1064
1104
  return {
@@ -1144,6 +1184,9 @@ function logPortalUsage(body, tag) {
1144
1184
  } catch {
1145
1185
  }
1146
1186
  }
1187
+ function isModerationResponse(body) {
1188
+ return typeof body === "object" && body !== null && body.id === "moderation";
1189
+ }
1147
1190
  function extractCompletionContent(body) {
1148
1191
  if (typeof body !== "object" || body === null) return null;
1149
1192
  const responsesText = extractResponsesContent(body);
@@ -4169,6 +4212,9 @@ async function updateMessageEmbeddingOp(ctx, uniqueId, vector, embeddingModel) {
4169
4212
  await ctx.database.write(async () => {
4170
4213
  await message.update((msg) => {
4171
4214
  msg._setRaw("vector", JSON.stringify(vector));
4215
+ if (msg.origin === "chunks_discarded" && vector.length > 0) {
4216
+ msg._setRaw("origin", "message");
4217
+ }
4172
4218
  msg._setRaw("embedding_model", embeddingModel);
4173
4219
  });
4174
4220
  });
@@ -4185,6 +4231,9 @@ async function updateMessageChunksOp(ctx, uniqueId, chunks, embeddingModel) {
4185
4231
  await ctx.database.write(async () => {
4186
4232
  await message.update((msg) => {
4187
4233
  msg._setRaw("chunks", JSON.stringify(storedChunks));
4234
+ if (msg.origin === "chunks_discarded" && storedChunks.length > 0 && storedChunks.every((chunk) => chunk.vector.length > 0)) {
4235
+ msg._setRaw("origin", "message");
4236
+ }
4188
4237
  msg._setRaw("embedding_model", embeddingModel);
4189
4238
  });
4190
4239
  });
@@ -6336,6 +6385,9 @@ function clientToolToResponsesFormat(tool) {
6336
6385
  ...toolConfig.removeAfterExecution !== void 0 && {
6337
6386
  removeAfterExecution: toolConfig.removeAfterExecution
6338
6387
  },
6388
+ ...toolConfig.removeAfterResult !== void 0 && {
6389
+ removeAfterResult: toolConfig.removeAfterResult
6390
+ },
6339
6391
  ...toolConfig.executorTimeout !== void 0 && {
6340
6392
  executorTimeout: toolConfig.executorTimeout
6341
6393
  },
@@ -6369,6 +6421,9 @@ function clientToolToCompletionsFormat(tool) {
6369
6421
  ...toolConfig.removeAfterExecution !== void 0 && {
6370
6422
  removeAfterExecution: toolConfig.removeAfterExecution
6371
6423
  },
6424
+ ...toolConfig.removeAfterResult !== void 0 && {
6425
+ removeAfterResult: toolConfig.removeAfterResult
6426
+ },
6372
6427
  ...toolConfig.executorTimeout !== void 0 && {
6373
6428
  executorTimeout: toolConfig.executorTimeout
6374
6429
  },
@@ -7351,6 +7406,7 @@ function toolsToApiFormat(tools, apiType) {
7351
7406
  executor: _executor,
7352
7407
  skipContinuation: _skipContinuation,
7353
7408
  removeAfterExecution: _removeAfterExecution,
7409
+ removeAfterResult: _removeAfterResult,
7354
7410
  executorTimeout: _executorTimeout,
7355
7411
  dependsOn: _dependsOn,
7356
7412
  deAnonymizeArgs: _deAnonymizeArgs,
@@ -9795,9 +9851,11 @@ Executing tool: ${toolInfo}
9795
9851
  }
9796
9852
  if (tools && apiTools) {
9797
9853
  const successfullyExecutedNames = /* @__PURE__ */ new Set();
9854
+ const successfulResults = [];
9798
9855
  for (const r of executionResults) {
9799
9856
  if (!r.error && !isToolErrorResult(r.result) && "name" in r && r.name) {
9800
9857
  successfullyExecutedNames.add(r.name);
9858
+ successfulResults.push(r.result);
9801
9859
  }
9802
9860
  }
9803
9861
  if (successfullyExecutedNames.size > 0) {
@@ -9806,7 +9864,15 @@ Executing tool: ${toolInfo}
9806
9864
  const tc = t;
9807
9865
  const func = tc.function;
9808
9866
  const toolName = typeof func?.name === "string" ? func.name : typeof tc.name === "string" ? tc.name : void 0;
9809
- if (tc.removeAfterExecution === true && toolName && successfullyExecutedNames.has(toolName)) {
9867
+ if (!toolName) continue;
9868
+ const removeAfterResult = tc.removeAfterResult;
9869
+ if (tc.removeAfterExecution === true && successfullyExecutedNames.has(toolName) || typeof removeAfterResult === "function" && successfulResults.some((result) => {
9870
+ try {
9871
+ return removeAfterResult(result);
9872
+ } catch {
9873
+ return false;
9874
+ }
9875
+ })) {
9810
9876
  toolsToRemove.add(toolName);
9811
9877
  }
9812
9878
  }
@@ -14894,7 +14960,7 @@ async function getMemoriesNeedingTopicExtractionOp(ctx, options) {
14894
14960
  const id = raw.id;
14895
14961
  const topics = parseTopics(raw.topics);
14896
14962
  const linked = linkedNames.get(id);
14897
- if (topics !== null && topics.length > 0 && linksDivergeFromTopics(topics, linked ?? /* @__PURE__ */ new Set())) {
14963
+ if (topics !== null && linksDivergeFromTopics(topics, linked ?? /* @__PURE__ */ new Set())) {
14898
14964
  topicsToRelinkAll.push(id);
14899
14965
  continue;
14900
14966
  }
@@ -14988,18 +15054,20 @@ async function relinkMemoryTopicsOp(ctx, memoryIds) {
14988
15054
  }
14989
15055
  const relinked = [];
14990
15056
  for (const id of Array.from(new Set(memoryIds))) {
14991
- let record;
14992
15057
  try {
14993
- record = await ctx.vaultMemoryCollection.find(id);
14994
- } catch {
14995
- continue;
14996
- }
14997
- if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) continue;
14998
- const topics = parseTopics(record.topics);
14999
- if (topics === null || topics.length === 0) continue;
15000
- try {
15001
- await relinkMemoryEntitiesFromTopicsOp(entityCtx, id, topics);
15002
- relinked.push(id);
15058
+ await ctx.database.write(async (writer) => {
15059
+ let record;
15060
+ try {
15061
+ record = await ctx.vaultMemoryCollection.find(id);
15062
+ } catch {
15063
+ return;
15064
+ }
15065
+ if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) return;
15066
+ const topics = parseTopics(record.topics);
15067
+ if (topics === null) return;
15068
+ await writer.callWriter(() => relinkMemoryEntitiesFromTopicsOp(entityCtx, id, topics));
15069
+ relinked.push(id);
15070
+ });
15003
15071
  } catch (err) {
15004
15072
  getLogger().warn("[memory/topics] relink failed", err);
15005
15073
  }
@@ -21616,6 +21684,28 @@ var BatchFailureError = class extends Error {
21616
21684
  this.terminal = terminal;
21617
21685
  }
21618
21686
  };
21687
+ var FlaggedBatchError = class extends Error {
21688
+ };
21689
+ function extracted(result) {
21690
+ return result.failedCount === 0 && result.outcome !== "empty-after-retry";
21691
+ }
21692
+ function batchError(result) {
21693
+ const failure = result.failure;
21694
+ if (failure?.reason === "http-terminal") {
21695
+ const status = failure.httpStatus;
21696
+ if (status !== void 0 && REQUEST_REJECTED_STATUSES.has(status))
21697
+ return new BatchFailureError(
21698
+ `Extraction rejected the batch (HTTP ${status}); not retried this session`,
21699
+ true
21700
+ );
21701
+ return new AccountFailureError(
21702
+ `Extraction refused for the account (HTTP ${status ?? "unknown"}); retried next session`
21703
+ );
21704
+ }
21705
+ if (result.failedCount > 0 || failure !== void 0 && CONTENT_FAILURE_REASONS.has(failure.reason))
21706
+ return new BatchFailureError("Extraction batch incomplete; retained for retry", false);
21707
+ return new Error("Extraction batch incomplete; retained for retry");
21708
+ }
21619
21709
  var AccountFailureError = class extends Error {
21620
21710
  };
21621
21711
  var LockedSourcesError = class extends Error {
@@ -21624,6 +21714,16 @@ var LockedSourcesError = class extends Error {
21624
21714
  this.persistentIds = persistentIds;
21625
21715
  }
21626
21716
  };
21717
+ function splitTurns(messages) {
21718
+ const turns = [];
21719
+ for (const message of messages) {
21720
+ const last = turns[turns.length - 1];
21721
+ if (!last || message.role === "user" && last.some((m) => m.role === "user"))
21722
+ turns.push([message]);
21723
+ else last.push(message);
21724
+ }
21725
+ return turns;
21726
+ }
21627
21727
  function idsOf(job) {
21628
21728
  return JSON.parse(String(job._getRaw("message_ids")));
21629
21729
  }
@@ -21774,6 +21874,10 @@ function createDurableAutoExtractor(options) {
21774
21874
  skippedThisSession.add(job.id);
21775
21875
  return false;
21776
21876
  }
21877
+ if (error instanceof FlaggedBatchError) {
21878
+ await acknowledge(job, ids, loaded, true);
21879
+ return true;
21880
+ }
21777
21881
  const counted = error instanceof BatchFailureError || error instanceof LockedSourcesError && error.persistentIds.length > 0;
21778
21882
  if (!counted) return false;
21779
21883
  const prior = sessionFailures.get(job.id);
@@ -21900,37 +22004,50 @@ function createDurableAutoExtractor(options) {
21900
22004
  const messages = loaded.flatMap(
21901
22005
  (m) => m && m.conversationId === conversationId && (m.role === "user" || m.role === "assistant") ? [{ id: m.uniqueId, role: m.role, content: m.content }] : []
21902
22006
  );
21903
- let success = messages.length === 0;
21904
- let result;
21905
- if (messages.length) {
21906
- result = await extractBatch(
21907
- messages,
22007
+ const jobScope = currentScope() === "private" ? "private" : String(job._getRaw("scope"));
22008
+ const jobFolderId = job._getRaw("folder_id") ?? null;
22009
+ const runExtraction = async (batch, batchIds) => {
22010
+ const result = await extractBatch(
22011
+ batch,
21908
22012
  conversationId,
21909
- ids,
21910
- currentScope() === "private" ? "private" : String(job._getRaw("scope")),
21911
- job._getRaw("folder_id") ?? null
22013
+ batchIds,
22014
+ jobScope,
22015
+ jobFolderId
21912
22016
  );
21913
- success = result.failedCount === 0 && result.outcome !== "empty-after-retry";
21914
22017
  options.onTurnComplete?.(result);
21915
- }
21916
- if (!success) {
21917
- const failure = result?.failure;
21918
- if (failure?.reason === "http-terminal") {
21919
- const status = failure.httpStatus;
21920
- if (status !== void 0 && REQUEST_REJECTED_STATUSES.has(status))
21921
- throw new BatchFailureError(
21922
- `Extraction rejected the batch (HTTP ${status}); not retried this session`,
21923
- true
22018
+ return result;
22019
+ };
22020
+ const flaggedIds = /* @__PURE__ */ new Set();
22021
+ if (messages.length) {
22022
+ const result = await runExtraction(messages, ids);
22023
+ if (result.failure?.reason === "content-flagged") {
22024
+ const turns = splitTurns(messages);
22025
+ if (turns.length < 2)
22026
+ throw new FlaggedBatchError("Extraction batch refused by moderation; abandoned");
22027
+ for (const turn of turns) {
22028
+ const turnResult = await runExtraction(
22029
+ turn,
22030
+ turn.map((m) => m.id)
21924
22031
  );
21925
- throw new AccountFailureError(
21926
- `Extraction refused for the account (HTTP ${status ?? "unknown"}); retried next session`
21927
- );
21928
- }
21929
- if ((result?.failedCount ?? 0) > 0 || failure !== void 0 && CONTENT_FAILURE_REASONS.has(failure.reason))
21930
- throw new BatchFailureError("Extraction batch incomplete; retained for retry", false);
21931
- throw new Error("Extraction batch incomplete; retained for retry");
22032
+ if (turnResult.failure?.reason === "content-flagged")
22033
+ for (const m of turn) flaggedIds.add(m.id);
22034
+ else if (!extracted(turnResult)) throw batchError(turnResult);
22035
+ }
22036
+ if (flaggedIds.size)
22037
+ reportError(
22038
+ new Error(
22039
+ `Dropped ${flaggedIds.size} source id(s) of an extraction batch refused by moderation`
22040
+ ),
22041
+ conversationId
22042
+ );
22043
+ } else if (!extracted(result)) throw batchError(result);
21932
22044
  }
21933
- await acknowledge(job, ids, loaded, false);
22045
+ await acknowledge(
22046
+ job,
22047
+ ids,
22048
+ loaded,
22049
+ ids.slice(-2).some((id) => flaggedIds.has(id))
22050
+ );
21934
22051
  scheduleAfterSuccess = true;
21935
22052
  } catch (error) {
21936
22053
  attempts.set(job.id, attempted + 1);
@@ -25171,10 +25288,10 @@ function useChatStorage(options) {
25171
25288
  let skipStorageEmbeddings = null;
25172
25289
  let skipStorageEmbeddingsFailed = false;
25173
25290
  if (needsEmbeddings2 && getToken) {
25174
- const extracted2 = extractUserMessageFromMessages(messages);
25291
+ const extracted3 = extractUserMessageFromMessages(messages);
25175
25292
  const messageContent = resolveStoredUserContent(
25176
25293
  storedUserContent,
25177
- extracted2?.content ?? ""
25294
+ extracted3?.content ?? ""
25178
25295
  );
25179
25296
  if (messageContent.length >= MIN_CONTENT_LENGTH_FOR_TOOLS) {
25180
25297
  const embeddingOptions = {
@@ -25331,15 +25448,15 @@ function useChatStorage(options) {
25331
25448
  skipped: true
25332
25449
  };
25333
25450
  }
25334
- const extracted = extractUserMessageFromMessages(messages);
25335
- if (!extracted || !extracted.content && !extracted.files?.length) {
25451
+ const extracted2 = extractUserMessageFromMessages(messages);
25452
+ if (!extracted2 || !extracted2.content && !extracted2.files?.length) {
25336
25453
  return {
25337
25454
  data: null,
25338
25455
  error: "No user message found in messages array"
25339
25456
  };
25340
25457
  }
25341
- const contentForStorage = resolveStoredUserContent(storedUserContent, extracted.content);
25342
- const filesForStorage = files ?? extracted.files;
25458
+ const contentForStorage = resolveStoredUserContent(storedUserContent, extracted2.content);
25459
+ const filesForStorage = files ?? extracted2.files;
25343
25460
  let fileContextForRequest;
25344
25461
  let fileContextIsCurrentTurn = false;
25345
25462
  let preprocessedFileIds = [];
@@ -25798,9 +25915,9 @@ function useChatStorage(options) {
25798
25915
  if (result.error || !result.data) {
25799
25916
  const abortedResult = result;
25800
25917
  if (abortedResult.error === "Request aborted") {
25801
- const extracted2 = abortedResult.data ? extractAssistantText(abortedResult.data) : { content: "", thinking: void 0 };
25802
- const assistantContent2 = extracted2.content;
25803
- const abortedThinkingContent = extracted2.thinking;
25918
+ const extracted3 = abortedResult.data ? extractAssistantText(abortedResult.data) : { content: "", thinking: void 0 };
25919
+ const assistantContent2 = extracted3.content;
25920
+ const abortedThinkingContent = extracted3.thinking;
25804
25921
  const responseModel = abortedResult.data?.model || model || "";
25805
25922
  let storedAssistantMessage2;
25806
25923
  try {
@@ -2138,6 +2138,21 @@ type ToolConfig = LlmapiChatCompletionTool & {
2138
2138
  * execution fails, allowing the model to retry.
2139
2139
  */
2140
2140
  removeAfterExecution?: boolean;
2141
+ /**
2142
+ * Remove this tool for the rest of the turn once ANY tool returns a
2143
+ * successful result this predicate accepts — its own or another's. Errors
2144
+ * never count, as for `removeAfterExecution`.
2145
+ *
2146
+ * Only results of tools executed on this client are offered; tools the
2147
+ * portal runs server-side never reach the predicate. A predicate that throws
2148
+ * is treated as not matching.
2149
+ *
2150
+ * For a family of tools that has to leave together: when one of them
2151
+ * declines an out-of-scope request, the model otherwise works through its
2152
+ * siblings round after round until the round cap. Give every member the same
2153
+ * predicate and the first refusal removes the whole family.
2154
+ */
2155
+ removeAfterResult?: (result: unknown) => boolean;
2141
2156
  /**
2142
2157
  * Timeout in milliseconds for this tool's executor.
2143
2158
  * Overrides the default 30-second timeout.
@@ -4995,6 +5010,7 @@ type MessageFeedback = "like" | "dislike" | null;
4995
5010
  * - `tool_result`: the hidden `[Tool Execution Results]` row built from a
4996
5011
  * turn's auto-executed tool results. Skipped by the embedding sweep (see
4997
5012
  * `memoryEngine/embeddings`) AND hidden from the transcript.
5013
+ * - `message`: an ordinary message with an index after explicit repair.
4998
5014
  * - `chunks_discarded`: an ordinary, still-rendered message whose chunk vectors
4999
5015
  * were built over `enc:v3:` ciphertext (sdk#864) and have been discarded
5000
5016
  * rather than re-embedded at the user's own expense (client#5618). Skipped by
@@ -5007,7 +5023,7 @@ type MessageFeedback = "like" | "dislike" | null;
5007
5023
  * encrypted flag would be unreadable exactly where it matters and the skip
5008
5024
  * would fail open.
5009
5025
  */
5010
- type MessageOrigin = "tool_result" | "chunks_discarded";
5026
+ type MessageOrigin = "tool_result" | "chunks_discarded" | "message";
5011
5027
  /**
5012
5028
  * Metadata for files attached to messages.
5013
5029
  *
@@ -6529,7 +6545,12 @@ type PortalLlmFailureReason =
6529
6545
  /** Content parsed to a literal `null`, which is never a valid response. */
6530
6546
  | "null-completion"
6531
6547
  /** `totalTimeoutMs` was spent before the next attempt could run. */
6532
- | "time-budget-exhausted";
6548
+ | "time-budget-exhausted"
6549
+ /**
6550
+ * The portal's moderation gate refused the request. Terminal: the same input
6551
+ * is flagged again on every retry. See {@link isModerationResponse}.
6552
+ */
6553
+ | "content-flagged";
6533
6554
  /**
6534
6555
  * One attempt of a {@link callPortalJsonCompletion} call, reported to
6535
6556
  * `onAttempt` as it settles — success included. The wire-level diagnostic the
@@ -7766,7 +7787,9 @@ interface ExtractFactsOptions extends PortalLlmAuth {
7766
7787
  * Optional per-call request path override, forwarded to
7767
7788
  * {@link callPortalJsonCompletion}. When set, the extraction call POSTs to
7768
7789
  * `baseUrl + endpointOverride` instead of the default
7769
- * `/api/v1/chat/completions` — path only, body unchanged. Lets callers route
7790
+ * `/api/v1/chat/completions`. The body follows the transport, not the path: a
7791
+ * model that needs the Responses API is moved from a `.../chat/completions`
7792
+ * override to its sibling `.../responses` with a Responses-shaped body. Lets callers route
7770
7793
  * this internal-utility pass to a dedicated endpoint. Invalid values throw at
7771
7794
  * call time (see {@link validateEndpointOverride}).
7772
7795
  *
@@ -8706,7 +8729,8 @@ declare function stampTopicsExtractedAtOp(ctx: VaultMemoryOperationsContext, mem
8706
8729
  * `topics_user_managed` in particular is left exactly as it arrived, so the
8707
8730
  * autotagger stays off a curated memory whose links this just restored.
8708
8731
  *
8709
- * Skips deleted, foreign-user, and record-less rows. Returns the ids relinked.
8732
+ * Skips deleted rows, foreign-user rows, and rows with null topics.
8733
+ * Empty topics remove stale links. Returns the relinked IDs.
8710
8734
  */
8711
8735
  declare function relinkMemoryTopicsOp(ctx: VaultMemoryOperationsContext, memoryIds: readonly string[]): Promise<string[]>;
8712
8736
  /**
@@ -10414,7 +10438,9 @@ interface TopicExtractOptions extends PortalLlmAuth {
10414
10438
  * Optional per-call request path override, forwarded to
10415
10439
  * {@link callPortalJsonCompletion}. When set, topic extraction POSTs to
10416
10440
  * `baseUrl + endpointOverride` instead of the default
10417
- * `/api/v1/chat/completions` — path only, body unchanged. Lets callers route
10441
+ * `/api/v1/chat/completions`. The body follows the transport, not the path: a
10442
+ * model that needs the Responses API is moved from a `.../chat/completions`
10443
+ * override to its sibling `.../responses` with a Responses-shaped body. Lets callers route
10418
10444
  * this internal-utility pass to a dedicated endpoint. Invalid values throw at
10419
10445
  * call time (see {@link validateEndpointOverride}).
10420
10446
  */
@@ -11040,14 +11066,10 @@ declare function generateEmbeddings(texts: string[], options: EmbeddingOptions):
11040
11066
  * re-index. Off by default, so no background pass can spend a user's credits
11041
11067
  * without being asked to.
11042
11068
  *
11043
- * That flag opens the gate for one call and nothing more. It does not heal the
11044
- * row: a message that re-indexes successfully still carries `chunks_discarded`
11045
- * afterwards, so the next pass skips it again unless that pass sets the flag
11046
- * too. An embedding-model migration is where this bites. It reaches this gate
11047
- * before it can do anything, so the repaired rows stay on the old model, and
11048
- * `searchChunksOp` already drops stale-model vectors — they go quiet with
11049
- * nothing telling them apart from rows that were never repaired. Clearing the
11050
- * marker on a successful re-index is the real fix, tracked in #879.
11069
+ * A successful repair replaces the marker with `message` in the index write.
11070
+ * The new marker preserves ordinary message provenance during chat replay.
11071
+ * Later model migrations can then include the repaired row without this flag.
11072
+ * Failed embedding requests and empty index data do not clear the marker.
11051
11073
  *
11052
11074
  * `satisfies MessageOrigin` so the constant and the union cannot drift apart
11053
11075
  * silently — the column's type is the union, and a typo here would otherwise
@@ -11084,9 +11106,8 @@ declare function embedAllMessages(ctx: StorageOperationsContext, options: Embedd
11084
11106
  * to an explicit user action, never to a background pass. Opens that marker
11085
11107
  * only — `tool_result` rows stay excluded.
11086
11108
  *
11087
- * Per call, not a state change: a row that re-indexes successfully keeps its
11088
- * marker, so a later pass that omits this flag skips it again. See
11089
- * {@link CHUNKS_DISCARDED_ORIGIN}.
11109
+ * A successful repair clears the marker. Later model migrations include
11110
+ * the repaired row without this flag. See {@link CHUNKS_DISCARDED_ORIGIN}.
11090
11111
  */
11091
11112
  reembedDiscarded?: boolean;
11092
11113
  }): Promise<number>;
@@ -11144,9 +11165,8 @@ declare function chunkAndEmbedAllMessages(ctx: StorageOperationsContext, options
11144
11165
  * chunks nor vector, so it never reaches that check and falls straight to the
11145
11166
  * origin gate.
11146
11167
  *
11147
- * Per call, not a state change: a row that re-indexes successfully keeps its
11148
- * marker, so a later pass that omits this flag skips it again. See
11149
- * {@link CHUNKS_DISCARDED_ORIGIN}.
11168
+ * A successful repair clears the marker. Later model migrations include
11169
+ * the repaired row without this flag. See {@link CHUNKS_DISCARDED_ORIGIN}.
11150
11170
  */
11151
11171
  reembedDiscarded?: boolean;
11152
11172
  }): Promise<number>;