@anuma/sdk 6.3.1 → 6.4.1

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.
@@ -3636,6 +3636,12 @@ async function embedMessage(ctx, messageId, options) {
3636
3636
  if (message.origin === NON_EMBEDDABLE_ORIGIN) {
3637
3637
  return message;
3638
3638
  }
3639
+ if (isEncrypted(message.content)) {
3640
+ getLogger().warn(
3641
+ `memoryEngine: message ${messageId} is still encrypted (key unavailable?) \u2014 not embedded`
3642
+ );
3643
+ return message;
3644
+ }
3639
3645
  const embedding = await generateEmbedding(message.content, options);
3640
3646
  const embeddingModel = options.model ?? DEFAULT_API_EMBEDDING_MODEL;
3641
3647
  return updateMessageEmbeddingOp(ctx, messageId, embedding, embeddingModel);
@@ -3643,11 +3649,17 @@ async function embedMessage(ctx, messageId, options) {
3643
3649
  async function embedAllMessages(ctx, options, filter) {
3644
3650
  const embeddingModel = options.model ?? DEFAULT_API_EMBEDDING_MODEL;
3645
3651
  let embeddedCount = 0;
3652
+ let stillEncrypted = 0;
3653
+ let considered = 0;
3654
+ let sealedRowsSeen = 0;
3655
+ let rowsSeen = 0;
3646
3656
  const conversations = await getConversationsOp(ctx);
3647
3657
  const targetConversations = filter?.conversationId ? conversations.filter((c) => c.conversationId === filter.conversationId) : conversations;
3648
3658
  for (const conv of targetConversations) {
3649
3659
  const messages = await getMessagesOp(ctx, conv.conversationId);
3650
3660
  for (const message of messages) {
3661
+ rowsSeen++;
3662
+ if (isEncrypted(message.content)) sealedRowsSeen++;
3651
3663
  if (message.vector && message.vector.length > 0) {
3652
3664
  continue;
3653
3665
  }
@@ -3660,6 +3672,11 @@ async function embedAllMessages(ctx, options, filter) {
3660
3672
  if (message.origin === NON_EMBEDDABLE_ORIGIN) {
3661
3673
  continue;
3662
3674
  }
3675
+ considered++;
3676
+ if (isEncrypted(message.content)) {
3677
+ stillEncrypted++;
3678
+ continue;
3679
+ }
3663
3680
  const minLength = filter?.minContentLength ?? DEFAULT_MIN_CONTENT_LENGTH;
3664
3681
  if (message.content.length < minLength) {
3665
3682
  continue;
@@ -3674,6 +3691,13 @@ async function embedAllMessages(ctx, options, filter) {
3674
3691
  }
3675
3692
  }
3676
3693
  }
3694
+ if (stillEncrypted > 0 || sealedRowsSeen > 0) {
3695
+ getLogger().error(
3696
+ "memoryEngine: messages still encrypted (key unavailable?) \u2014 excluded from embedding",
3697
+ void 0,
3698
+ { stillEncrypted, considered, sealedRowsSeen, rowsSeen }
3699
+ );
3700
+ }
3677
3701
  return embeddedCount;
3678
3702
  }
3679
3703
  async function chunkAndEmbedMessage(ctx, messageId, options) {
@@ -3688,6 +3712,12 @@ async function chunkAndEmbedMessage(ctx, messageId, options) {
3688
3712
  if (message.origin === NON_EMBEDDABLE_ORIGIN) {
3689
3713
  return message;
3690
3714
  }
3715
+ if (isEncrypted(message.content)) {
3716
+ getLogger().warn(
3717
+ `memoryEngine: message ${messageId} is still encrypted (key unavailable?) \u2014 not embedded`
3718
+ );
3719
+ return message;
3720
+ }
3691
3721
  const embeddingModel = options.model ?? DEFAULT_API_EMBEDDING_MODEL;
3692
3722
  if (!shouldChunkMessage(message.content, chunkSize)) {
3693
3723
  const embedding = await generateEmbedding(message.content, options);
@@ -3712,9 +3742,15 @@ async function chunkAndEmbedAllMessages(ctx, options, filter) {
3712
3742
  const targetConversations = filter?.conversationId ? conversations.filter((c) => c.conversationId === filter.conversationId) : conversations;
3713
3743
  const shortMessages = [];
3714
3744
  const longMessages = [];
3745
+ let stillEncrypted = 0;
3746
+ let considered = 0;
3747
+ let sealedRowsSeen = 0;
3748
+ let rowsSeen = 0;
3715
3749
  for (const conv of targetConversations) {
3716
3750
  const messages = await getMessagesOp(ctx, conv.conversationId);
3717
3751
  for (const message of messages) {
3752
+ rowsSeen++;
3753
+ if (isEncrypted(message.content)) sealedRowsSeen++;
3718
3754
  const isStale = message.embeddingModel !== void 0 && message.embeddingModel !== null && message.embeddingModel !== embeddingModel;
3719
3755
  if (message.chunks && message.chunks.length > 0 && !isStale) continue;
3720
3756
  const hasVector = message.vector && message.vector.length > 0;
@@ -3722,6 +3758,11 @@ async function chunkAndEmbedAllMessages(ctx, options, filter) {
3722
3758
  if (filter?.roles && !filter.roles.includes(message.role)) continue;
3723
3759
  if (message.role === "system") continue;
3724
3760
  if (message.origin === NON_EMBEDDABLE_ORIGIN) continue;
3761
+ considered++;
3762
+ if (isEncrypted(message.content)) {
3763
+ stillEncrypted++;
3764
+ continue;
3765
+ }
3725
3766
  if (message.content.length < minLength) continue;
3726
3767
  if (shouldChunkMessage(message.content, chunkSize)) {
3727
3768
  longMessages.push({
@@ -3733,6 +3774,13 @@ async function chunkAndEmbedAllMessages(ctx, options, filter) {
3733
3774
  }
3734
3775
  }
3735
3776
  }
3777
+ if (stillEncrypted > 0 || sealedRowsSeen > 0) {
3778
+ getLogger().error(
3779
+ "memoryEngine: messages still encrypted (key unavailable?) \u2014 excluded from embedding",
3780
+ void 0,
3781
+ { stillEncrypted, considered, sealedRowsSeen, rowsSeen }
3782
+ );
3783
+ }
3736
3784
  let embeddedCount = 0;
3737
3785
  if (shortMessages.length > 0) {
3738
3786
  try {
@@ -3783,6 +3831,7 @@ var init_embeddings = __esm({
3783
3831
  "src/lib/memoryEngine/embeddings.ts"() {
3784
3832
  "use strict";
3785
3833
  init_operations();
3834
+ init_encryption_utils();
3786
3835
  init_logger();
3787
3836
  init_chunking();
3788
3837
  init_constants();
@@ -14936,6 +14985,7 @@ async function cleanupConversationSummary(database, conversationId) {
14936
14985
  var HEADER = "[Tool Execution Results]\n\n";
14937
14986
  var FOOTER = "\n\nBased on these results, continue with the task.";
14938
14987
  var ENTRY_SEPARATOR = "\n\n";
14988
+ var TOOL_RESULT_FOOTER_LINE = FOOTER.trim();
14939
14989
  var MAX_PERSISTED_TOOL_RESULT_CHARS = 1e5;
14940
14990
  function buildToolResultContent(results) {
14941
14991
  const entries = results.map((r) => `Tool "${r.name}" returned: ${JSON.stringify(r.result)}`);
@@ -14945,6 +14995,9 @@ function buildToolResultContent(results) {
14945
14995
  );
14946
14996
  return `${HEADER}${summary}${FOOTER}`;
14947
14997
  }
14998
+ function capToolResultEntries(entries, budget) {
14999
+ return capEntries(entries, budget);
15000
+ }
14948
15001
  function capEntries(entries, budget) {
14949
15002
  const smallestFirst = entries.map((_, index) => index).sort((a, b) => entries[a].length - entries[b].length);
14950
15003
  const capped = [...entries];
@@ -14969,6 +15022,112 @@ function truncationMarker(omitted) {
14969
15022
  ... (tool output truncated, ${omitted} characters omitted)`;
14970
15023
  }
14971
15024
 
15025
+ // src/lib/chat/toolResults.ts
15026
+ var TOOL_RESULTS_PREFIX = "[Tool Execution Results]";
15027
+ var DISPLAY_CARD_PLACEHOLDER = "[displayed a card to the user with the tool result]";
15028
+ var TOOL_LINE = /^Tool "([^"]+)" returned: (.*)$/;
15029
+ var TOOL_RESULT_ORIGIN = "tool_result";
15030
+ function isToolResultsRow(row) {
15031
+ if (row.origin === TOOL_RESULT_ORIGIN) return true;
15032
+ if (typeof row.origin === "string") return false;
15033
+ if (row.role !== "user" || !row.content.startsWith(TOOL_RESULTS_PREFIX)) return false;
15034
+ return parseToolResultSegments(row.content).length > 0;
15035
+ }
15036
+ function parseToolResultSegments(content) {
15037
+ const segments = [];
15038
+ for (const raw of content.split("\n")) {
15039
+ const line = raw.trim();
15040
+ const match = TOOL_LINE.exec(line);
15041
+ if (match) {
15042
+ segments.push({ name: match[1], line });
15043
+ continue;
15044
+ }
15045
+ const current = segments[segments.length - 1];
15046
+ if (!current || line === "" || line === TOOL_RESULT_FOOTER_LINE) continue;
15047
+ if (line === TOOL_RESULTS_PREFIX) continue;
15048
+ current.line = `${current.line}
15049
+ ${line}`;
15050
+ }
15051
+ return segments;
15052
+ }
15053
+ function prepareToolResultsForReplay(rows, options) {
15054
+ if (options.fold) {
15055
+ return foldToolResultsRows(rows, {
15056
+ exclude: options.exclude,
15057
+ placeholder: options.placeholder
15058
+ });
15059
+ }
15060
+ return rows.filter((row) => !isToolResultsRow(row));
15061
+ }
15062
+ function foldToolResultsRows(rows, options) {
15063
+ const exclude = new Set(options?.exclude ?? []);
15064
+ const out = [];
15065
+ const pending = [];
15066
+ const assistantIndexById = /* @__PURE__ */ new Map();
15067
+ for (const row of rows) {
15068
+ if (isToolResultsRow(row)) {
15069
+ pending.push({ row, insertionPoint: out.length });
15070
+ continue;
15071
+ }
15072
+ out.push(row);
15073
+ if (row.role === "assistant" && row.uniqueId) {
15074
+ assistantIndexById.set(row.uniqueId, out.length - 1);
15075
+ }
15076
+ }
15077
+ const byTarget = /* @__PURE__ */ new Map();
15078
+ for (const { row, insertionPoint } of pending) {
15079
+ const target = resolveFoldTarget(out, row, insertionPoint, assistantIndexById);
15080
+ if (target === void 0) continue;
15081
+ const group = byTarget.get(target);
15082
+ if (group) group.push(row);
15083
+ else byTarget.set(target, [row]);
15084
+ }
15085
+ for (const [target, groupedRows] of byTarget) {
15086
+ const assistant = out[target];
15087
+ const kept = groupedRows.flatMap((row) => parseToolResultSegments(row.content)).filter((segment) => !exclude.has(segment.name));
15088
+ if (kept.length > 0) {
15089
+ out[target] = { ...assistant, content: appendToolResults(assistant.content, kept) };
15090
+ } else if (!assistant.content.trim() && options?.placeholder) {
15091
+ out[target] = { ...assistant, content: options.placeholder };
15092
+ }
15093
+ }
15094
+ return out;
15095
+ }
15096
+ function resolveFoldTarget(out, row, insertionPoint, assistantIndexById) {
15097
+ const linked = row.parentMessageId ? assistantIndexById.get(row.parentMessageId) : void 0;
15098
+ if (linked !== void 0) return linked;
15099
+ let previous;
15100
+ for (let i = insertionPoint - 1; i >= 0; i--) {
15101
+ if (out[i].role === "assistant") {
15102
+ previous = i;
15103
+ break;
15104
+ }
15105
+ }
15106
+ let next;
15107
+ for (let i = insertionPoint; i < out.length; i++) {
15108
+ if (out[i].role === "assistant") {
15109
+ next = i;
15110
+ break;
15111
+ }
15112
+ }
15113
+ if (previous === void 0) return next;
15114
+ if (next === void 0) return previous;
15115
+ return next - insertionPoint < insertionPoint - previous ? next : previous;
15116
+ }
15117
+ var MAX_FOLDED_APPENDIX_CHARS = 2e4;
15118
+ function appendToolResults(assistantContent, kept) {
15119
+ const framing = TOOL_RESULTS_PREFIX.length + 1 + (kept.length - 1);
15120
+ const capped = capToolResultEntries(
15121
+ kept.map((segment) => segment.line),
15122
+ MAX_FOLDED_APPENDIX_CHARS - framing
15123
+ );
15124
+ const appendix = `${TOOL_RESULTS_PREFIX}
15125
+ ${capped.join("\n")}`;
15126
+ return assistantContent.trim() ? `${assistantContent}
15127
+
15128
+ ${appendix}` : appendix;
15129
+ }
15130
+
14972
15131
  // src/lib/db/chat/lazyDecrypt.ts
14973
15132
  init_useEncryption();
14974
15133
  init_encryption_utils();
@@ -22324,11 +22483,17 @@ function useChatStorage(options) {
22324
22483
  preProcessors,
22325
22484
  piiRedaction,
22326
22485
  onPiiRedacted,
22327
- nerDetector
22486
+ nerDetector,
22487
+ toolResultsHistoryExclude,
22488
+ foldToolResultsInHistory
22328
22489
  } = options;
22329
22490
  const [currentConversationId, setCurrentConversationId] = (0, import_react5.useState)(
22330
22491
  initialConversationId || null
22331
22492
  );
22493
+ const toolResultsHistoryExcludeRef = (0, import_react5.useRef)(toolResultsHistoryExclude);
22494
+ toolResultsHistoryExcludeRef.current = toolResultsHistoryExclude;
22495
+ const foldToolResultsInHistoryRef = (0, import_react5.useRef)(foldToolResultsInHistory);
22496
+ foldToolResultsInHistoryRef.current = foldToolResultsInHistory;
22332
22497
  const nerDetectorRef = (0, import_react5.useRef)(nerDetector);
22333
22498
  nerDetectorRef.current = nerDetector;
22334
22499
  const resolvedPiiRedaction = (0, import_react5.useMemo)(
@@ -23336,7 +23501,12 @@ function useChatStorage(options) {
23336
23501
  }
23337
23502
  if (includeHistory) {
23338
23503
  const validMessages = storedMessages.filter((msg) => !msg.error);
23339
- const limitedMessages = validMessages.slice(-maxHistoryMessages);
23504
+ const replayableMessages = prepareToolResultsForReplay(validMessages, {
23505
+ fold: foldToolResultsInHistoryRef.current === true,
23506
+ exclude: toolResultsHistoryExcludeRef.current,
23507
+ placeholder: DISPLAY_CARD_PLACEHOLDER
23508
+ });
23509
+ const limitedMessages = replayableMessages.slice(-maxHistoryMessages);
23340
23510
  if (!fileContextForRequest) {
23341
23511
  for (let i = limitedMessages.length - 1; i >= 0; i--) {
23342
23512
  const msg = limitedMessages[i];
@@ -23346,6 +23516,7 @@ function useChatStorage(options) {
23346
23516
  }
23347
23517
  }
23348
23518
  }
23519
+ const foldedHistory = limitedMessages;
23349
23520
  let encryptionKey;
23350
23521
  if (walletAddress && hasEncryptionKey(walletAddress) && isOPFSSupported()) {
23351
23522
  try {
@@ -23362,7 +23533,7 @@ function useChatStorage(options) {
23362
23533
  const { messagesToConvert, summarySystemMessage } = await maybeSummarizeHistory({
23363
23534
  database,
23364
23535
  conversationId: convId,
23365
- messages: limitedMessages,
23536
+ messages: foldedHistory,
23366
23537
  summarizeHistory,
23367
23538
  summaryTokenThreshold,
23368
23539
  summaryMinWindowMessages,
@@ -23820,37 +23991,25 @@ function useChatStorage(options) {
23820
23991
  }
23821
23992
  }
23822
23993
  const autoToolResults = result.autoExecutedToolResults;
23994
+ let storedToolResultsMessage;
23823
23995
  if (autoToolResults && autoToolResults.length > 0) {
23824
- const toolResultContent = buildToolResultContent(autoToolResults);
23996
+ const toolResultsOpts = {
23997
+ conversationId: convId,
23998
+ role: "user",
23999
+ content: buildToolResultContent(autoToolResults),
24000
+ model: "",
24001
+ parentMessageId: storedAssistantMessage.uniqueId,
24002
+ origin: TOOL_RESULT_ORIGIN
24003
+ };
23825
24004
  try {
23826
- await writeOrQueue(
24005
+ const toolResultsWrite = await writeOrQueue(
23827
24006
  "createMessage",
23828
- {
23829
- conversationId: convId,
23830
- role: "user",
23831
- content: toolResultContent,
23832
- model: "",
23833
- parentMessageId: storedAssistantMessage.uniqueId,
23834
- origin: "tool_result"
23835
- },
23836
- () => createMessageOp(storageCtx, {
23837
- conversationId: convId,
23838
- role: "user",
23839
- content: toolResultContent,
23840
- model: "",
23841
- parentMessageId: storedAssistantMessage.uniqueId,
23842
- origin: "tool_result"
23843
- }),
23844
- () => makeSyntheticStoredMessage({
23845
- conversationId: convId,
23846
- role: "user",
23847
- content: toolResultContent,
23848
- model: "",
23849
- parentMessageId: storedAssistantMessage.uniqueId,
23850
- origin: "tool_result"
23851
- }),
24007
+ toolResultsOpts,
24008
+ () => createMessageOp(storageCtx, toolResultsOpts),
24009
+ () => makeSyntheticStoredMessage(toolResultsOpts),
23852
24010
  assistantMsgQueueId ? [assistantMsgQueueId] : []
23853
24011
  );
24012
+ storedToolResultsMessage = toolResultsWrite.result;
23854
24013
  } catch {
23855
24014
  }
23856
24015
  }
@@ -23873,7 +24032,8 @@ function useChatStorage(options) {
23873
24032
  error: null,
23874
24033
  userMessage: storedUserMessage,
23875
24034
  assistantMessage: storedAssistantMessage,
23876
- autoExecutedToolResults: result.autoExecutedToolResults
24035
+ autoExecutedToolResults: autoToolResults,
24036
+ toolResultsMessage: storedToolResultsMessage
23877
24037
  };
23878
24038
  },
23879
24039
  // 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
@@ -10354,6 +10388,12 @@ type SendMessageWithStorageResult = {
10354
10388
  name: string;
10355
10389
  result: unknown;
10356
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;
10357
10397
  } | {
10358
10398
  data: ApiResponse;
10359
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
@@ -10354,6 +10388,12 @@ type SendMessageWithStorageResult = {
10354
10388
  name: string;
10355
10389
  result: unknown;
10356
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;
10357
10397
  } | {
10358
10398
  data: ApiResponse;
10359
10399
  error: null;