@anuma/sdk 6.3.0 → 6.4.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.
@@ -4986,6 +4986,153 @@ async function cleanupConversationSummary(database, conversationId) {
4986
4986
  }
4987
4987
  }
4988
4988
 
4989
+ // src/lib/chat/toolResultMessage.ts
4990
+ var HEADER = "[Tool Execution Results]\n\n";
4991
+ var FOOTER = "\n\nBased on these results, continue with the task.";
4992
+ var ENTRY_SEPARATOR = "\n\n";
4993
+ var TOOL_RESULT_FOOTER_LINE = FOOTER.trim();
4994
+ var MAX_PERSISTED_TOOL_RESULT_CHARS = 1e5;
4995
+ function buildToolResultContent(results) {
4996
+ const entries = results.map((r) => `Tool "${r.name}" returned: ${JSON.stringify(r.result)}`);
4997
+ const framing = HEADER.length + FOOTER.length + ENTRY_SEPARATOR.length * Math.max(0, entries.length - 1);
4998
+ const summary = capEntries(entries, MAX_PERSISTED_TOOL_RESULT_CHARS - framing).join(
4999
+ ENTRY_SEPARATOR
5000
+ );
5001
+ return `${HEADER}${summary}${FOOTER}`;
5002
+ }
5003
+ function capToolResultEntries(entries, budget) {
5004
+ return capEntries(entries, budget);
5005
+ }
5006
+ function capEntries(entries, budget) {
5007
+ const smallestFirst = entries.map((_, index) => index).sort((a, b) => entries[a].length - entries[b].length);
5008
+ const capped = [...entries];
5009
+ let remaining = budget;
5010
+ let unassigned = entries.length;
5011
+ for (const index of smallestFirst) {
5012
+ capped[index] = truncateEntry(entries[index], Math.floor(remaining / unassigned));
5013
+ remaining -= capped[index].length;
5014
+ unassigned--;
5015
+ }
5016
+ return capped;
5017
+ }
5018
+ function truncateEntry(entry, allowance) {
5019
+ if (entry.length <= allowance) return entry;
5020
+ const keep = allowance - truncationMarker(entry.length).length;
5021
+ if (keep <= 0) return entry.slice(0, Math.max(0, allowance));
5022
+ return `${entry.slice(0, keep)}${truncationMarker(entry.length - keep)}`;
5023
+ }
5024
+ function truncationMarker(omitted) {
5025
+ return `
5026
+
5027
+ ... (tool output truncated, ${omitted} characters omitted)`;
5028
+ }
5029
+
5030
+ // src/lib/chat/toolResults.ts
5031
+ var TOOL_RESULTS_PREFIX = "[Tool Execution Results]";
5032
+ var DISPLAY_CARD_PLACEHOLDER = "[displayed a card to the user with the tool result]";
5033
+ var TOOL_LINE = /^Tool "([^"]+)" returned: (.*)$/;
5034
+ var TOOL_RESULT_ORIGIN = "tool_result";
5035
+ function isToolResultsRow(row) {
5036
+ if (row.origin === TOOL_RESULT_ORIGIN) return true;
5037
+ if (typeof row.origin === "string") return false;
5038
+ if (row.role !== "user" || !row.content.startsWith(TOOL_RESULTS_PREFIX)) return false;
5039
+ return parseToolResultSegments(row.content).length > 0;
5040
+ }
5041
+ function parseToolResultSegments(content) {
5042
+ const segments = [];
5043
+ for (const raw of content.split("\n")) {
5044
+ const line = raw.trim();
5045
+ const match = TOOL_LINE.exec(line);
5046
+ if (match) {
5047
+ segments.push({ name: match[1], line });
5048
+ continue;
5049
+ }
5050
+ const current = segments[segments.length - 1];
5051
+ if (!current || line === "" || line === TOOL_RESULT_FOOTER_LINE) continue;
5052
+ if (line === TOOL_RESULTS_PREFIX) continue;
5053
+ current.line = `${current.line}
5054
+ ${line}`;
5055
+ }
5056
+ return segments;
5057
+ }
5058
+ function prepareToolResultsForReplay(rows, options) {
5059
+ if (options.fold) {
5060
+ return foldToolResultsRows(rows, {
5061
+ exclude: options.exclude,
5062
+ placeholder: options.placeholder
5063
+ });
5064
+ }
5065
+ return rows.filter((row) => !isToolResultsRow(row));
5066
+ }
5067
+ function foldToolResultsRows(rows, options) {
5068
+ const exclude = new Set(options?.exclude ?? []);
5069
+ const out = [];
5070
+ const pending = [];
5071
+ const assistantIndexById = /* @__PURE__ */ new Map();
5072
+ for (const row of rows) {
5073
+ if (isToolResultsRow(row)) {
5074
+ pending.push({ row, insertionPoint: out.length });
5075
+ continue;
5076
+ }
5077
+ out.push(row);
5078
+ if (row.role === "assistant" && row.uniqueId) {
5079
+ assistantIndexById.set(row.uniqueId, out.length - 1);
5080
+ }
5081
+ }
5082
+ const byTarget = /* @__PURE__ */ new Map();
5083
+ for (const { row, insertionPoint } of pending) {
5084
+ const target = resolveFoldTarget(out, row, insertionPoint, assistantIndexById);
5085
+ if (target === void 0) continue;
5086
+ const group = byTarget.get(target);
5087
+ if (group) group.push(row);
5088
+ else byTarget.set(target, [row]);
5089
+ }
5090
+ for (const [target, groupedRows] of byTarget) {
5091
+ const assistant = out[target];
5092
+ const kept = groupedRows.flatMap((row) => parseToolResultSegments(row.content)).filter((segment) => !exclude.has(segment.name));
5093
+ if (kept.length > 0) {
5094
+ out[target] = { ...assistant, content: appendToolResults(assistant.content, kept) };
5095
+ } else if (!assistant.content.trim() && options?.placeholder) {
5096
+ out[target] = { ...assistant, content: options.placeholder };
5097
+ }
5098
+ }
5099
+ return out;
5100
+ }
5101
+ function resolveFoldTarget(out, row, insertionPoint, assistantIndexById) {
5102
+ const linked = row.parentMessageId ? assistantIndexById.get(row.parentMessageId) : void 0;
5103
+ if (linked !== void 0) return linked;
5104
+ let previous;
5105
+ for (let i = insertionPoint - 1; i >= 0; i--) {
5106
+ if (out[i].role === "assistant") {
5107
+ previous = i;
5108
+ break;
5109
+ }
5110
+ }
5111
+ let next;
5112
+ for (let i = insertionPoint; i < out.length; i++) {
5113
+ if (out[i].role === "assistant") {
5114
+ next = i;
5115
+ break;
5116
+ }
5117
+ }
5118
+ if (previous === void 0) return next;
5119
+ if (next === void 0) return previous;
5120
+ return next - insertionPoint < insertionPoint - previous ? next : previous;
5121
+ }
5122
+ var MAX_FOLDED_APPENDIX_CHARS = 2e4;
5123
+ function appendToolResults(assistantContent, kept) {
5124
+ const framing = TOOL_RESULTS_PREFIX.length + 1 + (kept.length - 1);
5125
+ const capped = capToolResultEntries(
5126
+ kept.map((segment) => segment.line),
5127
+ MAX_FOLDED_APPENDIX_CHARS - framing
5128
+ );
5129
+ const appendix = `${TOOL_RESULTS_PREFIX}
5130
+ ${capped.join("\n")}`;
5131
+ return assistantContent.trim() ? `${assistantContent}
5132
+
5133
+ ${appendix}` : appendix;
5134
+ }
5135
+
4989
5136
  // src/lib/db/chat/lazyDecrypt.ts
4990
5137
  var LRU_CAPACITY = 256;
4991
5138
  var titleCache = /* @__PURE__ */ new Map();
@@ -7061,6 +7208,9 @@ async function extractFacts(messages, options) {
7061
7208
  ...options.apiKey !== void 0 && { apiKey: options.apiKey },
7062
7209
  ...options.getToken !== void 0 && { getToken: options.getToken },
7063
7210
  ...options.baseUrl !== void 0 && { baseUrl: options.baseUrl },
7211
+ ...options.endpointOverride !== void 0 && {
7212
+ endpointOverride: options.endpointOverride
7213
+ },
7064
7214
  model: options.model ?? DEFAULT_EXTRACTION_MODEL,
7065
7215
  systemPrompt: SYSTEM_PROMPT2,
7066
7216
  userMessage: `Today's date is ${today}. Resolve any relative dates ("yesterday", "next week") against it.
@@ -10530,11 +10680,17 @@ function useChatStorage(options) {
10530
10680
  onPiiRedacted,
10531
10681
  nerDetector,
10532
10682
  onServerToolCall,
10533
- onToolCallArgumentsDelta
10683
+ onToolCallArgumentsDelta,
10684
+ toolResultsHistoryExclude,
10685
+ foldToolResultsInHistory
10534
10686
  } = options;
10535
10687
  const [currentConversationId, setCurrentConversationId] = useState4(
10536
10688
  initialConversationId || null
10537
10689
  );
10690
+ const toolResultsHistoryExcludeRef = useRef4(toolResultsHistoryExclude);
10691
+ toolResultsHistoryExcludeRef.current = toolResultsHistoryExclude;
10692
+ const foldToolResultsInHistoryRef = useRef4(foldToolResultsInHistory);
10693
+ foldToolResultsInHistoryRef.current = foldToolResultsInHistory;
10538
10694
  const nerDetectorRef = useRef4(nerDetector);
10539
10695
  nerDetectorRef.current = nerDetector;
10540
10696
  const resolvedPiiRedaction = useMemo(
@@ -11402,7 +11558,13 @@ function useChatStorage(options) {
11402
11558
  }
11403
11559
  if (includeHistory) {
11404
11560
  const validMessages = storedMessages.filter((msg) => !msg.error);
11405
- const limitedMessages = validMessages.slice(-maxHistoryMessages);
11561
+ const replayableMessages = prepareToolResultsForReplay(validMessages, {
11562
+ fold: foldToolResultsInHistoryRef.current === true,
11563
+ exclude: toolResultsHistoryExcludeRef.current,
11564
+ placeholder: DISPLAY_CARD_PLACEHOLDER
11565
+ });
11566
+ const limitedMessages = replayableMessages.slice(-maxHistoryMessages);
11567
+ const foldedHistory = limitedMessages;
11406
11568
  if (summarizeHistory && !getTokenRef.current) {
11407
11569
  getLogger().warn(
11408
11570
  "[summarize] summarizeHistory is enabled but getToken is not provided \u2014 summarization will be skipped"
@@ -11412,7 +11574,7 @@ function useChatStorage(options) {
11412
11574
  const { messagesToConvert, summarySystemMessage } = await maybeSummarizeHistory({
11413
11575
  database,
11414
11576
  conversationId: convId,
11415
- messages: limitedMessages,
11577
+ messages: foldedHistory,
11416
11578
  summarizeHistory,
11417
11579
  summaryTokenThreshold,
11418
11580
  summaryMinWindowMessages,
@@ -11782,6 +11944,7 @@ function useChatStorage(options) {
11782
11944
  uniqueId: effectiveAssistantUniqueId
11783
11945
  };
11784
11946
  let storedAssistantMessage;
11947
+ let assistantMsgQueueId;
11785
11948
  try {
11786
11949
  const assistantMsgResult = await writeOrQueue(
11787
11950
  "createMessage",
@@ -11791,6 +11954,7 @@ function useChatStorage(options) {
11791
11954
  userMsgQueueId ? [userMsgQueueId] : []
11792
11955
  );
11793
11956
  storedAssistantMessage = assistantMsgResult.result;
11957
+ assistantMsgQueueId = assistantMsgResult.queueId;
11794
11958
  if (!assistantMsgResult.queued) {
11795
11959
  void embedMessageAsync(storedAssistantMessage, maskForCall);
11796
11960
  }
@@ -11801,6 +11965,29 @@ function useChatStorage(options) {
11801
11965
  userMessage: storedUserMessage
11802
11966
  };
11803
11967
  }
11968
+ const autoToolResults = result.autoExecutedToolResults;
11969
+ let storedToolResultsMessage;
11970
+ if (autoToolResults && autoToolResults.length > 0) {
11971
+ const toolResultsOpts = {
11972
+ conversationId: convId,
11973
+ role: "user",
11974
+ content: buildToolResultContent(autoToolResults),
11975
+ model: "",
11976
+ parentMessageId: storedAssistantMessage.uniqueId,
11977
+ origin: TOOL_RESULT_ORIGIN
11978
+ };
11979
+ try {
11980
+ const toolResultsWrite = await writeOrQueue(
11981
+ "createMessage",
11982
+ toolResultsOpts,
11983
+ () => createMessageOp(storageCtx, toolResultsOpts),
11984
+ () => makeSyntheticStoredMessage(toolResultsOpts),
11985
+ assistantMsgQueueId ? [assistantMsgQueueId] : []
11986
+ );
11987
+ storedToolResultsMessage = toolResultsWrite.result;
11988
+ } catch {
11989
+ }
11990
+ }
11804
11991
  const refreshGetToken = getTokenRef.current;
11805
11992
  if (refreshGetToken) {
11806
11993
  Promise.resolve().then(() => shouldRefreshTools(getToolsChecksum(responseData), serverToolsConfig?.cache)).then((refresh) => {
@@ -11820,7 +12007,9 @@ function useChatStorage(options) {
11820
12007
  data: responseData,
11821
12008
  error: null,
11822
12009
  userMessage: storedUserMessage,
11823
- assistantMessage: storedAssistantMessage
12010
+ assistantMessage: storedAssistantMessage,
12011
+ autoExecutedToolResults: autoToolResults,
12012
+ toolResultsMessage: storedToolResultsMessage
11824
12013
  };
11825
12014
  },
11826
12015
  [
@@ -14936,6 +14936,7 @@ async function cleanupConversationSummary(database, conversationId) {
14936
14936
  var HEADER = "[Tool Execution Results]\n\n";
14937
14937
  var FOOTER = "\n\nBased on these results, continue with the task.";
14938
14938
  var ENTRY_SEPARATOR = "\n\n";
14939
+ var TOOL_RESULT_FOOTER_LINE = FOOTER.trim();
14939
14940
  var MAX_PERSISTED_TOOL_RESULT_CHARS = 1e5;
14940
14941
  function buildToolResultContent(results) {
14941
14942
  const entries = results.map((r) => `Tool "${r.name}" returned: ${JSON.stringify(r.result)}`);
@@ -14945,6 +14946,9 @@ function buildToolResultContent(results) {
14945
14946
  );
14946
14947
  return `${HEADER}${summary}${FOOTER}`;
14947
14948
  }
14949
+ function capToolResultEntries(entries, budget) {
14950
+ return capEntries(entries, budget);
14951
+ }
14948
14952
  function capEntries(entries, budget) {
14949
14953
  const smallestFirst = entries.map((_, index) => index).sort((a, b) => entries[a].length - entries[b].length);
14950
14954
  const capped = [...entries];
@@ -14969,6 +14973,112 @@ function truncationMarker(omitted) {
14969
14973
  ... (tool output truncated, ${omitted} characters omitted)`;
14970
14974
  }
14971
14975
 
14976
+ // src/lib/chat/toolResults.ts
14977
+ var TOOL_RESULTS_PREFIX = "[Tool Execution Results]";
14978
+ var DISPLAY_CARD_PLACEHOLDER = "[displayed a card to the user with the tool result]";
14979
+ var TOOL_LINE = /^Tool "([^"]+)" returned: (.*)$/;
14980
+ var TOOL_RESULT_ORIGIN = "tool_result";
14981
+ function isToolResultsRow(row) {
14982
+ if (row.origin === TOOL_RESULT_ORIGIN) return true;
14983
+ if (typeof row.origin === "string") return false;
14984
+ if (row.role !== "user" || !row.content.startsWith(TOOL_RESULTS_PREFIX)) return false;
14985
+ return parseToolResultSegments(row.content).length > 0;
14986
+ }
14987
+ function parseToolResultSegments(content) {
14988
+ const segments = [];
14989
+ for (const raw of content.split("\n")) {
14990
+ const line = raw.trim();
14991
+ const match = TOOL_LINE.exec(line);
14992
+ if (match) {
14993
+ segments.push({ name: match[1], line });
14994
+ continue;
14995
+ }
14996
+ const current = segments[segments.length - 1];
14997
+ if (!current || line === "" || line === TOOL_RESULT_FOOTER_LINE) continue;
14998
+ if (line === TOOL_RESULTS_PREFIX) continue;
14999
+ current.line = `${current.line}
15000
+ ${line}`;
15001
+ }
15002
+ return segments;
15003
+ }
15004
+ function prepareToolResultsForReplay(rows, options) {
15005
+ if (options.fold) {
15006
+ return foldToolResultsRows(rows, {
15007
+ exclude: options.exclude,
15008
+ placeholder: options.placeholder
15009
+ });
15010
+ }
15011
+ return rows.filter((row) => !isToolResultsRow(row));
15012
+ }
15013
+ function foldToolResultsRows(rows, options) {
15014
+ const exclude = new Set(options?.exclude ?? []);
15015
+ const out = [];
15016
+ const pending = [];
15017
+ const assistantIndexById = /* @__PURE__ */ new Map();
15018
+ for (const row of rows) {
15019
+ if (isToolResultsRow(row)) {
15020
+ pending.push({ row, insertionPoint: out.length });
15021
+ continue;
15022
+ }
15023
+ out.push(row);
15024
+ if (row.role === "assistant" && row.uniqueId) {
15025
+ assistantIndexById.set(row.uniqueId, out.length - 1);
15026
+ }
15027
+ }
15028
+ const byTarget = /* @__PURE__ */ new Map();
15029
+ for (const { row, insertionPoint } of pending) {
15030
+ const target = resolveFoldTarget(out, row, insertionPoint, assistantIndexById);
15031
+ if (target === void 0) continue;
15032
+ const group = byTarget.get(target);
15033
+ if (group) group.push(row);
15034
+ else byTarget.set(target, [row]);
15035
+ }
15036
+ for (const [target, groupedRows] of byTarget) {
15037
+ const assistant = out[target];
15038
+ const kept = groupedRows.flatMap((row) => parseToolResultSegments(row.content)).filter((segment) => !exclude.has(segment.name));
15039
+ if (kept.length > 0) {
15040
+ out[target] = { ...assistant, content: appendToolResults(assistant.content, kept) };
15041
+ } else if (!assistant.content.trim() && options?.placeholder) {
15042
+ out[target] = { ...assistant, content: options.placeholder };
15043
+ }
15044
+ }
15045
+ return out;
15046
+ }
15047
+ function resolveFoldTarget(out, row, insertionPoint, assistantIndexById) {
15048
+ const linked = row.parentMessageId ? assistantIndexById.get(row.parentMessageId) : void 0;
15049
+ if (linked !== void 0) return linked;
15050
+ let previous;
15051
+ for (let i = insertionPoint - 1; i >= 0; i--) {
15052
+ if (out[i].role === "assistant") {
15053
+ previous = i;
15054
+ break;
15055
+ }
15056
+ }
15057
+ let next;
15058
+ for (let i = insertionPoint; i < out.length; i++) {
15059
+ if (out[i].role === "assistant") {
15060
+ next = i;
15061
+ break;
15062
+ }
15063
+ }
15064
+ if (previous === void 0) return next;
15065
+ if (next === void 0) return previous;
15066
+ return next - insertionPoint < insertionPoint - previous ? next : previous;
15067
+ }
15068
+ var MAX_FOLDED_APPENDIX_CHARS = 2e4;
15069
+ function appendToolResults(assistantContent, kept) {
15070
+ const framing = TOOL_RESULTS_PREFIX.length + 1 + (kept.length - 1);
15071
+ const capped = capToolResultEntries(
15072
+ kept.map((segment) => segment.line),
15073
+ MAX_FOLDED_APPENDIX_CHARS - framing
15074
+ );
15075
+ const appendix = `${TOOL_RESULTS_PREFIX}
15076
+ ${capped.join("\n")}`;
15077
+ return assistantContent.trim() ? `${assistantContent}
15078
+
15079
+ ${appendix}` : appendix;
15080
+ }
15081
+
14972
15082
  // src/lib/db/chat/lazyDecrypt.ts
14973
15083
  init_useEncryption();
14974
15084
  init_encryption_utils();
@@ -17543,6 +17653,9 @@ async function extractFacts(messages, options) {
17543
17653
  ...options.apiKey !== void 0 && { apiKey: options.apiKey },
17544
17654
  ...options.getToken !== void 0 && { getToken: options.getToken },
17545
17655
  ...options.baseUrl !== void 0 && { baseUrl: options.baseUrl },
17656
+ ...options.endpointOverride !== void 0 && {
17657
+ endpointOverride: options.endpointOverride
17658
+ },
17546
17659
  model: options.model ?? DEFAULT_EXTRACTION_MODEL,
17547
17660
  systemPrompt: SYSTEM_PROMPT4,
17548
17661
  userMessage: `Today's date is ${today}. Resolve any relative dates ("yesterday", "next week") against it.
@@ -22321,11 +22434,17 @@ function useChatStorage(options) {
22321
22434
  preProcessors,
22322
22435
  piiRedaction,
22323
22436
  onPiiRedacted,
22324
- nerDetector
22437
+ nerDetector,
22438
+ toolResultsHistoryExclude,
22439
+ foldToolResultsInHistory
22325
22440
  } = options;
22326
22441
  const [currentConversationId, setCurrentConversationId] = (0, import_react5.useState)(
22327
22442
  initialConversationId || null
22328
22443
  );
22444
+ const toolResultsHistoryExcludeRef = (0, import_react5.useRef)(toolResultsHistoryExclude);
22445
+ toolResultsHistoryExcludeRef.current = toolResultsHistoryExclude;
22446
+ const foldToolResultsInHistoryRef = (0, import_react5.useRef)(foldToolResultsInHistory);
22447
+ foldToolResultsInHistoryRef.current = foldToolResultsInHistory;
22329
22448
  const nerDetectorRef = (0, import_react5.useRef)(nerDetector);
22330
22449
  nerDetectorRef.current = nerDetector;
22331
22450
  const resolvedPiiRedaction = (0, import_react5.useMemo)(
@@ -23333,7 +23452,12 @@ function useChatStorage(options) {
23333
23452
  }
23334
23453
  if (includeHistory) {
23335
23454
  const validMessages = storedMessages.filter((msg) => !msg.error);
23336
- const limitedMessages = validMessages.slice(-maxHistoryMessages);
23455
+ const replayableMessages = prepareToolResultsForReplay(validMessages, {
23456
+ fold: foldToolResultsInHistoryRef.current === true,
23457
+ exclude: toolResultsHistoryExcludeRef.current,
23458
+ placeholder: DISPLAY_CARD_PLACEHOLDER
23459
+ });
23460
+ const limitedMessages = replayableMessages.slice(-maxHistoryMessages);
23337
23461
  if (!fileContextForRequest) {
23338
23462
  for (let i = limitedMessages.length - 1; i >= 0; i--) {
23339
23463
  const msg = limitedMessages[i];
@@ -23343,6 +23467,7 @@ function useChatStorage(options) {
23343
23467
  }
23344
23468
  }
23345
23469
  }
23470
+ const foldedHistory = limitedMessages;
23346
23471
  let encryptionKey;
23347
23472
  if (walletAddress && hasEncryptionKey(walletAddress) && isOPFSSupported()) {
23348
23473
  try {
@@ -23359,7 +23484,7 @@ function useChatStorage(options) {
23359
23484
  const { messagesToConvert, summarySystemMessage } = await maybeSummarizeHistory({
23360
23485
  database,
23361
23486
  conversationId: convId,
23362
- messages: limitedMessages,
23487
+ messages: foldedHistory,
23363
23488
  summarizeHistory,
23364
23489
  summaryTokenThreshold,
23365
23490
  summaryMinWindowMessages,
@@ -23817,37 +23942,25 @@ function useChatStorage(options) {
23817
23942
  }
23818
23943
  }
23819
23944
  const autoToolResults = result.autoExecutedToolResults;
23945
+ let storedToolResultsMessage;
23820
23946
  if (autoToolResults && autoToolResults.length > 0) {
23821
- const toolResultContent = buildToolResultContent(autoToolResults);
23947
+ const toolResultsOpts = {
23948
+ conversationId: convId,
23949
+ role: "user",
23950
+ content: buildToolResultContent(autoToolResults),
23951
+ model: "",
23952
+ parentMessageId: storedAssistantMessage.uniqueId,
23953
+ origin: TOOL_RESULT_ORIGIN
23954
+ };
23822
23955
  try {
23823
- await writeOrQueue(
23956
+ const toolResultsWrite = await writeOrQueue(
23824
23957
  "createMessage",
23825
- {
23826
- conversationId: convId,
23827
- role: "user",
23828
- content: toolResultContent,
23829
- model: "",
23830
- parentMessageId: storedAssistantMessage.uniqueId,
23831
- origin: "tool_result"
23832
- },
23833
- () => createMessageOp(storageCtx, {
23834
- conversationId: convId,
23835
- role: "user",
23836
- content: toolResultContent,
23837
- model: "",
23838
- parentMessageId: storedAssistantMessage.uniqueId,
23839
- origin: "tool_result"
23840
- }),
23841
- () => makeSyntheticStoredMessage({
23842
- conversationId: convId,
23843
- role: "user",
23844
- content: toolResultContent,
23845
- model: "",
23846
- parentMessageId: storedAssistantMessage.uniqueId,
23847
- origin: "tool_result"
23848
- }),
23958
+ toolResultsOpts,
23959
+ () => createMessageOp(storageCtx, toolResultsOpts),
23960
+ () => makeSyntheticStoredMessage(toolResultsOpts),
23849
23961
  assistantMsgQueueId ? [assistantMsgQueueId] : []
23850
23962
  );
23963
+ storedToolResultsMessage = toolResultsWrite.result;
23851
23964
  } catch {
23852
23965
  }
23853
23966
  }
@@ -23870,7 +23983,8 @@ function useChatStorage(options) {
23870
23983
  error: null,
23871
23984
  userMessage: storedUserMessage,
23872
23985
  assistantMessage: storedAssistantMessage,
23873
- autoExecutedToolResults: result.autoExecutedToolResults
23986
+ autoExecutedToolResults: autoToolResults,
23987
+ toolResultsMessage: storedToolResultsMessage
23874
23988
  };
23875
23989
  },
23876
23990
  // eslint-disable-next-line react-hooks/exhaustive-deps -- intentionally omitting stable refs and config values that don't change identity
@@ -5220,6 +5220,40 @@ interface BaseUseChatStorageOptions {
5220
5220
  * Defaults to the hardcoded MCP_R2_DOMAIN from clientConfig.
5221
5221
  */
5222
5222
  mcpR2Domain?: string;
5223
+ /**
5224
+ * Tool names whose persisted results must never be replayed to the model.
5225
+ *
5226
+ * A turn's auto-executed tool results are stored as a synthetic
5227
+ * `[Tool Execution Results]` row and, on a replayed send, folded back onto the
5228
+ * assistant turn they belong to — that is what lets a follow-up question about a
5229
+ * tool's output work after a reload. Name a tool here when its payload exists for
5230
+ * the RENDERER rather than the model: a display card can carry data the model was
5231
+ * deliberately never given (People Nearby's card holds third parties' snapped
5232
+ * coordinates, which the search result strips), and replaying it would hand that
5233
+ * data straight back.
5234
+ *
5235
+ * Hook-level rather than per-send on purpose: an exclusion that has to be
5236
+ * remembered at every call site is one bad send away from leaking.
5237
+ */
5238
+ toolResultsHistoryExclude?: string[];
5239
+ /**
5240
+ * Fold persisted `[Tool Execution Results]` rows onto the assistant turn that produced them
5241
+ * when replaying stored history, instead of dropping them.
5242
+ *
5243
+ * **Defaults to `false`, and that default is deliberate.** Folding is the better behaviour —
5244
+ * it is what lets a follow-up about a tool's output work after a reload — but it moves the
5245
+ * payload from a `role: "user"` row onto an `assistant` row. Any consumer that scrubs these
5246
+ * rows by checking `role === "user"` plus the content prefix (which is how both apps did it
5247
+ * before this option existed) stops catching them the moment folding turns on, and starts
5248
+ * replaying whatever the row held. Opting in is therefore a statement that the caller has
5249
+ * checked its own filters and set {@link toolResultsHistoryExclude} for any payload that must
5250
+ * not reach the model.
5251
+ *
5252
+ * With it off, rows are dropped from the replayed history rather than sent verbatim. Verbatim
5253
+ * would put two consecutive `user` turns on the wire, and the model answers the previous turn
5254
+ * instead of the new prompt.
5255
+ */
5256
+ foldToolResultsInHistory?: boolean;
5223
5257
  /**
5224
5258
  * Pre-processors run after the last user message is received but before
5225
5259
  * the first LLM request. Each receives the prompt text and a shared
@@ -7094,6 +7128,31 @@ interface QuarantinedMemoryInfo {
7094
7128
  interface ExtractFactsOptions extends PortalLlmAuth {
7095
7129
  baseUrl?: string;
7096
7130
  model?: string;
7131
+ /**
7132
+ * Optional per-call request path override, forwarded to
7133
+ * {@link callPortalJsonCompletion}. When set, the extraction call POSTs to
7134
+ * `baseUrl + endpointOverride` instead of the default
7135
+ * `/api/v1/chat/completions` — path only, body unchanged. Lets callers route
7136
+ * this internal-utility pass to a dedicated endpoint. Invalid values throw at
7137
+ * call time (see {@link validateEndpointOverride}).
7138
+ *
7139
+ * Why this exists (anuma-ai/ai-memoryless-client#5536): auto-extraction is the
7140
+ * highest-volume first-party background call in the product — one per
7141
+ * extracting turn, on every platform — and it carries no flow fingerprint, so
7142
+ * the portal's freeloader detector classifies it as scripted abuse. In reject
7143
+ * mode that 403s every basic-tier extraction, which surfaces here as
7144
+ * `onExhaustedEmpty` → `empty-after-retry` and leaves free-tier vaults empty.
7145
+ * {@link TopicExtractOptions.endpointOverride} already exists for the same
7146
+ * reason on the topic pass; this is the fact pass catching up.
7147
+ *
7148
+ * IMPORTANT — the utility endpoint clamps to a PRICE CEILING and never
7149
+ * rejects, so pointing this at `/api/v1/utility/chat/completions` while the
7150
+ * portal's ceiling prices below {@link DEFAULT_EXTRACTION_MODEL} silently
7151
+ * rewrites the model instead of 403-ing. That trades a visible failure for an
7152
+ * invisible quality regression: raise `PORTAL_UTILITY_CEILING_MODEL` to at
7153
+ * least the extraction model's rate BEFORE setting this in a client.
7154
+ */
7155
+ endpointOverride?: string;
7097
7156
  /** Override the global fetch implementation (useful for tests). */
7098
7157
  fetchFn?: typeof fetch;
7099
7158
  /**
@@ -10329,6 +10388,12 @@ type SendMessageWithStorageResult = {
10329
10388
  name: string;
10330
10389
  result: unknown;
10331
10390
  }[];
10391
+ /**
10392
+ * The synthetic `[Tool Execution Results]` row those results were persisted as, so a caller can
10393
+ * key its transient overlay on the id the SDK actually wrote instead of deriving one (#5519).
10394
+ * Absent when no tool ran, or when that (non-fatal) write failed.
10395
+ */
10396
+ toolResultsMessage?: StoredMessage;
10332
10397
  } | {
10333
10398
  data: ApiResponse;
10334
10399
  error: null;
@@ -5220,6 +5220,40 @@ interface BaseUseChatStorageOptions {
5220
5220
  * Defaults to the hardcoded MCP_R2_DOMAIN from clientConfig.
5221
5221
  */
5222
5222
  mcpR2Domain?: string;
5223
+ /**
5224
+ * Tool names whose persisted results must never be replayed to the model.
5225
+ *
5226
+ * A turn's auto-executed tool results are stored as a synthetic
5227
+ * `[Tool Execution Results]` row and, on a replayed send, folded back onto the
5228
+ * assistant turn they belong to — that is what lets a follow-up question about a
5229
+ * tool's output work after a reload. Name a tool here when its payload exists for
5230
+ * the RENDERER rather than the model: a display card can carry data the model was
5231
+ * deliberately never given (People Nearby's card holds third parties' snapped
5232
+ * coordinates, which the search result strips), and replaying it would hand that
5233
+ * data straight back.
5234
+ *
5235
+ * Hook-level rather than per-send on purpose: an exclusion that has to be
5236
+ * remembered at every call site is one bad send away from leaking.
5237
+ */
5238
+ toolResultsHistoryExclude?: string[];
5239
+ /**
5240
+ * Fold persisted `[Tool Execution Results]` rows onto the assistant turn that produced them
5241
+ * when replaying stored history, instead of dropping them.
5242
+ *
5243
+ * **Defaults to `false`, and that default is deliberate.** Folding is the better behaviour —
5244
+ * it is what lets a follow-up about a tool's output work after a reload — but it moves the
5245
+ * payload from a `role: "user"` row onto an `assistant` row. Any consumer that scrubs these
5246
+ * rows by checking `role === "user"` plus the content prefix (which is how both apps did it
5247
+ * before this option existed) stops catching them the moment folding turns on, and starts
5248
+ * replaying whatever the row held. Opting in is therefore a statement that the caller has
5249
+ * checked its own filters and set {@link toolResultsHistoryExclude} for any payload that must
5250
+ * not reach the model.
5251
+ *
5252
+ * With it off, rows are dropped from the replayed history rather than sent verbatim. Verbatim
5253
+ * would put two consecutive `user` turns on the wire, and the model answers the previous turn
5254
+ * instead of the new prompt.
5255
+ */
5256
+ foldToolResultsInHistory?: boolean;
5223
5257
  /**
5224
5258
  * Pre-processors run after the last user message is received but before
5225
5259
  * the first LLM request. Each receives the prompt text and a shared
@@ -7094,6 +7128,31 @@ interface QuarantinedMemoryInfo {
7094
7128
  interface ExtractFactsOptions extends PortalLlmAuth {
7095
7129
  baseUrl?: string;
7096
7130
  model?: string;
7131
+ /**
7132
+ * Optional per-call request path override, forwarded to
7133
+ * {@link callPortalJsonCompletion}. When set, the extraction call POSTs to
7134
+ * `baseUrl + endpointOverride` instead of the default
7135
+ * `/api/v1/chat/completions` — path only, body unchanged. Lets callers route
7136
+ * this internal-utility pass to a dedicated endpoint. Invalid values throw at
7137
+ * call time (see {@link validateEndpointOverride}).
7138
+ *
7139
+ * Why this exists (anuma-ai/ai-memoryless-client#5536): auto-extraction is the
7140
+ * highest-volume first-party background call in the product — one per
7141
+ * extracting turn, on every platform — and it carries no flow fingerprint, so
7142
+ * the portal's freeloader detector classifies it as scripted abuse. In reject
7143
+ * mode that 403s every basic-tier extraction, which surfaces here as
7144
+ * `onExhaustedEmpty` → `empty-after-retry` and leaves free-tier vaults empty.
7145
+ * {@link TopicExtractOptions.endpointOverride} already exists for the same
7146
+ * reason on the topic pass; this is the fact pass catching up.
7147
+ *
7148
+ * IMPORTANT — the utility endpoint clamps to a PRICE CEILING and never
7149
+ * rejects, so pointing this at `/api/v1/utility/chat/completions` while the
7150
+ * portal's ceiling prices below {@link DEFAULT_EXTRACTION_MODEL} silently
7151
+ * rewrites the model instead of 403-ing. That trades a visible failure for an
7152
+ * invisible quality regression: raise `PORTAL_UTILITY_CEILING_MODEL` to at
7153
+ * least the extraction model's rate BEFORE setting this in a client.
7154
+ */
7155
+ endpointOverride?: string;
7097
7156
  /** Override the global fetch implementation (useful for tests). */
7098
7157
  fetchFn?: typeof fetch;
7099
7158
  /**
@@ -10329,6 +10388,12 @@ type SendMessageWithStorageResult = {
10329
10388
  name: string;
10330
10389
  result: unknown;
10331
10390
  }[];
10391
+ /**
10392
+ * The synthetic `[Tool Execution Results]` row those results were persisted as, so a caller can
10393
+ * key its transient overlay on the id the SDK actually wrote instead of deriving one (#5519).
10394
+ * Absent when no tool ran, or when that (non-fatal) write failed.
10395
+ */
10396
+ toolResultsMessage?: StoredMessage;
10332
10397
  } | {
10333
10398
  data: ApiResponse;
10334
10399
  error: null;