@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.
@@ -14957,10 +14957,23 @@ var require_stepsForMigration = __commonJS({
14957
14957
  });
14958
14958
 
14959
14959
  // src/lib/logger.ts
14960
+ function getLoggerState() {
14961
+ try {
14962
+ return loggerGlobal[LOGGER_STATE_KEY] ?? (loggerGlobal[LOGGER_STATE_KEY] = fallbackLoggerState);
14963
+ } catch {
14964
+ if (!fallbackWarned) {
14965
+ fallbackWarned = true;
14966
+ fallbackLoggerState.logger.warn(
14967
+ "@anuma/sdk: the global object rejects new properties. This entrypoint keeps its own logger, so setLogger does not reach other entrypoints."
14968
+ );
14969
+ }
14970
+ return fallbackLoggerState;
14971
+ }
14972
+ }
14960
14973
  function getLogger() {
14961
- return currentLogger;
14974
+ return getLoggerState().logger;
14962
14975
  }
14963
- var consoleLogger, currentLogger;
14976
+ var consoleLogger, LOGGER_STATE_KEY, loggerGlobal, fallbackLoggerState, fallbackWarned;
14964
14977
  var init_logger = __esm({
14965
14978
  "src/lib/logger.ts"() {
14966
14979
  "use strict";
@@ -14970,7 +14983,10 @@ var init_logger = __esm({
14970
14983
  warn: console.warn.bind(console),
14971
14984
  error: console.error.bind(console)
14972
14985
  };
14973
- currentLogger = consoleLogger;
14986
+ LOGGER_STATE_KEY = /* @__PURE__ */ Symbol.for("@anuma/sdk/logger/v1");
14987
+ loggerGlobal = globalThis;
14988
+ fallbackLoggerState = { logger: consoleLogger };
14989
+ fallbackWarned = false;
14974
14990
  }
14975
14991
  });
14976
14992
 
@@ -15076,9 +15092,17 @@ function responsesExtra(extra) {
15076
15092
  }
15077
15093
  return out;
15078
15094
  }
15095
+ function splitEndpoint(endpoint) {
15096
+ const cut = endpoint.search(/[?#]/);
15097
+ const rawPath = cut === -1 ? endpoint : endpoint.slice(0, cut);
15098
+ return {
15099
+ path: rawPath.replace(/\/+$/, ""),
15100
+ rest: cut === -1 ? "" : endpoint.slice(cut)
15101
+ };
15102
+ }
15079
15103
  function assertTransportMatchesEndpoint(transport, endpoint) {
15080
15104
  const foreign = FOREIGN_ENDPOINT_SUFFIX[transport];
15081
- if (!endpoint.endsWith(foreign)) return;
15105
+ if (!splitEndpoint(endpoint).path.endsWith(foreign)) return;
15082
15106
  throw new Error(
15083
15107
  `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.`
15084
15108
  );
@@ -15123,14 +15147,20 @@ async function resolvePortalAuthHeaders(auth, tag) {
15123
15147
  }
15124
15148
  async function callPortalJsonCompletion(req) {
15125
15149
  const log = getLogger();
15126
- let endpoint = req.transport === "responses" ? "/api/v1/responses" : "/api/v1/chat/completions";
15150
+ const transport = req.transport ?? (requiresResponsesTransport(req.model) ? "responses" : "chat");
15151
+ const autoUpgraded = req.transport === void 0 && transport === "responses";
15152
+ let endpoint = transport === "responses" ? "/api/v1/responses" : "/api/v1/chat/completions";
15127
15153
  if (req.endpointOverride !== void 0) {
15128
15154
  const overrideValidation = validateEndpointOverride(req.endpointOverride);
15129
15155
  if (!overrideValidation.valid) {
15130
15156
  throw new Error(overrideValidation.message);
15131
15157
  }
15132
15158
  endpoint = overrideValidation.endpoint;
15133
- assertTransportMatchesEndpoint(req.transport ?? "chat", endpoint);
15159
+ const { path, rest } = splitEndpoint(endpoint);
15160
+ if (autoUpgraded && path.endsWith("/chat/completions")) {
15161
+ endpoint = `${path.slice(0, -"/chat/completions".length)}/responses${rest}`;
15162
+ }
15163
+ assertTransportMatchesEndpoint(transport, endpoint);
15134
15164
  }
15135
15165
  const maxAttempts = Math.max(1, req.maxAttempts ?? 3);
15136
15166
  const startedAt = Date.now();
@@ -15155,7 +15185,10 @@ async function callPortalJsonCompletion(req) {
15155
15185
  ),
15156
15186
  ...reinforce && { reinforceJsonContract: true }
15157
15187
  } : reinforce ? { ...req, reinforceJsonContract: true } : req;
15158
- const outcome = await attemptPortalJson(attemptReq, endpoint);
15188
+ const outcome = await attemptPortalJson(
15189
+ { ...attemptReq, transport },
15190
+ endpoint
15191
+ );
15159
15192
  if (outcome.kind === "ok") {
15160
15193
  req.onAttempt?.({ attempt, ok: true });
15161
15194
  return outcome.value;
@@ -15307,6 +15340,13 @@ async function attemptPortalJson(req, endpoint) {
15307
15340
  }
15308
15341
  clearTimeout(timer);
15309
15342
  logPortalUsage(body, req.tag);
15343
+ if (isModerationResponse(body)) {
15344
+ return {
15345
+ kind: "terminal",
15346
+ code: "content-flagged",
15347
+ reason: "portal moderation refused the request"
15348
+ };
15349
+ }
15310
15350
  const rawContent = extractCompletionContent(body);
15311
15351
  if (!rawContent) {
15312
15352
  return {
@@ -15392,6 +15432,9 @@ function logPortalUsage(body, tag) {
15392
15432
  } catch {
15393
15433
  }
15394
15434
  }
15435
+ function isModerationResponse(body) {
15436
+ return typeof body === "object" && body !== null && body.id === "moderation";
15437
+ }
15395
15438
  function extractCompletionContent(body) {
15396
15439
  if (typeof body !== "object" || body === null) return null;
15397
15440
  const responsesText = extractResponsesContent(body);
@@ -20787,6 +20830,9 @@ async function updateMessageEmbeddingOp(ctx, uniqueId, vector, embeddingModel) {
20787
20830
  await ctx.database.write(async () => {
20788
20831
  await message.update((msg) => {
20789
20832
  msg._setRaw("vector", JSON.stringify(vector));
20833
+ if (msg.origin === "chunks_discarded" && vector.length > 0) {
20834
+ msg._setRaw("origin", "message");
20835
+ }
20790
20836
  msg._setRaw("embedding_model", embeddingModel);
20791
20837
  });
20792
20838
  });
@@ -20803,6 +20849,9 @@ async function updateMessageChunksOp(ctx, uniqueId, chunks, embeddingModel) {
20803
20849
  await ctx.database.write(async () => {
20804
20850
  await message.update((msg) => {
20805
20851
  msg._setRaw("chunks", JSON.stringify(storedChunks));
20852
+ if (msg.origin === "chunks_discarded" && storedChunks.length > 0 && storedChunks.every((chunk) => chunk.vector.length > 0)) {
20853
+ msg._setRaw("origin", "message");
20854
+ }
20806
20855
  msg._setRaw("embedding_model", embeddingModel);
20807
20856
  });
20808
20857
  });
@@ -22838,7 +22887,7 @@ async function getMemoriesNeedingTopicExtractionOp(ctx, options) {
22838
22887
  const id = raw.id;
22839
22888
  const topics = parseTopics(raw.topics);
22840
22889
  const linked = linkedNames.get(id);
22841
- if (topics !== null && topics.length > 0 && linksDivergeFromTopics(topics, linked ?? /* @__PURE__ */ new Set())) {
22890
+ if (topics !== null && linksDivergeFromTopics(topics, linked ?? /* @__PURE__ */ new Set())) {
22842
22891
  topicsToRelinkAll.push(id);
22843
22892
  continue;
22844
22893
  }
@@ -22932,18 +22981,20 @@ async function relinkMemoryTopicsOp(ctx, memoryIds) {
22932
22981
  }
22933
22982
  const relinked = [];
22934
22983
  for (const id of Array.from(new Set(memoryIds))) {
22935
- let record;
22936
22984
  try {
22937
- record = await ctx.vaultMemoryCollection.find(id);
22938
- } catch {
22939
- continue;
22940
- }
22941
- if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) continue;
22942
- const topics = parseTopics(record.topics);
22943
- if (topics === null || topics.length === 0) continue;
22944
- try {
22945
- await relinkMemoryEntitiesFromTopicsOp(entityCtx, id, topics);
22946
- relinked.push(id);
22985
+ await ctx.database.write(async (writer) => {
22986
+ let record;
22987
+ try {
22988
+ record = await ctx.vaultMemoryCollection.find(id);
22989
+ } catch {
22990
+ return;
22991
+ }
22992
+ if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) return;
22993
+ const topics = parseTopics(record.topics);
22994
+ if (topics === null) return;
22995
+ await writer.callWriter(() => relinkMemoryEntitiesFromTopicsOp(entityCtx, id, topics));
22996
+ relinked.push(id);
22997
+ });
22947
22998
  } catch (err) {
22948
22999
  getLogger().warn("[memory/topics] relink failed", err);
22949
23000
  }
@@ -32417,6 +32468,28 @@ var BatchFailureError = class extends Error {
32417
32468
  this.terminal = terminal;
32418
32469
  }
32419
32470
  };
32471
+ var FlaggedBatchError = class extends Error {
32472
+ };
32473
+ function extracted(result) {
32474
+ return result.failedCount === 0 && result.outcome !== "empty-after-retry";
32475
+ }
32476
+ function batchError(result) {
32477
+ const failure = result.failure;
32478
+ if (failure?.reason === "http-terminal") {
32479
+ const status = failure.httpStatus;
32480
+ if (status !== void 0 && REQUEST_REJECTED_STATUSES.has(status))
32481
+ return new BatchFailureError(
32482
+ `Extraction rejected the batch (HTTP ${status}); not retried this session`,
32483
+ true
32484
+ );
32485
+ return new AccountFailureError(
32486
+ `Extraction refused for the account (HTTP ${status ?? "unknown"}); retried next session`
32487
+ );
32488
+ }
32489
+ if (result.failedCount > 0 || failure !== void 0 && CONTENT_FAILURE_REASONS.has(failure.reason))
32490
+ return new BatchFailureError("Extraction batch incomplete; retained for retry", false);
32491
+ return new Error("Extraction batch incomplete; retained for retry");
32492
+ }
32420
32493
  var AccountFailureError = class extends Error {
32421
32494
  };
32422
32495
  var LockedSourcesError = class extends Error {
@@ -32425,6 +32498,16 @@ var LockedSourcesError = class extends Error {
32425
32498
  this.persistentIds = persistentIds;
32426
32499
  }
32427
32500
  };
32501
+ function splitTurns(messages) {
32502
+ const turns = [];
32503
+ for (const message of messages) {
32504
+ const last = turns[turns.length - 1];
32505
+ if (!last || message.role === "user" && last.some((m) => m.role === "user"))
32506
+ turns.push([message]);
32507
+ else last.push(message);
32508
+ }
32509
+ return turns;
32510
+ }
32428
32511
  function idsOf(job) {
32429
32512
  return JSON.parse(String(job._getRaw("message_ids")));
32430
32513
  }
@@ -32575,6 +32658,10 @@ function createDurableAutoExtractor(options) {
32575
32658
  skippedThisSession.add(job.id);
32576
32659
  return false;
32577
32660
  }
32661
+ if (error instanceof FlaggedBatchError) {
32662
+ await acknowledge(job, ids, loaded, true);
32663
+ return true;
32664
+ }
32578
32665
  const counted = error instanceof BatchFailureError || error instanceof LockedSourcesError && error.persistentIds.length > 0;
32579
32666
  if (!counted) return false;
32580
32667
  const prior = sessionFailures.get(job.id);
@@ -32701,37 +32788,50 @@ function createDurableAutoExtractor(options) {
32701
32788
  const messages = loaded.flatMap(
32702
32789
  (m) => m && m.conversationId === conversationId && (m.role === "user" || m.role === "assistant") ? [{ id: m.uniqueId, role: m.role, content: m.content }] : []
32703
32790
  );
32704
- let success = messages.length === 0;
32705
- let result;
32706
- if (messages.length) {
32707
- result = await extractBatch(
32708
- messages,
32791
+ const jobScope = currentScope() === "private" ? "private" : String(job._getRaw("scope"));
32792
+ const jobFolderId = job._getRaw("folder_id") ?? null;
32793
+ const runExtraction = async (batch, batchIds) => {
32794
+ const result = await extractBatch(
32795
+ batch,
32709
32796
  conversationId,
32710
- ids,
32711
- currentScope() === "private" ? "private" : String(job._getRaw("scope")),
32712
- job._getRaw("folder_id") ?? null
32797
+ batchIds,
32798
+ jobScope,
32799
+ jobFolderId
32713
32800
  );
32714
- success = result.failedCount === 0 && result.outcome !== "empty-after-retry";
32715
32801
  options.onTurnComplete?.(result);
32716
- }
32717
- if (!success) {
32718
- const failure = result?.failure;
32719
- if (failure?.reason === "http-terminal") {
32720
- const status = failure.httpStatus;
32721
- if (status !== void 0 && REQUEST_REJECTED_STATUSES.has(status))
32722
- throw new BatchFailureError(
32723
- `Extraction rejected the batch (HTTP ${status}); not retried this session`,
32724
- true
32802
+ return result;
32803
+ };
32804
+ const flaggedIds = /* @__PURE__ */ new Set();
32805
+ if (messages.length) {
32806
+ const result = await runExtraction(messages, ids);
32807
+ if (result.failure?.reason === "content-flagged") {
32808
+ const turns = splitTurns(messages);
32809
+ if (turns.length < 2)
32810
+ throw new FlaggedBatchError("Extraction batch refused by moderation; abandoned");
32811
+ for (const turn of turns) {
32812
+ const turnResult = await runExtraction(
32813
+ turn,
32814
+ turn.map((m) => m.id)
32725
32815
  );
32726
- throw new AccountFailureError(
32727
- `Extraction refused for the account (HTTP ${status ?? "unknown"}); retried next session`
32728
- );
32729
- }
32730
- if ((result?.failedCount ?? 0) > 0 || failure !== void 0 && CONTENT_FAILURE_REASONS.has(failure.reason))
32731
- throw new BatchFailureError("Extraction batch incomplete; retained for retry", false);
32732
- throw new Error("Extraction batch incomplete; retained for retry");
32816
+ if (turnResult.failure?.reason === "content-flagged")
32817
+ for (const m of turn) flaggedIds.add(m.id);
32818
+ else if (!extracted(turnResult)) throw batchError(turnResult);
32819
+ }
32820
+ if (flaggedIds.size)
32821
+ reportError(
32822
+ new Error(
32823
+ `Dropped ${flaggedIds.size} source id(s) of an extraction batch refused by moderation`
32824
+ ),
32825
+ conversationId
32826
+ );
32827
+ } else if (!extracted(result)) throw batchError(result);
32733
32828
  }
32734
- await acknowledge(job, ids, loaded, false);
32829
+ await acknowledge(
32830
+ job,
32831
+ ids,
32832
+ loaded,
32833
+ ids.slice(-2).some((id) => flaggedIds.has(id))
32834
+ );
32735
32835
  scheduleAfterSuccess = true;
32736
32836
  } catch (error) {
32737
32837
  attempts.set(job.id, attempted + 1);
@@ -35625,6 +35725,7 @@ function toolsToApiFormat(tools, apiType) {
35625
35725
  executor: _executor,
35626
35726
  skipContinuation: _skipContinuation,
35627
35727
  removeAfterExecution: _removeAfterExecution,
35728
+ removeAfterResult: _removeAfterResult,
35628
35729
  executorTimeout: _executorTimeout,
35629
35730
  dependsOn: _dependsOn,
35630
35731
  deAnonymizeArgs: _deAnonymizeArgs,
@@ -38069,9 +38170,11 @@ Executing tool: ${toolInfo}
38069
38170
  }
38070
38171
  if (tools && apiTools) {
38071
38172
  const successfullyExecutedNames = /* @__PURE__ */ new Set();
38173
+ const successfulResults = [];
38072
38174
  for (const r of executionResults) {
38073
38175
  if (!r.error && !isToolErrorResult(r.result) && "name" in r && r.name) {
38074
38176
  successfullyExecutedNames.add(r.name);
38177
+ successfulResults.push(r.result);
38075
38178
  }
38076
38179
  }
38077
38180
  if (successfullyExecutedNames.size > 0) {
@@ -38080,7 +38183,15 @@ Executing tool: ${toolInfo}
38080
38183
  const tc = t;
38081
38184
  const func = tc.function;
38082
38185
  const toolName2 = typeof func?.name === "string" ? func.name : typeof tc.name === "string" ? tc.name : void 0;
38083
- if (tc.removeAfterExecution === true && toolName2 && successfullyExecutedNames.has(toolName2)) {
38186
+ if (!toolName2) continue;
38187
+ const removeAfterResult = tc.removeAfterResult;
38188
+ if (tc.removeAfterExecution === true && successfullyExecutedNames.has(toolName2) || typeof removeAfterResult === "function" && successfulResults.some((result) => {
38189
+ try {
38190
+ return removeAfterResult(result);
38191
+ } catch {
38192
+ return false;
38193
+ }
38194
+ })) {
38084
38195
  toolsToRemove.add(toolName2);
38085
38196
  }
38086
38197
  }
@@ -2706,6 +2706,21 @@ type ToolConfig = LlmapiChatCompletionTool & {
2706
2706
  * execution fails, allowing the model to retry.
2707
2707
  */
2708
2708
  removeAfterExecution?: boolean;
2709
+ /**
2710
+ * Remove this tool for the rest of the turn once ANY tool returns a
2711
+ * successful result this predicate accepts — its own or another's. Errors
2712
+ * never count, as for `removeAfterExecution`.
2713
+ *
2714
+ * Only results of tools executed on this client are offered; tools the
2715
+ * portal runs server-side never reach the predicate. A predicate that throws
2716
+ * is treated as not matching.
2717
+ *
2718
+ * For a family of tools that has to leave together: when one of them
2719
+ * declines an out-of-scope request, the model otherwise works through its
2720
+ * siblings round after round until the round cap. Give every member the same
2721
+ * predicate and the first refusal removes the whole family.
2722
+ */
2723
+ removeAfterResult?: (result: unknown) => boolean;
2709
2724
  /**
2710
2725
  * Timeout in milliseconds for this tool's executor.
2711
2726
  * Overrides the default 30-second timeout.
@@ -3546,6 +3561,7 @@ type MessageFeedback = "like" | "dislike" | null;
3546
3561
  * - `tool_result`: the hidden `[Tool Execution Results]` row built from a
3547
3562
  * turn's auto-executed tool results. Skipped by the embedding sweep (see
3548
3563
  * `memoryEngine/embeddings`) AND hidden from the transcript.
3564
+ * - `message`: an ordinary message with an index after explicit repair.
3549
3565
  * - `chunks_discarded`: an ordinary, still-rendered message whose chunk vectors
3550
3566
  * were built over `enc:v3:` ciphertext (sdk#864) and have been discarded
3551
3567
  * rather than re-embedded at the user's own expense (client#5618). Skipped by
@@ -3558,7 +3574,7 @@ type MessageFeedback = "like" | "dislike" | null;
3558
3574
  * encrypted flag would be unreadable exactly where it matters and the skip
3559
3575
  * would fail open.
3560
3576
  */
3561
- type MessageOrigin = "tool_result" | "chunks_discarded";
3577
+ type MessageOrigin = "tool_result" | "chunks_discarded" | "message";
3562
3578
  /**
3563
3579
  * Metadata for files attached to messages.
3564
3580
  *
@@ -4988,7 +5004,12 @@ type PortalLlmFailureReason =
4988
5004
  /** Content parsed to a literal `null`, which is never a valid response. */
4989
5005
  | "null-completion"
4990
5006
  /** `totalTimeoutMs` was spent before the next attempt could run. */
4991
- | "time-budget-exhausted";
5007
+ | "time-budget-exhausted"
5008
+ /**
5009
+ * The portal's moderation gate refused the request. Terminal: the same input
5010
+ * is flagged again on every retry. See {@link isModerationResponse}.
5011
+ */
5012
+ | "content-flagged";
4992
5013
  /**
4993
5014
  * One attempt of a {@link callPortalJsonCompletion} call, reported to
4994
5015
  * `onAttempt` as it settles — success included. The wire-level diagnostic the
@@ -6338,7 +6359,9 @@ interface ExtractFactsOptions extends PortalLlmAuth {
6338
6359
  * Optional per-call request path override, forwarded to
6339
6360
  * {@link callPortalJsonCompletion}. When set, the extraction call POSTs to
6340
6361
  * `baseUrl + endpointOverride` instead of the default
6341
- * `/api/v1/chat/completions` — path only, body unchanged. Lets callers route
6362
+ * `/api/v1/chat/completions`. The body follows the transport, not the path: a
6363
+ * model that needs the Responses API is moved from a `.../chat/completions`
6364
+ * override to its sibling `.../responses` with a Responses-shaped body. Lets callers route
6342
6365
  * this internal-utility pass to a dedicated endpoint. Invalid values throw at
6343
6366
  * call time (see {@link validateEndpointOverride}).
6344
6367
  *
@@ -7113,7 +7136,8 @@ declare function stampTopicsExtractedAtOp(ctx: VaultMemoryOperationsContext, mem
7113
7136
  * `topics_user_managed` in particular is left exactly as it arrived, so the
7114
7137
  * autotagger stays off a curated memory whose links this just restored.
7115
7138
  *
7116
- * Skips deleted, foreign-user, and record-less rows. Returns the ids relinked.
7139
+ * Skips deleted rows, foreign-user rows, and rows with null topics.
7140
+ * Empty topics remove stale links. Returns the relinked IDs.
7117
7141
  */
7118
7142
  declare function relinkMemoryTopicsOp(ctx: VaultMemoryOperationsContext, memoryIds: readonly string[]): Promise<string[]>;
7119
7143
  /**
@@ -8089,9 +8113,8 @@ declare function embedAllMessages(ctx: StorageOperationsContext, options: Embedd
8089
8113
  * to an explicit user action, never to a background pass. Opens that marker
8090
8114
  * only — `tool_result` rows stay excluded.
8091
8115
  *
8092
- * Per call, not a state change: a row that re-indexes successfully keeps its
8093
- * marker, so a later pass that omits this flag skips it again. See
8094
- * {@link CHUNKS_DISCARDED_ORIGIN}.
8116
+ * A successful repair clears the marker. Later model migrations include
8117
+ * the repaired row without this flag. See {@link CHUNKS_DISCARDED_ORIGIN}.
8095
8118
  */
8096
8119
  reembedDiscarded?: boolean;
8097
8120
  }): Promise<number>;
@@ -8149,9 +8172,8 @@ declare function chunkAndEmbedAllMessages(ctx: StorageOperationsContext, options
8149
8172
  * chunks nor vector, so it never reaches that check and falls straight to the
8150
8173
  * origin gate.
8151
8174
  *
8152
- * Per call, not a state change: a row that re-indexes successfully keeps its
8153
- * marker, so a later pass that omits this flag skips it again. See
8154
- * {@link CHUNKS_DISCARDED_ORIGIN}.
8175
+ * A successful repair clears the marker. Later model migrations include
8176
+ * the repaired row without this flag. See {@link CHUNKS_DISCARDED_ORIGIN}.
8155
8177
  */
8156
8178
  reembedDiscarded?: boolean;
8157
8179
  }): Promise<number>;
@@ -9768,7 +9790,9 @@ interface TopicExtractOptions extends PortalLlmAuth {
9768
9790
  * Optional per-call request path override, forwarded to
9769
9791
  * {@link callPortalJsonCompletion}. When set, topic extraction POSTs to
9770
9792
  * `baseUrl + endpointOverride` instead of the default
9771
- * `/api/v1/chat/completions` — path only, body unchanged. Lets callers route
9793
+ * `/api/v1/chat/completions`. The body follows the transport, not the path: a
9794
+ * model that needs the Responses API is moved from a `.../chat/completions`
9795
+ * override to its sibling `.../responses` with a Responses-shaped body. Lets callers route
9772
9796
  * this internal-utility pass to a dedicated endpoint. Invalid values throw at
9773
9797
  * call time (see {@link validateEndpointOverride}).
9774
9798
  */
@@ -2706,6 +2706,21 @@ type ToolConfig = LlmapiChatCompletionTool & {
2706
2706
  * execution fails, allowing the model to retry.
2707
2707
  */
2708
2708
  removeAfterExecution?: boolean;
2709
+ /**
2710
+ * Remove this tool for the rest of the turn once ANY tool returns a
2711
+ * successful result this predicate accepts — its own or another's. Errors
2712
+ * never count, as for `removeAfterExecution`.
2713
+ *
2714
+ * Only results of tools executed on this client are offered; tools the
2715
+ * portal runs server-side never reach the predicate. A predicate that throws
2716
+ * is treated as not matching.
2717
+ *
2718
+ * For a family of tools that has to leave together: when one of them
2719
+ * declines an out-of-scope request, the model otherwise works through its
2720
+ * siblings round after round until the round cap. Give every member the same
2721
+ * predicate and the first refusal removes the whole family.
2722
+ */
2723
+ removeAfterResult?: (result: unknown) => boolean;
2709
2724
  /**
2710
2725
  * Timeout in milliseconds for this tool's executor.
2711
2726
  * Overrides the default 30-second timeout.
@@ -3546,6 +3561,7 @@ type MessageFeedback = "like" | "dislike" | null;
3546
3561
  * - `tool_result`: the hidden `[Tool Execution Results]` row built from a
3547
3562
  * turn's auto-executed tool results. Skipped by the embedding sweep (see
3548
3563
  * `memoryEngine/embeddings`) AND hidden from the transcript.
3564
+ * - `message`: an ordinary message with an index after explicit repair.
3549
3565
  * - `chunks_discarded`: an ordinary, still-rendered message whose chunk vectors
3550
3566
  * were built over `enc:v3:` ciphertext (sdk#864) and have been discarded
3551
3567
  * rather than re-embedded at the user's own expense (client#5618). Skipped by
@@ -3558,7 +3574,7 @@ type MessageFeedback = "like" | "dislike" | null;
3558
3574
  * encrypted flag would be unreadable exactly where it matters and the skip
3559
3575
  * would fail open.
3560
3576
  */
3561
- type MessageOrigin = "tool_result" | "chunks_discarded";
3577
+ type MessageOrigin = "tool_result" | "chunks_discarded" | "message";
3562
3578
  /**
3563
3579
  * Metadata for files attached to messages.
3564
3580
  *
@@ -4988,7 +5004,12 @@ type PortalLlmFailureReason =
4988
5004
  /** Content parsed to a literal `null`, which is never a valid response. */
4989
5005
  | "null-completion"
4990
5006
  /** `totalTimeoutMs` was spent before the next attempt could run. */
4991
- | "time-budget-exhausted";
5007
+ | "time-budget-exhausted"
5008
+ /**
5009
+ * The portal's moderation gate refused the request. Terminal: the same input
5010
+ * is flagged again on every retry. See {@link isModerationResponse}.
5011
+ */
5012
+ | "content-flagged";
4992
5013
  /**
4993
5014
  * One attempt of a {@link callPortalJsonCompletion} call, reported to
4994
5015
  * `onAttempt` as it settles — success included. The wire-level diagnostic the
@@ -6338,7 +6359,9 @@ interface ExtractFactsOptions extends PortalLlmAuth {
6338
6359
  * Optional per-call request path override, forwarded to
6339
6360
  * {@link callPortalJsonCompletion}. When set, the extraction call POSTs to
6340
6361
  * `baseUrl + endpointOverride` instead of the default
6341
- * `/api/v1/chat/completions` — path only, body unchanged. Lets callers route
6362
+ * `/api/v1/chat/completions`. The body follows the transport, not the path: a
6363
+ * model that needs the Responses API is moved from a `.../chat/completions`
6364
+ * override to its sibling `.../responses` with a Responses-shaped body. Lets callers route
6342
6365
  * this internal-utility pass to a dedicated endpoint. Invalid values throw at
6343
6366
  * call time (see {@link validateEndpointOverride}).
6344
6367
  *
@@ -7113,7 +7136,8 @@ declare function stampTopicsExtractedAtOp(ctx: VaultMemoryOperationsContext, mem
7113
7136
  * `topics_user_managed` in particular is left exactly as it arrived, so the
7114
7137
  * autotagger stays off a curated memory whose links this just restored.
7115
7138
  *
7116
- * Skips deleted, foreign-user, and record-less rows. Returns the ids relinked.
7139
+ * Skips deleted rows, foreign-user rows, and rows with null topics.
7140
+ * Empty topics remove stale links. Returns the relinked IDs.
7117
7141
  */
7118
7142
  declare function relinkMemoryTopicsOp(ctx: VaultMemoryOperationsContext, memoryIds: readonly string[]): Promise<string[]>;
7119
7143
  /**
@@ -8089,9 +8113,8 @@ declare function embedAllMessages(ctx: StorageOperationsContext, options: Embedd
8089
8113
  * to an explicit user action, never to a background pass. Opens that marker
8090
8114
  * only — `tool_result` rows stay excluded.
8091
8115
  *
8092
- * Per call, not a state change: a row that re-indexes successfully keeps its
8093
- * marker, so a later pass that omits this flag skips it again. See
8094
- * {@link CHUNKS_DISCARDED_ORIGIN}.
8116
+ * A successful repair clears the marker. Later model migrations include
8117
+ * the repaired row without this flag. See {@link CHUNKS_DISCARDED_ORIGIN}.
8095
8118
  */
8096
8119
  reembedDiscarded?: boolean;
8097
8120
  }): Promise<number>;
@@ -8149,9 +8172,8 @@ declare function chunkAndEmbedAllMessages(ctx: StorageOperationsContext, options
8149
8172
  * chunks nor vector, so it never reaches that check and falls straight to the
8150
8173
  * origin gate.
8151
8174
  *
8152
- * Per call, not a state change: a row that re-indexes successfully keeps its
8153
- * marker, so a later pass that omits this flag skips it again. See
8154
- * {@link CHUNKS_DISCARDED_ORIGIN}.
8175
+ * A successful repair clears the marker. Later model migrations include
8176
+ * the repaired row without this flag. See {@link CHUNKS_DISCARDED_ORIGIN}.
8155
8177
  */
8156
8178
  reembedDiscarded?: boolean;
8157
8179
  }): Promise<number>;
@@ -9768,7 +9790,9 @@ interface TopicExtractOptions extends PortalLlmAuth {
9768
9790
  * Optional per-call request path override, forwarded to
9769
9791
  * {@link callPortalJsonCompletion}. When set, topic extraction POSTs to
9770
9792
  * `baseUrl + endpointOverride` instead of the default
9771
- * `/api/v1/chat/completions` — path only, body unchanged. Lets callers route
9793
+ * `/api/v1/chat/completions`. The body follows the transport, not the path: a
9794
+ * model that needs the Responses API is moved from a `.../chat/completions`
9795
+ * override to its sibling `.../responses` with a Responses-shaped body. Lets callers route
9772
9796
  * this internal-utility pass to a dedicated endpoint. Invalid values throw at
9773
9797
  * call time (see {@link validateEndpointOverride}).
9774
9798
  */