@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-N5CKISI3.mjs";
5
+ } from "./chunk-XD6WOQ5I.mjs";
6
6
  import "./chunk-GYKJOWEO.mjs";
7
7
 
8
8
  // src/lib/memory/consolidate.ts
@@ -44,13 +44,26 @@ var __decorateClass = (decorators, target, key, kind) => {
44
44
  };
45
45
 
46
46
  // src/lib/logger.ts
47
+ function getLoggerState() {
48
+ try {
49
+ return loggerGlobal[LOGGER_STATE_KEY] ?? (loggerGlobal[LOGGER_STATE_KEY] = fallbackLoggerState);
50
+ } catch {
51
+ if (!fallbackWarned) {
52
+ fallbackWarned = true;
53
+ fallbackLoggerState.logger.warn(
54
+ "@anuma/sdk: the global object rejects new properties. This entrypoint keeps its own logger, so setLogger does not reach other entrypoints."
55
+ );
56
+ }
57
+ return fallbackLoggerState;
58
+ }
59
+ }
47
60
  function setLogger(logger) {
48
- currentLogger = logger;
61
+ getLoggerState().logger = logger;
49
62
  }
50
63
  function getLogger() {
51
- return currentLogger;
64
+ return getLoggerState().logger;
52
65
  }
53
- var consoleLogger, noopLogger, currentLogger;
66
+ var consoleLogger, noopLogger, LOGGER_STATE_KEY, loggerGlobal, fallbackLoggerState, fallbackWarned;
54
67
  var init_logger = __esm({
55
68
  "src/lib/logger.ts"() {
56
69
  "use strict";
@@ -70,7 +83,10 @@ var init_logger = __esm({
70
83
  error: () => {
71
84
  }
72
85
  };
73
- currentLogger = consoleLogger;
86
+ LOGGER_STATE_KEY = /* @__PURE__ */ Symbol.for("@anuma/sdk/logger/v1");
87
+ loggerGlobal = globalThis;
88
+ fallbackLoggerState = { logger: consoleLogger };
89
+ fallbackWarned = false;
74
90
  }
75
91
  });
76
92
 
@@ -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);
@@ -3707,6 +3750,9 @@ async function updateMessageEmbeddingOp(ctx, uniqueId, vector, embeddingModel) {
3707
3750
  await ctx.database.write(async () => {
3708
3751
  await message.update((msg) => {
3709
3752
  msg._setRaw("vector", JSON.stringify(vector));
3753
+ if (msg.origin === "chunks_discarded" && vector.length > 0) {
3754
+ msg._setRaw("origin", "message");
3755
+ }
3710
3756
  msg._setRaw("embedding_model", embeddingModel);
3711
3757
  });
3712
3758
  });
@@ -3723,6 +3769,9 @@ async function updateMessageChunksOp(ctx, uniqueId, chunks, embeddingModel) {
3723
3769
  await ctx.database.write(async () => {
3724
3770
  await message.update((msg) => {
3725
3771
  msg._setRaw("chunks", JSON.stringify(storedChunks));
3772
+ if (msg.origin === "chunks_discarded" && storedChunks.length > 0 && storedChunks.every((chunk) => chunk.vector.length > 0)) {
3773
+ msg._setRaw("origin", "message");
3774
+ }
3726
3775
  msg._setRaw("embedding_model", embeddingModel);
3727
3776
  });
3728
3777
  });
@@ -5947,6 +5996,9 @@ function clientToolToResponsesFormat(tool) {
5947
5996
  ...toolConfig.removeAfterExecution !== void 0 && {
5948
5997
  removeAfterExecution: toolConfig.removeAfterExecution
5949
5998
  },
5999
+ ...toolConfig.removeAfterResult !== void 0 && {
6000
+ removeAfterResult: toolConfig.removeAfterResult
6001
+ },
5950
6002
  ...toolConfig.executorTimeout !== void 0 && {
5951
6003
  executorTimeout: toolConfig.executorTimeout
5952
6004
  },
@@ -5980,6 +6032,9 @@ function clientToolToCompletionsFormat(tool) {
5980
6032
  ...toolConfig.removeAfterExecution !== void 0 && {
5981
6033
  removeAfterExecution: toolConfig.removeAfterExecution
5982
6034
  },
6035
+ ...toolConfig.removeAfterResult !== void 0 && {
6036
+ removeAfterResult: toolConfig.removeAfterResult
6037
+ },
5983
6038
  ...toolConfig.executorTimeout !== void 0 && {
5984
6039
  executorTimeout: toolConfig.executorTimeout
5985
6040
  },
@@ -6914,6 +6969,7 @@ function toolsToApiFormat(tools, apiType) {
6914
6969
  executor: _executor,
6915
6970
  skipContinuation: _skipContinuation,
6916
6971
  removeAfterExecution: _removeAfterExecution,
6972
+ removeAfterResult: _removeAfterResult,
6917
6973
  executorTimeout: _executorTimeout,
6918
6974
  dependsOn: _dependsOn,
6919
6975
  deAnonymizeArgs: _deAnonymizeArgs,
@@ -9358,9 +9414,11 @@ Executing tool: ${toolInfo}
9358
9414
  }
9359
9415
  if (tools && apiTools) {
9360
9416
  const successfullyExecutedNames = /* @__PURE__ */ new Set();
9417
+ const successfulResults = [];
9361
9418
  for (const r of executionResults) {
9362
9419
  if (!r.error && !isToolErrorResult(r.result) && "name" in r && r.name) {
9363
9420
  successfullyExecutedNames.add(r.name);
9421
+ successfulResults.push(r.result);
9364
9422
  }
9365
9423
  }
9366
9424
  if (successfullyExecutedNames.size > 0) {
@@ -9369,7 +9427,15 @@ Executing tool: ${toolInfo}
9369
9427
  const tc = t;
9370
9428
  const func = tc.function;
9371
9429
  const toolName = typeof func?.name === "string" ? func.name : typeof tc.name === "string" ? tc.name : void 0;
9372
- if (tc.removeAfterExecution === true && toolName && successfullyExecutedNames.has(toolName)) {
9430
+ if (!toolName) continue;
9431
+ const removeAfterResult = tc.removeAfterResult;
9432
+ if (tc.removeAfterExecution === true && successfullyExecutedNames.has(toolName) || typeof removeAfterResult === "function" && successfulResults.some((result) => {
9433
+ try {
9434
+ return removeAfterResult(result);
9435
+ } catch {
9436
+ return false;
9437
+ }
9438
+ })) {
9373
9439
  toolsToRemove.add(toolName);
9374
9440
  }
9375
9441
  }
@@ -13210,7 +13276,7 @@ async function getMemoriesNeedingTopicExtractionOp(ctx, options) {
13210
13276
  const id = raw.id;
13211
13277
  const topics = parseTopics(raw.topics);
13212
13278
  const linked = linkedNames.get(id);
13213
- if (topics !== null && topics.length > 0 && linksDivergeFromTopics(topics, linked ?? /* @__PURE__ */ new Set())) {
13279
+ if (topics !== null && linksDivergeFromTopics(topics, linked ?? /* @__PURE__ */ new Set())) {
13214
13280
  topicsToRelinkAll.push(id);
13215
13281
  continue;
13216
13282
  }
@@ -13304,18 +13370,20 @@ async function relinkMemoryTopicsOp(ctx, memoryIds) {
13304
13370
  }
13305
13371
  const relinked = [];
13306
13372
  for (const id of Array.from(new Set(memoryIds))) {
13307
- let record;
13308
13373
  try {
13309
- record = await ctx.vaultMemoryCollection.find(id);
13310
- } catch {
13311
- continue;
13312
- }
13313
- if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) continue;
13314
- const topics = parseTopics(record.topics);
13315
- if (topics === null || topics.length === 0) continue;
13316
- try {
13317
- await relinkMemoryEntitiesFromTopicsOp(entityCtx, id, topics);
13318
- relinked.push(id);
13374
+ await ctx.database.write(async (writer) => {
13375
+ let record;
13376
+ try {
13377
+ record = await ctx.vaultMemoryCollection.find(id);
13378
+ } catch {
13379
+ return;
13380
+ }
13381
+ if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) return;
13382
+ const topics = parseTopics(record.topics);
13383
+ if (topics === null) return;
13384
+ await writer.callWriter(() => relinkMemoryEntitiesFromTopicsOp(entityCtx, id, topics));
13385
+ relinked.push(id);
13386
+ });
13319
13387
  } catch (err) {
13320
13388
  getLogger().warn("[memory/topics] relink failed", err);
13321
13389
  }
@@ -20269,6 +20337,28 @@ var BatchFailureError = class extends Error {
20269
20337
  this.terminal = terminal;
20270
20338
  }
20271
20339
  };
20340
+ var FlaggedBatchError = class extends Error {
20341
+ };
20342
+ function extracted(result) {
20343
+ return result.failedCount === 0 && result.outcome !== "empty-after-retry";
20344
+ }
20345
+ function batchError(result) {
20346
+ const failure = result.failure;
20347
+ if (failure?.reason === "http-terminal") {
20348
+ const status = failure.httpStatus;
20349
+ if (status !== void 0 && REQUEST_REJECTED_STATUSES.has(status))
20350
+ return new BatchFailureError(
20351
+ `Extraction rejected the batch (HTTP ${status}); not retried this session`,
20352
+ true
20353
+ );
20354
+ return new AccountFailureError(
20355
+ `Extraction refused for the account (HTTP ${status ?? "unknown"}); retried next session`
20356
+ );
20357
+ }
20358
+ if (result.failedCount > 0 || failure !== void 0 && CONTENT_FAILURE_REASONS.has(failure.reason))
20359
+ return new BatchFailureError("Extraction batch incomplete; retained for retry", false);
20360
+ return new Error("Extraction batch incomplete; retained for retry");
20361
+ }
20272
20362
  var AccountFailureError = class extends Error {
20273
20363
  };
20274
20364
  var LockedSourcesError = class extends Error {
@@ -20277,6 +20367,16 @@ var LockedSourcesError = class extends Error {
20277
20367
  this.persistentIds = persistentIds;
20278
20368
  }
20279
20369
  };
20370
+ function splitTurns(messages) {
20371
+ const turns = [];
20372
+ for (const message of messages) {
20373
+ const last = turns[turns.length - 1];
20374
+ if (!last || message.role === "user" && last.some((m) => m.role === "user"))
20375
+ turns.push([message]);
20376
+ else last.push(message);
20377
+ }
20378
+ return turns;
20379
+ }
20280
20380
  function idsOf(job) {
20281
20381
  return JSON.parse(String(job._getRaw("message_ids")));
20282
20382
  }
@@ -20427,6 +20527,10 @@ function createDurableAutoExtractor(options) {
20427
20527
  skippedThisSession.add(job.id);
20428
20528
  return false;
20429
20529
  }
20530
+ if (error instanceof FlaggedBatchError) {
20531
+ await acknowledge(job, ids, loaded, true);
20532
+ return true;
20533
+ }
20430
20534
  const counted = error instanceof BatchFailureError || error instanceof LockedSourcesError && error.persistentIds.length > 0;
20431
20535
  if (!counted) return false;
20432
20536
  const prior = sessionFailures.get(job.id);
@@ -20553,37 +20657,50 @@ function createDurableAutoExtractor(options) {
20553
20657
  const messages = loaded.flatMap(
20554
20658
  (m) => m && m.conversationId === conversationId && (m.role === "user" || m.role === "assistant") ? [{ id: m.uniqueId, role: m.role, content: m.content }] : []
20555
20659
  );
20556
- let success = messages.length === 0;
20557
- let result;
20558
- if (messages.length) {
20559
- result = await extractBatch(
20560
- messages,
20660
+ const jobScope = currentScope() === "private" ? "private" : String(job._getRaw("scope"));
20661
+ const jobFolderId = job._getRaw("folder_id") ?? null;
20662
+ const runExtraction = async (batch, batchIds) => {
20663
+ const result = await extractBatch(
20664
+ batch,
20561
20665
  conversationId,
20562
- ids,
20563
- currentScope() === "private" ? "private" : String(job._getRaw("scope")),
20564
- job._getRaw("folder_id") ?? null
20666
+ batchIds,
20667
+ jobScope,
20668
+ jobFolderId
20565
20669
  );
20566
- success = result.failedCount === 0 && result.outcome !== "empty-after-retry";
20567
20670
  options.onTurnComplete?.(result);
20568
- }
20569
- if (!success) {
20570
- const failure = result?.failure;
20571
- if (failure?.reason === "http-terminal") {
20572
- const status = failure.httpStatus;
20573
- if (status !== void 0 && REQUEST_REJECTED_STATUSES.has(status))
20574
- throw new BatchFailureError(
20575
- `Extraction rejected the batch (HTTP ${status}); not retried this session`,
20576
- true
20671
+ return result;
20672
+ };
20673
+ const flaggedIds = /* @__PURE__ */ new Set();
20674
+ if (messages.length) {
20675
+ const result = await runExtraction(messages, ids);
20676
+ if (result.failure?.reason === "content-flagged") {
20677
+ const turns = splitTurns(messages);
20678
+ if (turns.length < 2)
20679
+ throw new FlaggedBatchError("Extraction batch refused by moderation; abandoned");
20680
+ for (const turn of turns) {
20681
+ const turnResult = await runExtraction(
20682
+ turn,
20683
+ turn.map((m) => m.id)
20577
20684
  );
20578
- throw new AccountFailureError(
20579
- `Extraction refused for the account (HTTP ${status ?? "unknown"}); retried next session`
20580
- );
20581
- }
20582
- if ((result?.failedCount ?? 0) > 0 || failure !== void 0 && CONTENT_FAILURE_REASONS.has(failure.reason))
20583
- throw new BatchFailureError("Extraction batch incomplete; retained for retry", false);
20584
- throw new Error("Extraction batch incomplete; retained for retry");
20685
+ if (turnResult.failure?.reason === "content-flagged")
20686
+ for (const m of turn) flaggedIds.add(m.id);
20687
+ else if (!extracted(turnResult)) throw batchError(turnResult);
20688
+ }
20689
+ if (flaggedIds.size)
20690
+ reportError(
20691
+ new Error(
20692
+ `Dropped ${flaggedIds.size} source id(s) of an extraction batch refused by moderation`
20693
+ ),
20694
+ conversationId
20695
+ );
20696
+ } else if (!extracted(result)) throw batchError(result);
20585
20697
  }
20586
- await acknowledge(job, ids, loaded, false);
20698
+ await acknowledge(
20699
+ job,
20700
+ ids,
20701
+ loaded,
20702
+ ids.slice(-2).some((id) => flaggedIds.has(id))
20703
+ );
20587
20704
  scheduleAfterSuccess = true;
20588
20705
  } catch (error) {
20589
20706
  attempts.set(job.id, attempted + 1);
@@ -22241,10 +22358,10 @@ function useChatStorage(options) {
22241
22358
  let mergedTools2 = void 0;
22242
22359
  let filteredServerTools2 = [];
22243
22360
  const isServerToolsFunction2 = typeof serverToolsFilter === "function";
22244
- const extracted2 = extractUserMessageFromMessages(messages);
22361
+ const extracted3 = extractUserMessageFromMessages(messages);
22245
22362
  const messageContent = resolveStoredUserContent(
22246
22363
  storedUserContent,
22247
- extracted2?.content ?? ""
22364
+ extracted3?.content ?? ""
22248
22365
  );
22249
22366
  let skipUserEmbedding;
22250
22367
  let skipEmbeddingFailed = false;
@@ -22410,15 +22527,15 @@ function useChatStorage(options) {
22410
22527
  skipped: true
22411
22528
  };
22412
22529
  }
22413
- const extracted = extractUserMessageFromMessages(messages);
22414
- if (!extracted || !extracted.content && !extracted.files?.length) {
22530
+ const extracted2 = extractUserMessageFromMessages(messages);
22531
+ if (!extracted2 || !extracted2.content && !extracted2.files?.length) {
22415
22532
  return {
22416
22533
  data: null,
22417
22534
  error: "No user message found in messages array"
22418
22535
  };
22419
22536
  }
22420
- const contentForStorage = resolveStoredUserContent(storedUserContent, extracted.content);
22421
- const filesForStorage = files ?? extracted.files;
22537
+ const contentForStorage = resolveStoredUserContent(storedUserContent, extracted2.content);
22538
+ const filesForStorage = files ?? extracted2.files;
22422
22539
  let convId;
22423
22540
  try {
22424
22541
  convId = await ensureConversation();
@@ -22746,9 +22863,9 @@ function useChatStorage(options) {
22746
22863
  if (result.error || !result.data) {
22747
22864
  const abortedResult = result;
22748
22865
  if (abortedResult.error === "Request aborted") {
22749
- const extracted2 = abortedResult.data ? extractAssistantText(abortedResult.data) : { content: "", thinking: void 0 };
22750
- const assistantContent2 = extracted2.content;
22751
- const abortedThinkingContent = extracted2.thinking;
22866
+ const extracted3 = abortedResult.data ? extractAssistantText(abortedResult.data) : { content: "", thinking: void 0 };
22867
+ const assistantContent2 = extracted3.content;
22868
+ const abortedThinkingContent = extracted3.thinking;
22752
22869
  const responseModel = abortedResult.data?.model || model || "";
22753
22870
  let storedAssistantMessage2;
22754
22871
  try {
@@ -2209,6 +2209,21 @@ type ToolConfig = LlmapiChatCompletionTool & {
2209
2209
  * execution fails, allowing the model to retry.
2210
2210
  */
2211
2211
  removeAfterExecution?: boolean;
2212
+ /**
2213
+ * Remove this tool for the rest of the turn once ANY tool returns a
2214
+ * successful result this predicate accepts — its own or another's. Errors
2215
+ * never count, as for `removeAfterExecution`.
2216
+ *
2217
+ * Only results of tools executed on this client are offered; tools the
2218
+ * portal runs server-side never reach the predicate. A predicate that throws
2219
+ * is treated as not matching.
2220
+ *
2221
+ * For a family of tools that has to leave together: when one of them
2222
+ * declines an out-of-scope request, the model otherwise works through its
2223
+ * siblings round after round until the round cap. Give every member the same
2224
+ * predicate and the first refusal removes the whole family.
2225
+ */
2226
+ removeAfterResult?: (result: unknown) => boolean;
2212
2227
  /**
2213
2228
  * Timeout in milliseconds for this tool's executor.
2214
2229
  * Overrides the default 30-second timeout.
@@ -3512,6 +3527,7 @@ type MessageFeedback = "like" | "dislike" | null;
3512
3527
  * - `tool_result`: the hidden `[Tool Execution Results]` row built from a
3513
3528
  * turn's auto-executed tool results. Skipped by the embedding sweep (see
3514
3529
  * `memoryEngine/embeddings`) AND hidden from the transcript.
3530
+ * - `message`: an ordinary message with an index after explicit repair.
3515
3531
  * - `chunks_discarded`: an ordinary, still-rendered message whose chunk vectors
3516
3532
  * were built over `enc:v3:` ciphertext (sdk#864) and have been discarded
3517
3533
  * rather than re-embedded at the user's own expense (client#5618). Skipped by
@@ -3524,7 +3540,7 @@ type MessageFeedback = "like" | "dislike" | null;
3524
3540
  * encrypted flag would be unreadable exactly where it matters and the skip
3525
3541
  * would fail open.
3526
3542
  */
3527
- type MessageOrigin = "tool_result" | "chunks_discarded";
3543
+ type MessageOrigin = "tool_result" | "chunks_discarded" | "message";
3528
3544
  /**
3529
3545
  * Metadata for files attached to messages.
3530
3546
  *
@@ -5202,7 +5218,12 @@ type PortalLlmFailureReason =
5202
5218
  /** Content parsed to a literal `null`, which is never a valid response. */
5203
5219
  | "null-completion"
5204
5220
  /** `totalTimeoutMs` was spent before the next attempt could run. */
5205
- | "time-budget-exhausted";
5221
+ | "time-budget-exhausted"
5222
+ /**
5223
+ * The portal's moderation gate refused the request. Terminal: the same input
5224
+ * is flagged again on every retry. See {@link isModerationResponse}.
5225
+ */
5226
+ | "content-flagged";
5206
5227
  /**
5207
5228
  * One attempt of a {@link callPortalJsonCompletion} call, reported to
5208
5229
  * `onAttempt` as it settles — success included. The wire-level diagnostic the
@@ -6439,7 +6460,9 @@ interface ExtractFactsOptions extends PortalLlmAuth {
6439
6460
  * Optional per-call request path override, forwarded to
6440
6461
  * {@link callPortalJsonCompletion}. When set, the extraction call POSTs to
6441
6462
  * `baseUrl + endpointOverride` instead of the default
6442
- * `/api/v1/chat/completions` — path only, body unchanged. Lets callers route
6463
+ * `/api/v1/chat/completions`. The body follows the transport, not the path: a
6464
+ * model that needs the Responses API is moved from a `.../chat/completions`
6465
+ * override to its sibling `.../responses` with a Responses-shaped body. Lets callers route
6443
6466
  * this internal-utility pass to a dedicated endpoint. Invalid values throw at
6444
6467
  * call time (see {@link validateEndpointOverride}).
6445
6468
  *
@@ -7292,7 +7315,8 @@ declare function stampTopicsExtractedAtOp(ctx: VaultMemoryOperationsContext, mem
7292
7315
  * `topics_user_managed` in particular is left exactly as it arrived, so the
7293
7316
  * autotagger stays off a curated memory whose links this just restored.
7294
7317
  *
7295
- * Skips deleted, foreign-user, and record-less rows. Returns the ids relinked.
7318
+ * Skips deleted rows, foreign-user rows, and rows with null topics.
7319
+ * Empty topics remove stale links. Returns the relinked IDs.
7296
7320
  */
7297
7321
  declare function relinkMemoryTopicsOp(ctx: VaultMemoryOperationsContext, memoryIds: readonly string[]): Promise<string[]>;
7298
7322
  /**
@@ -9121,7 +9145,9 @@ interface TopicExtractOptions extends PortalLlmAuth {
9121
9145
  * Optional per-call request path override, forwarded to
9122
9146
  * {@link callPortalJsonCompletion}. When set, topic extraction POSTs to
9123
9147
  * `baseUrl + endpointOverride` instead of the default
9124
- * `/api/v1/chat/completions` — path only, body unchanged. Lets callers route
9148
+ * `/api/v1/chat/completions`. The body follows the transport, not the path: a
9149
+ * model that needs the Responses API is moved from a `.../chat/completions`
9150
+ * override to its sibling `.../responses` with a Responses-shaped body. Lets callers route
9125
9151
  * this internal-utility pass to a dedicated endpoint. Invalid values throw at
9126
9152
  * call time (see {@link validateEndpointOverride}).
9127
9153
  */
@@ -9747,14 +9773,10 @@ declare function generateEmbeddings(texts: string[], options: EmbeddingOptions):
9747
9773
  * re-index. Off by default, so no background pass can spend a user's credits
9748
9774
  * without being asked to.
9749
9775
  *
9750
- * That flag opens the gate for one call and nothing more. It does not heal the
9751
- * row: a message that re-indexes successfully still carries `chunks_discarded`
9752
- * afterwards, so the next pass skips it again unless that pass sets the flag
9753
- * too. An embedding-model migration is where this bites. It reaches this gate
9754
- * before it can do anything, so the repaired rows stay on the old model, and
9755
- * `searchChunksOp` already drops stale-model vectors — they go quiet with
9756
- * nothing telling them apart from rows that were never repaired. Clearing the
9757
- * marker on a successful re-index is the real fix, tracked in #879.
9776
+ * A successful repair replaces the marker with `message` in the index write.
9777
+ * The new marker preserves ordinary message provenance during chat replay.
9778
+ * Later model migrations can then include the repaired row without this flag.
9779
+ * Failed embedding requests and empty index data do not clear the marker.
9758
9780
  *
9759
9781
  * `satisfies MessageOrigin` so the constant and the union cannot drift apart
9760
9782
  * silently — the column's type is the union, and a typo here would otherwise
@@ -9791,9 +9813,8 @@ declare function embedAllMessages(ctx: StorageOperationsContext, options: Embedd
9791
9813
  * to an explicit user action, never to a background pass. Opens that marker
9792
9814
  * only — `tool_result` rows stay excluded.
9793
9815
  *
9794
- * Per call, not a state change: a row that re-indexes successfully keeps its
9795
- * marker, so a later pass that omits this flag skips it again. See
9796
- * {@link CHUNKS_DISCARDED_ORIGIN}.
9816
+ * A successful repair clears the marker. Later model migrations include
9817
+ * the repaired row without this flag. See {@link CHUNKS_DISCARDED_ORIGIN}.
9797
9818
  */
9798
9819
  reembedDiscarded?: boolean;
9799
9820
  }): Promise<number>;
@@ -9851,9 +9872,8 @@ declare function chunkAndEmbedAllMessages(ctx: StorageOperationsContext, options
9851
9872
  * chunks nor vector, so it never reaches that check and falls straight to the
9852
9873
  * origin gate.
9853
9874
  *
9854
- * Per call, not a state change: a row that re-indexes successfully keeps its
9855
- * marker, so a later pass that omits this flag skips it again. See
9856
- * {@link CHUNKS_DISCARDED_ORIGIN}.
9875
+ * A successful repair clears the marker. Later model migrations include
9876
+ * the repaired row without this flag. See {@link CHUNKS_DISCARDED_ORIGIN}.
9857
9877
  */
9858
9878
  reembedDiscarded?: boolean;
9859
9879
  }): Promise<number>;