@mastra/memory 1.26.1-alpha.0 → 1.26.1-alpha.3

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.
@@ -18542,33 +18542,62 @@ var TokenCounter = class TokenCounter {
18542
18542
  if (isImageAttachment) await resolveImageDimensionsAsync(part);
18543
18543
  return this.countAttachmentPartSync(part);
18544
18544
  }
18545
+ /**
18546
+ * Count the name and the arguments of a tool call. Every state before the tool produces an
18547
+ * output holds the same call signature in the context window, so all of those states share
18548
+ * these cache kinds. `buildEstimateKey` hashes the text, so a shared kind stays correct and
18549
+ * keeps the estimate warm while the invocation moves from one state to the next.
18550
+ */
18551
+ countToolCallSignature(part, invocation) {
18552
+ let tokens = 0;
18553
+ let overheadDelta = 0;
18554
+ if (invocation.toolName) tokens += this.readOrPersistPartEstimate(part, "tool-call-name", invocation.toolName);
18555
+ if (invocation.args) if (typeof invocation.args === "string") tokens += this.readOrPersistPartEstimate(part, "tool-call-args", invocation.args);
18556
+ else {
18557
+ const argsJson = JSON.stringify(invocation.args);
18558
+ tokens += this.readOrPersistPartEstimate(part, "tool-call-args-json", argsJson);
18559
+ overheadDelta -= 12;
18560
+ }
18561
+ return {
18562
+ tokens,
18563
+ overheadDelta
18564
+ };
18565
+ }
18545
18566
  countNonAttachmentPart(part) {
18546
18567
  let overheadDelta = 0;
18547
- let toolResultDelta = 0;
18568
+ let extraMessageDelta = 0;
18548
18569
  if (part.type === "text") return {
18549
18570
  tokens: this.readOrPersistPartEstimate(part, "text", part.text),
18550
18571
  overheadDelta,
18551
- toolResultDelta
18572
+ extraMessageDelta
18552
18573
  };
18553
18574
  if (part.type === "tool-invocation") {
18554
18575
  const invocation = part.toolInvocation;
18576
+ const state = invocation.state;
18555
18577
  let tokens = 0;
18556
- if (invocation.state === "call" || invocation.state === "partial-call") {
18557
- if (invocation.toolName) tokens += this.readOrPersistPartEstimate(part, `tool-${invocation.state}-name`, invocation.toolName);
18558
- if (invocation.args) if (typeof invocation.args === "string") tokens += this.readOrPersistPartEstimate(part, `tool-${invocation.state}-args`, invocation.args);
18559
- else {
18560
- const argsJson = JSON.stringify(invocation.args);
18561
- tokens += this.readOrPersistPartEstimate(part, `tool-${invocation.state}-args-json`, argsJson);
18562
- overheadDelta -= 12;
18563
- }
18578
+ if (state === "call" || state === "partial-call" || state === "approval-requested") {
18579
+ const signature = this.countToolCallSignature(part, invocation);
18580
+ return {
18581
+ tokens: signature.tokens,
18582
+ overheadDelta: overheadDelta + signature.overheadDelta,
18583
+ extraMessageDelta
18584
+ };
18585
+ }
18586
+ if (state === "approval-responded") {
18587
+ extraMessageDelta++;
18588
+ const signature = this.countToolCallSignature(part, invocation);
18589
+ tokens += signature.tokens;
18590
+ overheadDelta += signature.overheadDelta;
18591
+ const reason = invocation.approval?.reason;
18592
+ if (reason) tokens += this.readOrPersistPartEstimate(part, "tool-approval-reason", reason);
18564
18593
  return {
18565
18594
  tokens,
18566
18595
  overheadDelta,
18567
- toolResultDelta
18596
+ extraMessageDelta
18568
18597
  };
18569
18598
  }
18570
- if (invocation.state === "result") {
18571
- toolResultDelta++;
18599
+ if (state === "result") {
18600
+ extraMessageDelta++;
18572
18601
  const { value: resultForCounting, usingStoredModelOutput } = this.resolveToolResultForTokenCounting(part, invocation.result);
18573
18602
  if (resultForCounting !== void 0) {
18574
18603
  const contentTokens = this.countMultimodalToolResultContent(part, resultForCounting);
@@ -18582,47 +18611,45 @@ var TokenCounter = class TokenCounter {
18582
18611
  return {
18583
18612
  tokens,
18584
18613
  overheadDelta,
18585
- toolResultDelta
18614
+ extraMessageDelta
18586
18615
  };
18587
18616
  }
18588
- if (invocation.state === "output-denied") {
18589
- toolResultDelta++;
18617
+ if (state === "output-denied") {
18618
+ extraMessageDelta++;
18590
18619
  const reason = invocation.approval?.reason ?? "Tool call was not approved by the user";
18591
18620
  tokens += this.readOrPersistPartEstimate(part, "tool-result-denied", reason);
18592
18621
  return {
18593
18622
  tokens,
18594
18623
  overheadDelta,
18595
- toolResultDelta
18624
+ extraMessageDelta
18596
18625
  };
18597
18626
  }
18598
- if (invocation.state === "output-error") {
18599
- toolResultDelta++;
18600
- const errorText = invocation.errorText;
18601
- const errorMessage = typeof errorText === "string" ? errorText : "Tool execution failed";
18627
+ if (state === "output-error") {
18628
+ extraMessageDelta++;
18629
+ const errorMessage = typeof invocation.errorText === "string" ? invocation.errorText : "Tool execution failed";
18602
18630
  tokens += this.readOrPersistPartEstimate(part, "tool-result-error", errorMessage);
18603
18631
  return {
18604
18632
  tokens,
18605
18633
  overheadDelta,
18606
- toolResultDelta
18634
+ extraMessageDelta
18607
18635
  };
18608
18636
  }
18609
- throw new Error(`Unhandled tool-invocation state '${part.toolInvocation?.state}' in token counting for part type '${part.type}'`);
18610
18637
  }
18611
18638
  if (typeof part.type === "string" && part.type.startsWith("data-")) return {
18612
18639
  tokens: 0,
18613
18640
  overheadDelta,
18614
- toolResultDelta
18641
+ extraMessageDelta
18615
18642
  };
18616
18643
  if (part.type === "reasoning") return {
18617
18644
  tokens: 0,
18618
18645
  overheadDelta,
18619
- toolResultDelta
18646
+ extraMessageDelta
18620
18647
  };
18621
18648
  const serialized = serializePartForTokenCounting(part);
18622
18649
  return {
18623
18650
  tokens: this.readOrPersistPartEstimate(part, `part-${part.type}`, serialized),
18624
18651
  overheadDelta,
18625
- toolResultDelta
18652
+ extraMessageDelta
18626
18653
  };
18627
18654
  }
18628
18655
  /**
@@ -18631,7 +18658,7 @@ var TokenCounter = class TokenCounter {
18631
18658
  countMessage(message) {
18632
18659
  let payloadTokens = this.countString(message.role);
18633
18660
  let overhead = TokenCounter.TOKENS_PER_MESSAGE;
18634
- let toolResultCount = 0;
18661
+ let extraMessageCount = 0;
18635
18662
  if (typeof message.content === "string") payloadTokens += this.readOrPersistMessageEstimate(message, "message-content", message.content);
18636
18663
  else if (message.content && typeof message.content === "object") {
18637
18664
  if (message.content.content && !Array.isArray(message.content.parts)) payloadTokens += this.readOrPersistMessageEstimate(message, "content-content", message.content.content);
@@ -18644,16 +18671,16 @@ var TokenCounter = class TokenCounter {
18644
18671
  const result = this.countNonAttachmentPart(part);
18645
18672
  payloadTokens += result.tokens;
18646
18673
  overhead += result.overheadDelta;
18647
- toolResultCount += result.toolResultDelta;
18674
+ extraMessageCount += result.extraMessageDelta;
18648
18675
  }
18649
18676
  }
18650
- if (toolResultCount > 0) overhead += toolResultCount * TokenCounter.TOKENS_PER_MESSAGE;
18677
+ if (extraMessageCount > 0) overhead += extraMessageCount * TokenCounter.TOKENS_PER_MESSAGE;
18651
18678
  return Math.round(payloadTokens + overhead);
18652
18679
  }
18653
18680
  async countMessageAsync(message) {
18654
18681
  let payloadTokens = this.countString(message.role);
18655
18682
  let overhead = TokenCounter.TOKENS_PER_MESSAGE;
18656
- let toolResultCount = 0;
18683
+ let extraMessageCount = 0;
18657
18684
  if (typeof message.content === "string") payloadTokens += this.readOrPersistMessageEstimate(message, "message-content", message.content);
18658
18685
  else if (message.content && typeof message.content === "object") {
18659
18686
  if (message.content.content && !Array.isArray(message.content.parts)) payloadTokens += this.readOrPersistMessageEstimate(message, "content-content", message.content.content);
@@ -18666,10 +18693,10 @@ var TokenCounter = class TokenCounter {
18666
18693
  const result = this.countNonAttachmentPart(part);
18667
18694
  payloadTokens += result.tokens;
18668
18695
  overhead += result.overheadDelta;
18669
- toolResultCount += result.toolResultDelta;
18696
+ extraMessageCount += result.extraMessageDelta;
18670
18697
  }
18671
18698
  }
18672
- if (toolResultCount > 0) overhead += toolResultCount * TokenCounter.TOKENS_PER_MESSAGE;
18699
+ if (extraMessageCount > 0) overhead += extraMessageCount * TokenCounter.TOKENS_PER_MESSAGE;
18673
18700
  return Math.round(payloadTokens + overhead);
18674
18701
  }
18675
18702
  /**
@@ -18980,9 +19007,10 @@ async function listThreadsForResource({ memory, resourceId, currentThreadId, pag
18980
19007
  hasMore
18981
19008
  };
18982
19009
  }
19010
+ const SEARCH_NOT_CONFIGURED_MESSAGE = "Search is not configured. Enable it with `retrieval: { vector: true }` and configure a vector store and embedder on your Memory instance.";
18983
19011
  async function searchMessagesForResource({ memory, resourceId, currentThreadId, query, topK = 10, maxTokens = DEFAULT_MAX_RESULT_TOKENS, before, after, threadScope }) {
18984
19012
  if (!memory.searchMessages) return {
18985
- results: "Search is not configured. Enable it with `retrieval: { vector: true }` and configure a vector store and embedder on your Memory instance.",
19013
+ results: SEARCH_NOT_CONFIGURED_MESSAGE,
18986
19014
  count: 0
18987
19015
  };
18988
19016
  const MAX_TOPK = 20;
@@ -19528,9 +19556,16 @@ async function recallThreadFromStart({ memory, threadId, resourceId, page = 1, l
19528
19556
  }
19529
19557
  const recallTool = (_memoryConfig, options) => {
19530
19558
  const isResourceScope = (options?.retrievalScope ?? "thread") === "resource";
19559
+ const searchEnabled = options?.searchEnabled ?? true;
19560
+ const description = isResourceScope ? `Browse conversation history. Use mode="threads" to list all threads for the current user. Use mode="messages" (default) to browse messages in the current thread or pass threadId to browse another thread in the active resource. When mode="messages" has no cursor or threadId, it defaults to the current thread and says so at the top of the result. If you pass only a cursor, it must belong to the current thread.${searchEnabled ? " Use mode=\"search\" to find messages by content across all threads." : ""}` : `Browse conversation history in the current thread. Use mode="messages" (default) to page through messages near a cursor.${searchEnabled ? " Use mode=\"search\" to find messages by content in this thread." : ""} Use mode="threads" to get the current thread's ID and title.`;
19561
+ const modeEnum = searchEnabled ? [
19562
+ "messages",
19563
+ "threads",
19564
+ "search"
19565
+ ] : ["messages", "threads"];
19531
19566
  return (0, _mastra_core_tools.createTool)({
19532
19567
  id: "recall",
19533
- description: isResourceScope ? "Browse conversation history. Use mode=\"threads\" to list all threads for the current user. Use mode=\"messages\" (default) to browse messages in the current thread or pass threadId to browse another thread in the active resource. When mode=\"messages\" has no cursor or threadId, it defaults to the current thread and says so at the top of the result. If you pass only a cursor, it must belong to the current thread. Use mode=\"search\" to find messages by content across all threads." : "Browse conversation history in the current thread. Use mode=\"messages\" (default) to page through messages near a cursor. Use mode=\"search\" to find messages by content in this thread. Use mode=\"threads\" to get the current thread's ID and title.",
19568
+ description,
19534
19569
  inputSchema: {
19535
19570
  $schema: "http://json-schema.org/draft-07/schema#",
19536
19571
  type: "object",
@@ -19538,12 +19573,8 @@ const recallTool = (_memoryConfig, options) => {
19538
19573
  ...isResourceScope ? {
19539
19574
  mode: {
19540
19575
  type: "string",
19541
- enum: [
19542
- "messages",
19543
- "threads",
19544
- "search"
19545
- ],
19546
- description: "What to retrieve. \"messages\" (default) pages through message history. \"threads\" lists all threads for the current user. \"search\" finds messages by semantic similarity across all threads."
19576
+ enum: modeEnum,
19577
+ description: `What to retrieve. "messages" (default) pages through message history. "threads" lists all threads for the current user.${searchEnabled ? " \"search\" finds messages by semantic similarity across all threads." : ""}`
19547
19578
  },
19548
19579
  threadId: {
19549
19580
  type: "string",
@@ -19560,18 +19591,14 @@ const recallTool = (_memoryConfig, options) => {
19560
19591
  }
19561
19592
  } : { mode: {
19562
19593
  type: "string",
19563
- enum: [
19564
- "messages",
19565
- "threads",
19566
- "search"
19567
- ],
19568
- description: "What to retrieve. \"messages\" (default) pages through message history. \"threads\" returns info about the current thread. \"search\" finds messages by semantic similarity in this thread."
19594
+ enum: modeEnum,
19595
+ description: `What to retrieve. "messages" (default) pages through message history. "threads" returns info about the current thread.${searchEnabled ? " \"search\" finds messages by semantic similarity in this thread." : ""}`
19569
19596
  } },
19570
- query: {
19597
+ ...searchEnabled ? { query: {
19571
19598
  type: "string",
19572
19599
  minLength: 1,
19573
19600
  description: "Search query for mode=\"search\". Finds messages semantically similar to this text."
19574
- },
19601
+ } } : {},
19575
19602
  cursor: {
19576
19603
  type: "string",
19577
19604
  minLength: 1,
@@ -19632,6 +19659,10 @@ const recallTool = (_memoryConfig, options) => {
19632
19659
  if (!memory) throw new Error("Memory instance is required for recall");
19633
19660
  if (explicitThreadId === "current" && !currentThreadId) throw new Error("Could not resolve current thread.");
19634
19661
  if (mode === "search") {
19662
+ if (!searchEnabled) return {
19663
+ results: SEARCH_NOT_CONFIGURED_MESSAGE,
19664
+ count: 0
19665
+ };
19635
19666
  if (!query) throw new Error("query is required for mode=\"search\"");
19636
19667
  if (!resourceId) throw new Error("Resource ID is required for recall");
19637
19668
  return searchMessagesForResource({
@@ -19768,6 +19799,7 @@ function deepMergeWorkingMemory(existing, update) {
19768
19799
  for (const key of Object.keys(update)) {
19769
19800
  const updateValue = update[key];
19770
19801
  const existingValue = result[key];
19802
+ if (updateValue === void 0) continue;
19771
19803
  if (updateValue === null) delete result[key];
19772
19804
  else if (Array.isArray(updateValue)) result[key] = updateValue;
19773
19805
  else if (typeof updateValue === "object" && updateValue !== null && typeof existingValue === "object" && existingValue !== null && !Array.isArray(existingValue)) result[key] = deepMergeWorkingMemory(existingValue, updateValue);
@@ -19820,7 +19852,7 @@ const updateWorkingMemoryTool = (memoryConfig) => {
19820
19852
  version: 1,
19821
19853
  vendor: "mastra",
19822
19854
  validate: (value) => {
19823
- const memoryValue = !!value && typeof value === "object" && !Array.isArray(value) && "memory" in value ? value.memory : stripNullsFromOptional(value, jsonSchema);
19855
+ const memoryValue = stripNullsFromOptional(!!value && typeof value === "object" && !Array.isArray(value) && "memory" in value ? value.memory : value, jsonSchema);
19824
19856
  const result = validateMemory(memoryValue);
19825
19857
  return result instanceof Promise ? result.then(toWrappedResult) : toWrappedResult(result);
19826
19858
  },
@@ -19837,6 +19869,7 @@ const updateWorkingMemoryTool = (memoryConfig) => {
19837
19869
  id: "update-working-memory",
19838
19870
  description: schema ? useStateSignals ? `${stateSignalsPreamble} Data is merged with existing memory — only include fields you want to add or update.` : `Update the working memory with new information. Data is merged with existing memory - only include fields you want to add or update. To preserve existing data, omit the field entirely. Arrays are replaced entirely when provided, so pass the complete array or omit it to keep the existing values.` : useStateSignals ? `${stateSignalsPreamble} Pass the full updated Markdown blob as a string in the memory field.` : `Update the working memory with new information. Any data not included will be overwritten. Always pass data as string to the memory field. Never pass an object.`,
19839
19871
  inputSchema,
19872
+ ...usesMergeSemantics ? { strict: false } : {},
19840
19873
  execute: async (inputData, context) => {
19841
19874
  const workingMemoryInput = inputData;
19842
19875
  const threadId = context?.agent?.threadId;
@@ -20593,6 +20626,24 @@ function getUnobservedPartsPreservingToolCallPairs(message) {
20593
20626
  return preservedCalls.length > 0 ? [...preservedCalls, ...unobservedParts] : unobservedParts;
20594
20627
  }
20595
20628
  /**
20629
+ * Get the messages Observational Memory is allowed to work with.
20630
+ *
20631
+ * Messages supplied through the `context` option are per-run ephemeral input. Core's
20632
+ * persistence contract already treats them as never-persist: `MessageStateManager` routes
20633
+ * them into `userContextMessages`, and `drainUnsavedMessages` only drains input/response.
20634
+ *
20635
+ * OM builds its windows from `get.all.db()`, which includes context messages, and then
20636
+ * seals and persists candidates directly — turning ephemeral context into durable user
20637
+ * messages. Excluding them here keeps OM's window, sealing, persistence and token
20638
+ * accounting consistent with that contract.
20639
+ */
20640
+ function getObservableMessages(messageList) {
20641
+ const allMessages = messageList.get.all.db();
20642
+ const contextMessageIds = messageList.makeMessageSourceChecker().context;
20643
+ if (contextMessageIds.size === 0) return allMessages;
20644
+ return allMessages.filter((message) => !contextMessageIds.has(message.id));
20645
+ }
20646
+ /**
20596
20647
  * Safely extract buffered observation chunks from a record.
20597
20648
  * Handles both array and JSON-string formats, returning empty array if malformed.
20598
20649
  */
@@ -20605,7 +20656,7 @@ function getUnobservedPartsPreservingToolCallPairs(message) {
20605
20656
  */
20606
20657
  function filterObservedMessages(opts) {
20607
20658
  const { messageList, record } = opts;
20608
- const allMessages = messageList.get.all.db();
20659
+ const allMessages = getObservableMessages(messageList);
20609
20660
  const useMarkerBoundaryPruning = opts.useMarkerBoundaryPruning ?? true;
20610
20661
  const preserveMessageIds = opts.preserveMessageIds ?? /* @__PURE__ */ new Set();
20611
20662
  const observedIds = new Set(Array.isArray(record?.observedMessageIds) ? record.observedMessageIds : []);
@@ -21158,7 +21209,7 @@ var ObservationStrategy = class ObservationStrategy {
21158
21209
  */
21159
21210
  async persistMarkerToMessage(marker, messageList, threadId, resourceId) {
21160
21211
  if (!messageList) return false;
21161
- const allMsgs = messageList.get.all.db();
21212
+ const allMsgs = getObservableMessages(messageList);
21162
21213
  for (let i = allMsgs.length - 1; i >= 0; i--) {
21163
21214
  const msg = allMsgs[i];
21164
21215
  if (msg?.role === "assistant" && msg.content?.parts && Array.isArray(msg.content.parts)) {
@@ -22037,7 +22088,7 @@ var ObservationStep = class {
22037
22088
  let didThresholdCleanup = false;
22038
22089
  let observerExchange;
22039
22090
  if (this.stepNumber === 0) {
22040
- const step0Messages = messageList.get.all.db();
22091
+ const step0Messages = getObservableMessages(messageList);
22041
22092
  const activation = await om.activate({
22042
22093
  threadId,
22043
22094
  resourceId,
@@ -22069,7 +22120,7 @@ var ObservationStep = class {
22069
22120
  currentModel: this.turn.actorModelContext,
22070
22121
  requestContext: this.turn.requestContext,
22071
22122
  observabilityContext: this.turn.observabilityContext,
22072
- lastActivityAt: getLastActivityFromMessages(messageList.get.all.db()),
22123
+ lastActivityAt: getLastActivityFromMessages(getObservableMessages(messageList)),
22073
22124
  reflectionHooks: om.composeHooks(void 0, {
22074
22125
  threadId,
22075
22126
  resourceId,
@@ -22079,7 +22130,7 @@ var ObservationStep = class {
22079
22130
  await this.turn.refreshRecord();
22080
22131
  if (this.turn.record.generationCount > preReflectGeneration) reflected = true;
22081
22132
  }
22082
- const allMsgsForToolCheck = messageList.get.all.db();
22133
+ const allMsgsForToolCheck = getObservableMessages(messageList);
22083
22134
  const lastMessage = allMsgsForToolCheck[allMsgsForToolCheck.length - 1];
22084
22135
  const pendingStepMessages = [...messageList.get.input.db(), ...messageList.get.response.db()];
22085
22136
  const latestStepParts = [...getLatestStepParts(lastMessage?.content?.parts ?? []), ...pendingStepMessages.flatMap((msg) => getLatestStepParts(msg.content?.parts ?? []))];
@@ -22088,10 +22139,10 @@ var ObservationStep = class {
22088
22139
  let statusSnapshot = await om.getStatus({
22089
22140
  threadId,
22090
22141
  resourceId,
22091
- messages: messageList.get.all.db()
22142
+ messages: getObservableMessages(messageList)
22092
22143
  });
22093
22144
  if (statusSnapshot.shouldBuffer && !hasIncompleteToolCalls) {
22094
- const allMessages = messageList.get.all.db();
22145
+ const allMessages = getObservableMessages(messageList);
22095
22146
  const unobservedMessages = om.getUnobservedMessages(allMessages, statusSnapshot.record);
22096
22147
  const candidates = om.getUnobservedMessages(unobservedMessages, statusSnapshot.record, { excludeBuffered: true });
22097
22148
  if (candidates.length > 0) {
@@ -22183,7 +22234,7 @@ var ObservationStep = class {
22183
22234
  statusSnapshot = await om.getStatus({
22184
22235
  threadId,
22185
22236
  resourceId,
22186
- messages: messageList.get.all.db()
22237
+ messages: getObservableMessages(messageList)
22187
22238
  });
22188
22239
  }
22189
22240
  const otherThreadsContext = await this.turn.refreshOtherThreadsContext();
@@ -22232,7 +22283,7 @@ var ObservationStep = class {
22232
22283
  const { threadId, resourceId, messageList } = this.turn;
22233
22284
  const om = this.turn.om;
22234
22285
  await om.waitForBuffering(threadId, resourceId);
22235
- const observableMessages = this.seededResponseMessage ? messageList.get.all.db().filter((msg) => msg.id !== this.turn.responseMessageId) : messageList.get.all.db();
22286
+ const observableMessages = this.seededResponseMessage ? getObservableMessages(messageList).filter((msg) => msg.id !== this.turn.responseMessageId) : getObservableMessages(messageList);
22236
22287
  const freshStatus = await om.getStatus({
22237
22288
  threadId,
22238
22289
  resourceId,
@@ -22262,7 +22313,7 @@ var ObservationStep = class {
22262
22313
  currentModel: this.turn.actorModelContext,
22263
22314
  requestContext: this.turn.requestContext,
22264
22315
  observabilityContext: this.turn.observabilityContext,
22265
- lastActivityAt: getLastActivityFromMessages(messageList.get.all.db()),
22316
+ lastActivityAt: getLastActivityFromMessages(getObservableMessages(messageList)),
22266
22317
  reflectionHooks: om.composeHooks(void 0, {
22267
22318
  threadId,
22268
22319
  resourceId,
@@ -22288,7 +22339,7 @@ var ObservationStep = class {
22288
22339
  });
22289
22340
  if (obsResult.observed) {
22290
22341
  const observedMessageIds = new Set(obsResult.record.observedMessageIds ?? []);
22291
- const liveMessages = messageList.get.all.db();
22342
+ const liveMessages = getObservableMessages(messageList);
22292
22343
  let latestObservedIndex = -1;
22293
22344
  for (let i = liveMessages.length - 1; i >= 0; i--) {
22294
22345
  const message = liveMessages[i];
@@ -22463,7 +22514,7 @@ var ObservationTurn = class {
22463
22514
  const asyncObservationEnabled = this.om.buffering.isAsyncObservationEnabled();
22464
22515
  const bufferOnIdle = this.om.getObservationConfig().bufferOnIdle;
22465
22516
  if (asyncObservationEnabled && bufferOnIdle) {
22466
- const allMessages = this.messageList.get.all.db();
22517
+ const allMessages = getObservableMessages(this.messageList);
22467
22518
  const record = this._record;
22468
22519
  const unobservedMessages = this.om.getUnobservedMessages(allMessages, record);
22469
22520
  if (unobservedMessages.length > 0) this.om.buffer({
@@ -22833,7 +22884,7 @@ function getCurrentModel$1(model) {
22833
22884
  return formatModelContext$1(model?.provider, model?.modelId);
22834
22885
  }
22835
22886
  function getLastModelFromMessageList(messageList) {
22836
- const messages = messageList?.get.all.db();
22887
+ const messages = messageList ? getObservableMessages(messageList) : void 0;
22837
22888
  if (!messages) return void 0;
22838
22889
  for (let i = messages.length - 1; i >= 0; i--) {
22839
22890
  const message = messages[i];
@@ -24417,7 +24468,7 @@ var ObservationalMemory = class ObservationalMemory {
24417
24468
  */
24418
24469
  async persistMarkerToMessage(marker, messageList, threadId, resourceId) {
24419
24470
  if (!messageList) return;
24420
- const allMsgs = messageList.get.all.db();
24471
+ const allMsgs = getObservableMessages(messageList);
24421
24472
  for (let i = allMsgs.length - 1; i >= 0; i--) {
24422
24473
  const msg = allMsgs[i];
24423
24474
  if (msg?.role === "assistant" && msg.content?.parts && Array.isArray(msg.content.parts)) {
@@ -25095,7 +25146,7 @@ ${formattedMessages}
25095
25146
  async cleanupMessages(opts) {
25096
25147
  const { threadId, resourceId, observedMessageIds, retentionFloor, preserveMessageIds } = opts;
25097
25148
  const messageList = this.isMessageList(opts.messages) ? opts.messages : void 0;
25098
- const allMsgs = messageList ? messageList.get.all.db() : opts.messages;
25149
+ const allMsgs = messageList ? getObservableMessages(messageList) : opts.messages;
25099
25150
  let markerIdx = -1;
25100
25151
  let markerMsg = null;
25101
25152
  for (let i = allMsgs.length - 1; i >= 0; i--) {
@@ -26211,7 +26262,7 @@ function isTemporalGapMarkerForMessage(message, targetMessageId) {
26211
26262
  async function insertTemporalGapMarkers({ messageList, sendSignal }) {
26212
26263
  const latestInputMessage = messageList.get.input.db().filter((message) => Boolean(message)).at(-1);
26213
26264
  if (!latestInputMessage || isTemporalGapMarker(latestInputMessage)) return;
26214
- const allMessages = messageList.get.all.db().filter((message) => Boolean(message));
26265
+ const allMessages = getObservableMessages(messageList).filter((message) => Boolean(message));
26215
26266
  const latestInputIndex = allMessages.findIndex((message) => message.id === latestInputMessage.id);
26216
26267
  if (latestInputIndex <= 0) return;
26217
26268
  if (allMessages.some((message) => isTemporalGapMarkerForMessage(message, latestInputMessage.id))) return;
@@ -26435,7 +26486,7 @@ var ObservationalMemoryProcessor = class {
26435
26486
  threadId,
26436
26487
  resourceId
26437
26488
  });
26438
- const allDbMsgs = messageList.get.all.db();
26489
+ const allDbMsgs = getObservableMessages(messageList);
26439
26490
  const tokenCounter = this.engine.getTokenCounter();
26440
26491
  const contextTokens = await tokenCounter.countMessagesAsync(allDbMsgs);
26441
26492
  const otherThreadsContext = this.turn.context.otherThreadsContext;
@@ -26805,6 +26856,18 @@ var Memory = class extends _mastra_core_memory.MastraMemory {
26805
26856
  _omEngine;
26806
26857
  _omEngineInstance;
26807
26858
  _mastraInstance;
26859
+ /**
26860
+ * Every vector cleanup that deleteThread or deleteMessages started in the background.
26861
+ * Callers do not wait for the cleanup, so this handle is the only join point.
26862
+ */
26863
+ pendingVectorCleanup = Promise.resolve();
26864
+ /**
26865
+ * Adds a background vector cleanup to the join handle.
26866
+ * The handle keeps the earlier cleanups, so it settles only after all of them end.
26867
+ */
26868
+ trackVectorCleanup(cleanup) {
26869
+ this.pendingVectorCleanup = Promise.allSettled([this.pendingVectorCleanup, cleanup]).then(() => void 0);
26870
+ }
26808
26871
  /** The shared ObservationalMemory engine. Lazily created on first access. */
26809
26872
  get omEngine() {
26810
26873
  if (!this._omEngine) this._omEngine = this._initOMEngine().then((engine) => {
@@ -27069,26 +27132,40 @@ var Memory = class extends _mastra_core_memory.MastraMemory {
27069
27132
  const thread = await memoryStore.getThreadById({ threadId });
27070
27133
  await memoryStore.deleteThread({ threadId });
27071
27134
  if (thread?.resourceId && memoryStore.supportsObservationalMemory) await memoryStore.clearObservationalMemory(threadId, thread.resourceId);
27072
- if (this.vector) this.deleteThreadVectors(threadId);
27135
+ if (this.vector) this.trackVectorCleanup(this.deleteThreadVectors(threadId));
27136
+ }
27137
+ /**
27138
+ * Prefix shared by every message index. The index for the default embedding
27139
+ * dimension is named with the bare prefix; other dimensions add a suffix.
27140
+ */
27141
+ get messageIndexPrefix() {
27142
+ return this.getEmbeddingIndexName();
27073
27143
  }
27074
27144
  /**
27075
- * Lists all vector indexes that match the memory messages prefix.
27076
- * Handles separator differences across vector store backends (e.g. '_' vs '-').
27145
+ * Prefix shared by every observation index. Each observation index adds a dimension suffix.
27077
27146
  */
27078
- async getMemoryVectorIndexes() {
27147
+ get observationIndexPrefix() {
27148
+ return `memory${this.vector?.indexSeparator ?? "_"}observations`;
27149
+ }
27150
+ /**
27151
+ * Lists the vector indexes whose name starts with one of the given prefixes.
27152
+ * Index names can carry a dimension suffix, so discovery matches on the prefix.
27153
+ */
27154
+ async getMemoryVectorIndexes(prefixes) {
27079
27155
  if (!this.vector) return [];
27080
- const prefix = `memory${this.vector.indexSeparator ?? "_"}messages`;
27081
- return (await this.vector.listIndexes()).filter((name) => name.startsWith(prefix));
27156
+ return (await this.vector.listIndexes()).filter((name) => prefixes.some((prefix) => name.startsWith(prefix)));
27082
27157
  }
27083
27158
  /**
27084
27159
  * Deletes all vector embeddings associated with a thread.
27085
27160
  * This is called internally by deleteThread to clean up orphaned vectors.
27161
+ * Both message and observation vectors are removed, so no text of the deleted
27162
+ * thread stays reachable through resource-scoped retrieval.
27086
27163
  *
27087
27164
  * @param threadId - The ID of the thread whose vectors should be deleted
27088
27165
  */
27089
27166
  async deleteThreadVectors(threadId) {
27090
27167
  try {
27091
- const memoryIndexes = await this.getMemoryVectorIndexes();
27168
+ const memoryIndexes = await this.getMemoryVectorIndexes([this.messageIndexPrefix, this.observationIndexPrefix]);
27092
27169
  await Promise.all(memoryIndexes.map(async (indexName) => {
27093
27170
  try {
27094
27171
  await this.vector.deleteVectors({
@@ -27096,14 +27173,14 @@ var Memory = class extends _mastra_core_memory.MastraMemory {
27096
27173
  filter: { thread_id: threadId }
27097
27174
  });
27098
27175
  } catch {
27099
- this.logger.debug("Failed to delete vectors for thread, skipping", {
27176
+ this.logger.warn("Failed to delete vectors of the deleted thread from index", {
27100
27177
  threadId,
27101
27178
  indexName
27102
27179
  });
27103
27180
  }
27104
27181
  }));
27105
27182
  } catch {
27106
- this.logger.debug("Failed to clean up vectors for thread", { threadId });
27183
+ this.logger.warn("Failed to clean up vectors of the deleted thread", { threadId });
27107
27184
  }
27108
27185
  }
27109
27186
  async updateWorkingMemory({ threadId, resourceId, workingMemory, memoryConfig, observabilityContext }) {
@@ -27776,7 +27853,7 @@ Notes:
27776
27853
  getObservationEmbeddingIndexName(dimensions) {
27777
27854
  const usedDimensions = dimensions ?? 384;
27778
27855
  const separator = this.vector?.indexSeparator ?? "_";
27779
- return `memory${separator}observations${separator}${usedDimensions}`;
27856
+ return `${this.observationIndexPrefix}${separator}${usedDimensions}`;
27780
27857
  }
27781
27858
  async createObservationEmbeddingIndex(dimensions) {
27782
27859
  const usedDimensions = dimensions ?? 384;
@@ -28022,7 +28099,10 @@ Notes:
28022
28099
  tools[name] = tool;
28023
28100
  }
28024
28101
  const omConfig = normalizeObservationalMemoryConfig(mergedConfig.observationalMemory);
28025
- if (omConfig?.retrieval) tools.recall = recallTool(mergedConfig, { retrievalScope: typeof omConfig.retrieval === "object" ? omConfig.retrieval.scope ?? "resource" : "resource" });
28102
+ if (omConfig?.retrieval) tools.recall = recallTool(mergedConfig, {
28103
+ retrievalScope: typeof omConfig.retrieval === "object" ? omConfig.retrieval.scope ?? "resource" : "resource",
28104
+ searchEnabled: this.hasRetrievalSearch(omConfig.retrieval)
28105
+ });
28026
28106
  return tools;
28027
28107
  }
28028
28108
  /**
@@ -28078,7 +28158,7 @@ Notes:
28078
28158
  }));
28079
28159
  const messageIdsNeedingDeletion = /* @__PURE__ */ new Set([...messageIdsWithClearedContent, ...messageIdsWithNewEmbeddings]);
28080
28160
  if (messageIdsNeedingDeletion.size > 0) try {
28081
- const memoryIndexes = await this.getMemoryVectorIndexes();
28161
+ const memoryIndexes = await this.getMemoryVectorIndexes([this.messageIndexPrefix]);
28082
28162
  const idsToDelete = [...messageIdsNeedingDeletion];
28083
28163
  await Promise.all(memoryIndexes.map(async (indexName) => {
28084
28164
  for (let i = 0; i < idsToDelete.length; i += VECTOR_DELETE_BATCH_SIZE) {
@@ -28137,7 +28217,7 @@ Notes:
28137
28217
  const span = this.createMemorySpan("delete", observabilityContext, void 0, { messageCount: messageIds.length });
28138
28218
  try {
28139
28219
  await (await this.getMemoryStore()).deleteMessages(messageIds);
28140
- if (this.vector) this.deleteMessageVectors(messageIds);
28220
+ if (this.vector) this.trackVectorCleanup(this.deleteMessageVectors(messageIds));
28141
28221
  span?.end({
28142
28222
  output: { success: true },
28143
28223
  attributes: { messageCount: messageIds.length }
@@ -28153,12 +28233,14 @@ Notes:
28153
28233
  /**
28154
28234
  * Deletes vector embeddings for specific messages.
28155
28235
  * This is called internally by deleteMessages to clean up orphaned vectors.
28236
+ * Only the message indexes are touched, because observation vectors can hold
28237
+ * text of other messages of the thread.
28156
28238
  *
28157
28239
  * @param messageIds - The IDs of the messages whose vectors should be deleted
28158
28240
  */
28159
28241
  async deleteMessageVectors(messageIds) {
28160
28242
  try {
28161
- const memoryIndexes = await this.getMemoryVectorIndexes();
28243
+ const memoryIndexes = await this.getMemoryVectorIndexes([this.messageIndexPrefix]);
28162
28244
  await Promise.all(memoryIndexes.map(async (indexName) => {
28163
28245
  for (let i = 0; i < messageIds.length; i += VECTOR_DELETE_BATCH_SIZE) {
28164
28246
  const batch = messageIds.slice(i, i + VECTOR_DELETE_BATCH_SIZE);
@@ -28785,4 +28867,4 @@ Object.defineProperty(exports, "wrapInObservationGroup", {
28785
28867
  }
28786
28868
  });
28787
28869
 
28788
- //# sourceMappingURL=src-x5iu_K3X.cjs.map
28870
+ //# sourceMappingURL=src-CTQrRb5X.cjs.map