@mastra/memory 1.10.1-alpha.3 → 1.11.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.
Files changed (48) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/dist/{chunk-SKRONGCV.js → chunk-D4D6ZFBQ.js} +355 -105
  3. package/dist/chunk-D4D6ZFBQ.js.map +1 -0
  4. package/dist/{chunk-DTAZSLVU.cjs → chunk-VINRPDYQ.cjs} +355 -105
  5. package/dist/chunk-VINRPDYQ.cjs.map +1 -0
  6. package/dist/docs/SKILL.md +1 -1
  7. package/dist/docs/assets/SOURCE_MAP.json +47 -47
  8. package/dist/docs/references/docs-memory-observational-memory.md +49 -4
  9. package/dist/docs/references/reference-memory-observational-memory.md +32 -6
  10. package/dist/index.cjs +577 -36
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.ts +46 -0
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +570 -29
  15. package/dist/index.js.map +1 -1
  16. package/dist/{observational-memory-XIZTJN3S.cjs → observational-memory-FATH657E.cjs} +26 -26
  17. package/dist/{observational-memory-XIZTJN3S.cjs.map → observational-memory-FATH657E.cjs.map} +1 -1
  18. package/dist/{observational-memory-UGDENJPE.js → observational-memory-SN7GKMHZ.js} +3 -3
  19. package/dist/{observational-memory-UGDENJPE.js.map → observational-memory-SN7GKMHZ.js.map} +1 -1
  20. package/dist/processors/index.cjs +24 -24
  21. package/dist/processors/index.js +1 -1
  22. package/dist/processors/observational-memory/observation-strategies/async-buffer.d.ts.map +1 -1
  23. package/dist/processors/observational-memory/observation-strategies/base.d.ts +9 -0
  24. package/dist/processors/observational-memory/observation-strategies/base.d.ts.map +1 -1
  25. package/dist/processors/observational-memory/observation-strategies/resource-scoped.d.ts.map +1 -1
  26. package/dist/processors/observational-memory/observation-strategies/sync.d.ts.map +1 -1
  27. package/dist/processors/observational-memory/observation-strategies/types.d.ts +2 -0
  28. package/dist/processors/observational-memory/observation-strategies/types.d.ts.map +1 -1
  29. package/dist/processors/observational-memory/observation-turn/step.d.ts.map +1 -1
  30. package/dist/processors/observational-memory/observation-turn/turn.d.ts +4 -0
  31. package/dist/processors/observational-memory/observation-turn/turn.d.ts.map +1 -1
  32. package/dist/processors/observational-memory/observational-memory.d.ts +27 -2
  33. package/dist/processors/observational-memory/observational-memory.d.ts.map +1 -1
  34. package/dist/processors/observational-memory/observer-runner.d.ts +14 -3
  35. package/dist/processors/observational-memory/observer-runner.d.ts.map +1 -1
  36. package/dist/processors/observational-memory/processor.d.ts +8 -12
  37. package/dist/processors/observational-memory/processor.d.ts.map +1 -1
  38. package/dist/processors/observational-memory/reflector-runner.d.ts +11 -1
  39. package/dist/processors/observational-memory/reflector-runner.d.ts.map +1 -1
  40. package/dist/processors/observational-memory/tracing.d.ts +17 -0
  41. package/dist/processors/observational-memory/tracing.d.ts.map +1 -0
  42. package/dist/processors/observational-memory/types.d.ts +19 -1
  43. package/dist/processors/observational-memory/types.d.ts.map +1 -1
  44. package/dist/tools/om-tools.d.ts +96 -4
  45. package/dist/tools/om-tools.d.ts.map +1 -1
  46. package/package.json +7 -7
  47. package/dist/chunk-DTAZSLVU.cjs.map +0 -1
  48. package/dist/chunk-SKRONGCV.js.map +0 -1
package/dist/index.cjs CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  var chunkCK4U3AYR_cjs = require('./chunk-CK4U3AYR.cjs');
4
4
  var chunkIDRQZVB4_cjs = require('./chunk-IDRQZVB4.cjs');
5
- var chunkDTAZSLVU_cjs = require('./chunk-DTAZSLVU.cjs');
5
+ var chunkVINRPDYQ_cjs = require('./chunk-VINRPDYQ.cjs');
6
6
  var v3 = require('zod/v3');
7
7
  var zod = require('zod');
8
8
  var z4 = require('zod/v4');
@@ -15473,7 +15473,7 @@ function parseRangeFormat(cursor) {
15473
15473
  }
15474
15474
  return null;
15475
15475
  }
15476
- async function resolveCursorMessage(memory, cursor) {
15476
+ async function resolveCursorMessage(memory, cursor, access) {
15477
15477
  const normalized = cursor.trim();
15478
15478
  if (!normalized) {
15479
15479
  throw new Error("Cursor is required");
@@ -15491,8 +15491,171 @@ async function resolveCursorMessage(memory, cursor) {
15491
15491
  if (!message) {
15492
15492
  throw new Error(`Could not resolve cursor message: ${cursor}`);
15493
15493
  }
15494
+ if (access?.resourceId && message.resourceId !== access.resourceId) {
15495
+ throw new Error(`Could not resolve cursor message: ${cursor}`);
15496
+ }
15497
+ if (access?.threadScope && message.threadId !== access.threadScope) {
15498
+ throw new Error(`Could not resolve cursor message: ${cursor}`);
15499
+ }
15494
15500
  return message;
15495
15501
  }
15502
+ async function listThreadsForResource({
15503
+ memory,
15504
+ resourceId,
15505
+ currentThreadId,
15506
+ page = 0,
15507
+ limit = 20,
15508
+ before,
15509
+ after
15510
+ }) {
15511
+ if (!resourceId) {
15512
+ throw new Error("Resource ID is required to list threads");
15513
+ }
15514
+ const MAX_LIMIT = 50;
15515
+ const normalizedLimit = Math.min(Math.max(limit, 1), MAX_LIMIT);
15516
+ const hasDateFilter = !!(before || after);
15517
+ const beforeDate = before ? new Date(before) : null;
15518
+ const afterDate = after ? new Date(after) : null;
15519
+ const result = await memory.listThreads({
15520
+ filter: { resourceId },
15521
+ page: hasDateFilter ? 0 : page,
15522
+ perPage: hasDateFilter ? false : normalizedLimit,
15523
+ orderBy: { field: "updatedAt", direction: "DESC" }
15524
+ });
15525
+ let threads = result.threads;
15526
+ if (beforeDate) {
15527
+ threads = threads.filter((t) => t.createdAt < beforeDate);
15528
+ }
15529
+ if (afterDate) {
15530
+ threads = threads.filter((t) => t.createdAt > afterDate);
15531
+ }
15532
+ let hasMore;
15533
+ if (hasDateFilter) {
15534
+ const offset = page * normalizedLimit;
15535
+ hasMore = offset + normalizedLimit < threads.length;
15536
+ threads = threads.slice(offset, offset + normalizedLimit);
15537
+ } else {
15538
+ hasMore = result.hasMore;
15539
+ }
15540
+ if (threads.length === 0) {
15541
+ return {
15542
+ threads: "No threads found matching the criteria.",
15543
+ count: 0,
15544
+ page,
15545
+ hasMore: false
15546
+ };
15547
+ }
15548
+ const lines = [];
15549
+ for (const thread of threads) {
15550
+ const isCurrent = thread.id === currentThreadId;
15551
+ const title = thread.title || "(untitled)";
15552
+ const updated = formatTimestamp(thread.updatedAt);
15553
+ const created = formatTimestamp(thread.createdAt);
15554
+ const marker21 = isCurrent ? " \u2190 current" : "";
15555
+ lines.push(`- **${title}**${marker21}`);
15556
+ lines.push(` id: ${thread.id}`);
15557
+ lines.push(` updated: ${updated} | created: ${created}`);
15558
+ }
15559
+ return {
15560
+ threads: lines.join("\n"),
15561
+ count: threads.length,
15562
+ page,
15563
+ hasMore
15564
+ };
15565
+ }
15566
+ async function searchMessagesForResource({
15567
+ memory,
15568
+ resourceId,
15569
+ currentThreadId,
15570
+ query,
15571
+ topK = 10,
15572
+ maxTokens = DEFAULT_MAX_RESULT_TOKENS,
15573
+ before,
15574
+ after,
15575
+ threadScope
15576
+ }) {
15577
+ if (!memory.searchMessages) {
15578
+ return {
15579
+ results: "Search is not configured. Enable it with `retrieval: { vector: true }` and configure a vector store and embedder on your Memory instance.",
15580
+ count: 0
15581
+ };
15582
+ }
15583
+ const MAX_TOPK = 20;
15584
+ const clampedTopK = Math.min(Math.max(topK, 1), MAX_TOPK);
15585
+ const effectiveTopK = threadScope || before || after ? Math.max(clampedTopK * 3, clampedTopK + 10) : clampedTopK;
15586
+ const searchTopK = Math.min(MAX_TOPK, effectiveTopK);
15587
+ const beforeDate = before ? new Date(before) : void 0;
15588
+ const afterDate = after ? new Date(after) : void 0;
15589
+ const { results } = await memory.searchMessages({
15590
+ query,
15591
+ resourceId,
15592
+ topK: searchTopK,
15593
+ filter: {
15594
+ ...threadScope ? { threadId: threadScope } : {},
15595
+ ...afterDate ? { observedAfter: afterDate } : {},
15596
+ ...beforeDate ? { observedBefore: beforeDate } : {}
15597
+ }
15598
+ });
15599
+ if (results.length === 0) {
15600
+ return {
15601
+ results: "No matching messages found.",
15602
+ count: 0
15603
+ };
15604
+ }
15605
+ const threadIds = [...new Set(results.map((r) => r.threadId))];
15606
+ const threadMap = /* @__PURE__ */ new Map();
15607
+ if (memory.getThreadById) {
15608
+ await Promise.all(
15609
+ threadIds.map(async (id) => {
15610
+ const thread = await memory.getThreadById({ threadId: id });
15611
+ if (thread) threadMap.set(id, thread);
15612
+ })
15613
+ );
15614
+ }
15615
+ const filteredMatches = results.filter((match) => {
15616
+ if (threadScope && match.threadId !== threadScope) return false;
15617
+ if (beforeDate && match.observedAt && match.observedAt >= beforeDate) return false;
15618
+ if (afterDate && match.observedAt && match.observedAt <= afterDate) return false;
15619
+ return true;
15620
+ });
15621
+ if (filteredMatches.length === 0) {
15622
+ return { results: "No matching messages found.", count: 0 };
15623
+ }
15624
+ const limitedMatches = filteredMatches.slice(0, clampedTopK);
15625
+ const sections = limitedMatches.map((match) => {
15626
+ const thread = threadMap.get(match.threadId);
15627
+ const title = thread?.title || "(untitled)";
15628
+ const isCurrentThread = match.threadId === currentThreadId;
15629
+ const generationLabel = isCurrentThread ? "Current thread memory" : "Older memory from another thread";
15630
+ const generationDetail = isCurrentThread ? "This result came from the current thread." : "This result came from an older memory generation in another thread.";
15631
+ const threadLine = `- thread: ${match.threadId}${thread ? ` (${title})` : ""}`;
15632
+ const sourceLine = match.range ? `- source: raw messages from ID ${match.range.split(":")[0] ?? "(unknown)"} through ID ${match.range.split(":")[1] ?? "(unknown)"}` : "- source: raw message range unavailable";
15633
+ const updatedLine = thread ? `- thread updated: ${formatTimestamp(thread.updatedAt)}` : void 0;
15634
+ const groupLine = match.groupId ? `- observation group: ${match.groupId}` : void 0;
15635
+ const scoreLine = `- score: ${match.score.toFixed(2)}`;
15636
+ const body = (match.text || "").trim() || "_Observation text unavailable._";
15637
+ return [
15638
+ `### ${generationLabel}`,
15639
+ "",
15640
+ generationDetail,
15641
+ threadLine,
15642
+ sourceLine,
15643
+ updatedLine,
15644
+ groupLine,
15645
+ scoreLine,
15646
+ "",
15647
+ "```text",
15648
+ body,
15649
+ "```"
15650
+ ].filter(Boolean).join("\n");
15651
+ });
15652
+ const assembled = sections.join("\n\n");
15653
+ const { text: limited } = truncateByTokens(assembled, maxTokens);
15654
+ return {
15655
+ results: limited,
15656
+ count: limitedMatches.length
15657
+ };
15658
+ }
15496
15659
  var LOW_DETAIL_PART_TOKENS = 30;
15497
15660
  var AUTO_EXPAND_TEXT_TOKENS = 100;
15498
15661
  var AUTO_EXPAND_TOOL_TOKENS = 20;
@@ -15503,7 +15666,7 @@ function formatTimestamp(date) {
15503
15666
  }
15504
15667
  function truncateByTokens(text4, maxTokens, hint) {
15505
15668
  if (tokenx.estimateTokenCount(text4) <= maxTokens) return { text: text4, wasTruncated: false };
15506
- const truncated = chunkDTAZSLVU_cjs.truncateStringByTokens(text4, maxTokens);
15669
+ const truncated = chunkVINRPDYQ_cjs.truncateStringByTokens(text4, maxTokens);
15507
15670
  const suffix = hint ? ` [${hint} for more]` : "";
15508
15671
  return { text: truncated + suffix, wasTruncated: true };
15509
15672
  }
@@ -15536,11 +15699,11 @@ function formatMessageParts(msg, detail) {
15536
15699
  } else if (partType === "tool-invocation") {
15537
15700
  const inv = part.toolInvocation;
15538
15701
  if (inv.state === "result") {
15539
- const { value: resultValue } = chunkDTAZSLVU_cjs.resolveToolResultValue(
15702
+ const { value: resultValue } = chunkVINRPDYQ_cjs.resolveToolResultValue(
15540
15703
  part,
15541
15704
  inv.result
15542
15705
  );
15543
- const resultStr = chunkDTAZSLVU_cjs.formatToolResultForObserver(resultValue, { maxTokens: HIGH_DETAIL_TOOL_RESULT_TOKENS });
15706
+ const resultStr = chunkVINRPDYQ_cjs.formatToolResultForObserver(resultValue, { maxTokens: HIGH_DETAIL_TOOL_RESULT_TOKENS });
15544
15707
  const fullText = `[Tool Result: ${inv.toolName}]
15545
15708
  ${resultStr}`;
15546
15709
  parts.push(makePart(msg, i, "tool-result", fullText, detail));
@@ -15603,7 +15766,7 @@ function renderFormattedParts(parts, timestamps, options) {
15603
15766
  const text4 = buildRenderedText(parts, timestamps);
15604
15767
  let totalTokens = tokenx.estimateTokenCount(text4);
15605
15768
  if (totalTokens > options.maxTokens) {
15606
- const truncated = chunkDTAZSLVU_cjs.truncateStringByTokens(text4, options.maxTokens);
15769
+ const truncated = chunkVINRPDYQ_cjs.truncateStringByTokens(text4, options.maxTokens);
15607
15770
  return { text: truncated, truncated: true, tokenOffset: totalTokens - options.maxTokens };
15608
15771
  }
15609
15772
  const truncatedIndices = parts.map((p, i) => ({ part: p, index: i })).filter(({ part }) => part.text !== part.fullText).sort((a, b) => expandPriority(a.part) - expandPriority(b.part));
@@ -15636,14 +15799,16 @@ function renderFormattedParts(parts, timestamps, options) {
15636
15799
  if (expandedTokens <= options.maxTokens) {
15637
15800
  return { text: expanded, truncated: false, tokenOffset: 0 };
15638
15801
  }
15639
- const hardTruncated = chunkDTAZSLVU_cjs.truncateStringByTokens(expanded, options.maxTokens);
15802
+ const hardTruncated = chunkVINRPDYQ_cjs.truncateStringByTokens(expanded, options.maxTokens);
15640
15803
  return { text: hardTruncated, truncated: true, tokenOffset: expandedTokens - options.maxTokens };
15641
15804
  }
15642
15805
  async function recallPart({
15643
15806
  memory,
15644
15807
  threadId,
15808
+ resourceId,
15645
15809
  cursor,
15646
15810
  partIndex,
15811
+ threadScope,
15647
15812
  maxTokens = DEFAULT_MAX_RESULT_TOKENS
15648
15813
  }) {
15649
15814
  if (!memory || typeof memory.getMemoryStore !== "function") {
@@ -15652,13 +15817,10 @@ async function recallPart({
15652
15817
  if (!threadId) {
15653
15818
  throw new Error("Thread ID is required for recall");
15654
15819
  }
15655
- const resolved = await resolveCursorMessage(memory, cursor);
15820
+ const resolved = await resolveCursorMessage(memory, cursor, { resourceId, threadScope });
15656
15821
  if ("hint" in resolved) {
15657
15822
  throw new Error(resolved.hint);
15658
15823
  }
15659
- if (resolved.threadId !== threadId) {
15660
- throw new Error("The requested cursor does not belong to the current thread");
15661
- }
15662
15824
  const allParts = formatMessageParts(resolved, "high");
15663
15825
  if (allParts.length === 0) {
15664
15826
  throw new Error(
@@ -15671,7 +15833,7 @@ async function recallPart({
15671
15833
  `Part index ${partIndex} not found in message ${cursor}. Available indices: ${allParts.map((p) => p.partIndex).join(", ")}`
15672
15834
  );
15673
15835
  }
15674
- const truncatedText = chunkDTAZSLVU_cjs.truncateStringByTokens(target.text, maxTokens);
15836
+ const truncatedText = chunkVINRPDYQ_cjs.truncateStringByTokens(target.text, maxTokens);
15675
15837
  const wasTruncated = truncatedText !== target.text;
15676
15838
  return {
15677
15839
  text: truncatedText,
@@ -15690,6 +15852,7 @@ async function recallMessages({
15690
15852
  page = 1,
15691
15853
  limit = 20,
15692
15854
  detail = "low",
15855
+ threadScope,
15693
15856
  maxTokens = DEFAULT_MAX_RESULT_TOKENS
15694
15857
  }) {
15695
15858
  if (!memory) {
@@ -15706,7 +15869,7 @@ async function recallMessages({
15706
15869
  const rawPage = page === 0 ? 1 : page;
15707
15870
  const normalizedPage = Math.max(Math.min(rawPage, MAX_PAGE), -MAX_PAGE);
15708
15871
  const normalizedLimit = Math.min(limit, MAX_LIMIT);
15709
- const resolved = await resolveCursorMessage(memory, cursor);
15872
+ const resolved = await resolveCursorMessage(memory, cursor, { resourceId, threadScope });
15710
15873
  if ("hint" in resolved) {
15711
15874
  return {
15712
15875
  messages: resolved.hint,
@@ -15722,15 +15885,27 @@ async function recallMessages({
15722
15885
  };
15723
15886
  }
15724
15887
  const anchor = resolved;
15725
- if (anchor.threadId !== threadId) {
15726
- throw new Error("The requested cursor does not belong to the current thread");
15888
+ if (anchor.threadId && anchor.threadId !== threadId) {
15889
+ return {
15890
+ messages: `Cursor does not belong to the active thread. Expected thread "${threadId}" but cursor "${cursor}" belongs to "${anchor.threadId}".`,
15891
+ count: 0,
15892
+ cursor,
15893
+ page: normalizedPage,
15894
+ limit: normalizedLimit,
15895
+ detail,
15896
+ hasNextPage: false,
15897
+ hasPrevPage: false,
15898
+ truncated: false,
15899
+ tokenOffset: 0
15900
+ };
15727
15901
  }
15902
+ const resolvedThreadId = threadId;
15728
15903
  const isForward = normalizedPage > 0;
15729
15904
  const pageIndex = Math.max(Math.abs(normalizedPage), 1) - 1;
15730
15905
  const skip = pageIndex * normalizedLimit;
15731
15906
  const fetchCount = skip + normalizedLimit + 1;
15732
15907
  const result = await memory.recall({
15733
- threadId,
15908
+ threadId: resolvedThreadId,
15734
15909
  resourceId,
15735
15910
  page: 0,
15736
15911
  perPage: fetchCount,
@@ -15813,55 +15988,233 @@ High detail returns 1 part at a time. To continue: ${hints.join(", or ")}.`;
15813
15988
  tokenOffset: rendered.tokenOffset
15814
15989
  };
15815
15990
  }
15816
- var recallTool = (_memoryConfig) => {
15991
+ async function recallThreadFromStart({
15992
+ memory,
15993
+ threadId,
15994
+ resourceId,
15995
+ page = 1,
15996
+ limit = 20,
15997
+ detail = "low",
15998
+ maxTokens = DEFAULT_MAX_RESULT_TOKENS
15999
+ }) {
16000
+ if (!memory) {
16001
+ throw new Error("Memory instance is required for recall");
16002
+ }
16003
+ if (!threadId) {
16004
+ throw new Error("Thread ID is required for recall");
16005
+ }
16006
+ if (resourceId && memory.getThreadById) {
16007
+ const thread = await memory.getThreadById({ threadId });
16008
+ if (!thread || thread.resourceId !== resourceId) {
16009
+ throw new Error("Thread not found");
16010
+ }
16011
+ }
16012
+ const MAX_PAGE = 50;
16013
+ const MAX_LIMIT = 20;
16014
+ const normalizedPage = Math.max(Math.min(page, MAX_PAGE), 1);
16015
+ const normalizedLimit = Math.min(Math.max(limit, 1), MAX_LIMIT);
16016
+ const pageIndex = normalizedPage - 1;
16017
+ const fetchCount = pageIndex * normalizedLimit + normalizedLimit + 1;
16018
+ const result = await memory.recall({
16019
+ threadId,
16020
+ resourceId,
16021
+ page: 0,
16022
+ perPage: fetchCount,
16023
+ orderBy: { field: "createdAt", direction: "ASC" }
16024
+ });
16025
+ const visibleMessages = result.messages.filter(hasVisibleParts);
16026
+ const total = visibleMessages.length;
16027
+ const skip = pageIndex * normalizedLimit;
16028
+ const hasMore = total > skip + normalizedLimit;
16029
+ const messages = visibleMessages.slice(skip, skip + normalizedLimit);
16030
+ const allParts = [];
16031
+ const timestamps = /* @__PURE__ */ new Map();
16032
+ for (const msg of messages) {
16033
+ timestamps.set(msg.id, msg.createdAt);
16034
+ allParts.push(...formatMessageParts(msg, detail));
16035
+ }
16036
+ const rendered = renderFormattedParts(allParts, timestamps, { maxTokens });
16037
+ return {
16038
+ messages: rendered.text || "(no messages in this thread)",
16039
+ count: messages.length,
16040
+ cursor: messages[0]?.id || "",
16041
+ page: normalizedPage,
16042
+ limit: normalizedLimit,
16043
+ detail,
16044
+ hasNextPage: hasMore,
16045
+ hasPrevPage: pageIndex > 0,
16046
+ truncated: rendered.truncated,
16047
+ tokenOffset: rendered.tokenOffset
16048
+ };
16049
+ }
16050
+ var recallTool = (_memoryConfig, options) => {
16051
+ const retrievalScope = options?.retrievalScope ?? "thread";
16052
+ const isResourceScope = retrievalScope === "resource";
16053
+ 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. 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.`;
15817
16054
  return tools.createTool({
15818
16055
  id: "recall",
15819
- description: 'Retrieve raw message history near an observation group cursor. Observation group ranges use the format startId:endId. Pass either the start or end message ID as the cursor. Use detail="low" (default) for an overview, detail="high" for full content, or provide partIndex to fetch a specific part from the cursor message.',
16056
+ description,
15820
16057
  inputSchema: zod.z.object({
15821
- cursor: zod.z.string().min(1).describe("A single message ID to use as the pagination cursor. Extract it from the start or end of a range."),
16058
+ ...isResourceScope ? {
16059
+ mode: zod.z.enum(["messages", "threads", "search"]).optional().describe(
16060
+ '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.'
16061
+ ),
16062
+ threadId: zod.z.string().min(1).optional().describe('Browse a different thread. Use mode="threads" first to discover thread IDs.'),
16063
+ before: zod.z.string().optional().describe(
16064
+ 'For mode="threads": only show threads created before this date. ISO 8601 or natural date string (e.g. "2026-03-15", "2026-03-10T00:00:00Z").'
16065
+ ),
16066
+ after: zod.z.string().optional().describe(
16067
+ 'For mode="threads": only show threads created after this date. ISO 8601 or natural date string (e.g. "2026-03-01", "2026-03-10T00:00:00Z").'
16068
+ )
16069
+ } : {
16070
+ mode: zod.z.enum(["messages", "threads", "search"]).optional().describe(
16071
+ '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.'
16072
+ )
16073
+ },
16074
+ query: zod.z.string().min(1).optional().describe('Search query for mode="search". Finds messages semantically similar to this text.'),
16075
+ cursor: zod.z.string().min(1).optional().describe(
16076
+ 'A message ID to use as the pagination cursor. For mode="messages", provide either cursor or threadId. If only cursor is provided, it must belong to the current thread. Extract it from the start or end of an observation group range.'
16077
+ ),
15822
16078
  page: zod.z.number().int().min(-50).max(50).optional().describe(
15823
- "Pagination offset from the cursor. Positive pages move forward, negative pages move backward, and 0 is treated as 1."
16079
+ "Pagination offset. For messages: positive pages move forward from cursor, negative move backward. For threads: page number (0-indexed). 0 is treated as 1 for messages."
15824
16080
  ),
15825
- limit: zod.z.number().int().positive().max(20).optional().describe("Maximum number of messages to return. Defaults to 20."),
16081
+ limit: zod.z.number().int().positive().max(20).optional().describe("Maximum number of items to return per page. Defaults to 20."),
15826
16082
  detail: zod.z.enum(["low", "high"]).optional().describe(
15827
- 'Detail level. "low" (default) returns truncated text and tool names. "high" returns full content with tool args/results.'
16083
+ 'Detail level for messages. "low" (default) returns truncated text and tool names. "high" returns full content with tool args/results.'
15828
16084
  ),
15829
16085
  partIndex: zod.z.number().int().min(0).optional().describe(
15830
16086
  "Fetch a single part from the cursor message by its positional index. When provided, returns only that part at high detail. Indices are shown as [p0], [p1], etc. in recall results."
15831
16087
  )
15832
16088
  }),
15833
16089
  execute: async ({
16090
+ mode,
16091
+ query,
15834
16092
  cursor,
16093
+ threadId: explicitThreadId,
15835
16094
  page,
15836
16095
  limit,
15837
16096
  detail,
15838
- partIndex
16097
+ partIndex,
16098
+ before,
16099
+ after
15839
16100
  }, context2) => {
15840
16101
  const memory = context2?.memory;
15841
- const threadId = context2?.agent?.threadId;
16102
+ const currentThreadId = context2?.agent?.threadId;
15842
16103
  const resourceId = context2?.agent?.resourceId;
15843
16104
  if (!memory) {
15844
16105
  throw new Error("Memory instance is required for recall");
15845
16106
  }
15846
- if (!threadId) {
16107
+ if (mode === "search") {
16108
+ if (!query) {
16109
+ throw new Error('query is required for mode="search"');
16110
+ }
16111
+ if (!resourceId) {
16112
+ throw new Error("Resource ID is required for recall");
16113
+ }
16114
+ return searchMessagesForResource({
16115
+ memory,
16116
+ resourceId,
16117
+ currentThreadId: currentThreadId || void 0,
16118
+ query,
16119
+ topK: limit ?? 10,
16120
+ before,
16121
+ after,
16122
+ threadScope: !isResourceScope ? currentThreadId || void 0 : void 0
16123
+ });
16124
+ }
16125
+ if (mode === "threads") {
16126
+ if (!isResourceScope) {
16127
+ if (!currentThreadId || !memory.getThreadById) {
16128
+ return { error: "Could not resolve current thread." };
16129
+ }
16130
+ const thread = await memory.getThreadById({ threadId: currentThreadId });
16131
+ if (!thread) {
16132
+ return { error: "Could not resolve current thread." };
16133
+ }
16134
+ return {
16135
+ threads: `- **${thread.title || "(untitled)"}** \u2190 current
16136
+ id: ${thread.id}
16137
+ updated: ${formatTimestamp(thread.updatedAt)} | created: ${formatTimestamp(thread.createdAt)}`,
16138
+ count: 1,
16139
+ page: 0,
16140
+ hasMore: false
16141
+ };
16142
+ }
16143
+ if (!resourceId) {
16144
+ throw new Error("Resource ID is required for recall");
16145
+ }
16146
+ return listThreadsForResource({
16147
+ memory,
16148
+ resourceId,
16149
+ currentThreadId: currentThreadId || "",
16150
+ page: page ?? 0,
16151
+ limit: limit ?? 20,
16152
+ before,
16153
+ after
16154
+ });
16155
+ }
16156
+ const hasExplicitThreadId = typeof explicitThreadId === "string" && explicitThreadId.length > 0;
16157
+ const hasCursor = typeof cursor === "string" && cursor.length > 0;
16158
+ if (!hasExplicitThreadId && !hasCursor) {
16159
+ throw new Error('Either cursor or threadId is required for mode="messages"');
16160
+ }
16161
+ let targetThreadId;
16162
+ let threadScope;
16163
+ if (!isResourceScope) {
16164
+ targetThreadId = currentThreadId;
16165
+ threadScope = currentThreadId || void 0;
16166
+ } else if (hasExplicitThreadId) {
16167
+ if (!resourceId) {
16168
+ throw new Error("Resource ID is required for recall");
16169
+ }
16170
+ if (!memory.getThreadById) {
16171
+ throw new Error("Memory instance cannot verify thread access for recall");
16172
+ }
16173
+ const thread = await memory.getThreadById({ threadId: explicitThreadId });
16174
+ if (!thread || thread.resourceId !== resourceId) {
16175
+ throw new Error("Thread does not belong to the active resource");
16176
+ }
16177
+ targetThreadId = thread.id;
16178
+ threadScope = thread.id;
16179
+ } else {
16180
+ targetThreadId = currentThreadId;
16181
+ threadScope = currentThreadId || void 0;
16182
+ }
16183
+ if (hasCursor && !hasExplicitThreadId && !currentThreadId) {
16184
+ throw new Error("Current thread is required when browsing by cursor");
16185
+ }
16186
+ if (!targetThreadId) {
15847
16187
  throw new Error("Thread ID is required for recall");
15848
16188
  }
16189
+ if (!cursor) {
16190
+ return recallThreadFromStart({
16191
+ memory,
16192
+ threadId: targetThreadId,
16193
+ resourceId: isResourceScope ? resourceId : void 0,
16194
+ page: page ?? 1,
16195
+ limit: limit ?? 20,
16196
+ detail: detail ?? "low"
16197
+ });
16198
+ }
15849
16199
  if (partIndex !== void 0 && partIndex !== null) {
15850
16200
  return recallPart({
15851
16201
  memory,
15852
- threadId,
16202
+ threadId: targetThreadId,
16203
+ resourceId: isResourceScope ? resourceId : void 0,
15853
16204
  cursor,
15854
- partIndex
16205
+ partIndex,
16206
+ threadScope
15855
16207
  });
15856
16208
  }
15857
16209
  return recallMessages({
15858
16210
  memory,
15859
- threadId,
15860
- resourceId,
16211
+ threadId: targetThreadId,
16212
+ resourceId: isResourceScope ? resourceId : void 0,
15861
16213
  cursor,
15862
16214
  page,
15863
16215
  limit,
15864
- detail: detail ?? "low"
16216
+ detail: detail ?? "low",
16217
+ threadScope
15865
16218
  });
15866
16219
  }
15867
16220
  });
@@ -16113,6 +16466,19 @@ var Memory = class extends memory.MastraMemory {
16113
16466
  observationalMemory: config.options?.observationalMemory
16114
16467
  });
16115
16468
  this.threadConfig = mergedConfig;
16469
+ const omConfig = normalizeObservationalMemoryConfig(mergedConfig.observationalMemory);
16470
+ if (omConfig?.retrieval && typeof omConfig.retrieval === "object" && omConfig.retrieval.vector) {
16471
+ if (!this.vector) {
16472
+ throw new Error(
16473
+ "`retrieval: { vector: true }` requires a vector store. Pass a `vector` option to your Memory instance."
16474
+ );
16475
+ }
16476
+ if (!this.embedder) {
16477
+ throw new Error(
16478
+ "`retrieval: { vector: true }` requires an embedder. Pass an `embedder` option to your Memory instance."
16479
+ );
16480
+ }
16481
+ }
16116
16482
  }
16117
16483
  /**
16118
16484
  * Gets the memory storage domain, throwing if not available.
@@ -16882,13 +17248,17 @@ ${workingMemory}`;
16882
17248
  "Observational memory requires @mastra/core support for request-response-id-rotation. Please bump @mastra/core to a newer version."
16883
17249
  );
16884
17250
  }
16885
- const { ObservationalMemory: OMClass } = await import('./observational-memory-XIZTJN3S.cjs');
17251
+ const { ObservationalMemory: OMClass } = await import('./observational-memory-FATH657E.cjs');
17252
+ const onIndexObservations = this.hasRetrievalSearch(omConfig.retrieval) ? async (observation) => {
17253
+ await this.indexObservation(observation);
17254
+ } : void 0;
16886
17255
  return new OMClass({
16887
17256
  storage: memoryStore,
16888
17257
  scope: omConfig.scope,
16889
17258
  retrieval: omConfig.retrieval,
16890
17259
  shareTokenBudget: omConfig.shareTokenBudget,
16891
17260
  model: omConfig.model,
17261
+ onIndexObservations,
16892
17262
  observation: omConfig.observation ? {
16893
17263
  model: omConfig.observation.model,
16894
17264
  messageTokens: omConfig.observation.messageTokens,
@@ -17024,6 +17394,176 @@ Notes:
17024
17394
  const isMDWorkingMemory = !(`schema` in config.workingMemory) && (typeof config.workingMemory.template === `string` || config.workingMemory.template) && config.workingMemory;
17025
17395
  return Boolean(isMDWorkingMemory && isMDWorkingMemory.version === `vnext`);
17026
17396
  }
17397
+ getObservationEmbeddingIndexName(dimensions) {
17398
+ const defaultDimensions = 384;
17399
+ const usedDimensions = dimensions ?? defaultDimensions;
17400
+ const separator = this.vector?.indexSeparator ?? "_";
17401
+ return `memory${separator}observations${separator}${usedDimensions}`;
17402
+ }
17403
+ async createObservationEmbeddingIndex(dimensions) {
17404
+ const defaultDimensions = 384;
17405
+ const usedDimensions = dimensions ?? defaultDimensions;
17406
+ const indexName = this.getObservationEmbeddingIndexName(dimensions);
17407
+ if (typeof this.vector === `undefined`) {
17408
+ throw new Error(
17409
+ `Tried to create observation embedding index but no vector db is attached to this Memory instance.`
17410
+ );
17411
+ }
17412
+ await this.vector.createIndex({
17413
+ indexName,
17414
+ dimension: usedDimensions
17415
+ });
17416
+ return { indexName };
17417
+ }
17418
+ /**
17419
+ * Search observation groups across threads by semantic similarity.
17420
+ * Requires a vector store and embedder to be configured.
17421
+ */
17422
+ async searchMessages({
17423
+ query,
17424
+ resourceId,
17425
+ topK = 10,
17426
+ filter: filter3
17427
+ }) {
17428
+ if (!this.vector) {
17429
+ throw new Error("searchMessages requires a vector store. Configure vector and embedder on your Memory instance.");
17430
+ }
17431
+ const { embeddings, dimension } = await this.embedMessageContent(query);
17432
+ const { indexName } = await this.createObservationEmbeddingIndex(dimension);
17433
+ const vectorFilter = { resource_id: resourceId };
17434
+ if (filter3?.threadId) {
17435
+ vectorFilter.thread_id = filter3.threadId;
17436
+ }
17437
+ if (filter3?.observedAfter || filter3?.observedBefore) {
17438
+ vectorFilter.observed_at = {
17439
+ ...filter3.observedAfter ? { $gt: filter3.observedAfter.toISOString() } : {},
17440
+ ...filter3.observedBefore ? { $lt: filter3.observedBefore.toISOString() } : {}
17441
+ };
17442
+ }
17443
+ const queryResults = [];
17444
+ await Promise.all(
17445
+ embeddings.map(async (embedding) => {
17446
+ const results2 = await this.vector.query({
17447
+ indexName,
17448
+ queryVector: embedding,
17449
+ topK,
17450
+ filter: vectorFilter
17451
+ });
17452
+ for (const r of results2) {
17453
+ if (!r.metadata?.thread_id) {
17454
+ continue;
17455
+ }
17456
+ const groupId = typeof r.metadata.group_id === "string" ? r.metadata.group_id : void 0;
17457
+ if (!groupId) {
17458
+ continue;
17459
+ }
17460
+ queryResults.push({
17461
+ threadId: r.metadata.thread_id,
17462
+ score: r.score,
17463
+ groupId,
17464
+ range: typeof r.metadata.range === "string" ? r.metadata.range : void 0,
17465
+ text: typeof r.metadata.text === "string" ? r.metadata.text : void 0,
17466
+ observedAt: typeof r.metadata.observed_at === "string" || r.metadata.observed_at instanceof Date ? new Date(r.metadata.observed_at) : void 0
17467
+ });
17468
+ }
17469
+ })
17470
+ );
17471
+ const bestByGroup = /* @__PURE__ */ new Map();
17472
+ for (const result of queryResults) {
17473
+ if (!result.groupId) {
17474
+ continue;
17475
+ }
17476
+ const existing = bestByGroup.get(result.groupId);
17477
+ if (!existing || result.score > existing.score) {
17478
+ bestByGroup.set(result.groupId, result);
17479
+ }
17480
+ }
17481
+ const results = [...bestByGroup.values()].sort((a, b) => b.score - a.score);
17482
+ return { results };
17483
+ }
17484
+ /**
17485
+ * Index a single observation group into the observation vector store.
17486
+ */
17487
+ async indexObservation({
17488
+ text: text4,
17489
+ groupId,
17490
+ range,
17491
+ threadId,
17492
+ resourceId,
17493
+ observedAt
17494
+ }) {
17495
+ if (!this.vector || !this.embedder) return;
17496
+ const embedResult = await this.embedMessageContent(text4);
17497
+ if (embedResult.embeddings.length === 0 || embedResult.dimension === void 0) {
17498
+ return;
17499
+ }
17500
+ const { indexName } = await this.createObservationEmbeddingIndex(embedResult.dimension);
17501
+ await this.vector.upsert({
17502
+ indexName,
17503
+ vectors: embedResult.embeddings,
17504
+ metadata: embedResult.chunks.map((chunk) => ({
17505
+ group_id: groupId,
17506
+ range,
17507
+ thread_id: threadId,
17508
+ resource_id: resourceId,
17509
+ observed_at: observedAt?.toISOString(),
17510
+ text: chunk
17511
+ }))
17512
+ });
17513
+ }
17514
+ /**
17515
+ * Index a list of messages directly (without querying storage).
17516
+ * Used by observe-time indexing to vectorize newly-observed messages.
17517
+ */
17518
+ async indexMessagesList(messages) {
17519
+ if (!this.vector || !this.embedder) return;
17520
+ const embeddingData = [];
17521
+ let dimension;
17522
+ await Promise.all(
17523
+ messages.map(async (message) => {
17524
+ let textForEmbedding = null;
17525
+ if (message.content.content && typeof message.content.content === "string" && message.content.content.trim() !== "") {
17526
+ textForEmbedding = message.content.content;
17527
+ } else if (message.content.parts && message.content.parts.length > 0) {
17528
+ const joined = message.content.parts.filter((part) => part.type === "text").map((part) => part.text).join(" ").trim();
17529
+ if (joined) textForEmbedding = joined;
17530
+ }
17531
+ if (!textForEmbedding) return;
17532
+ const embedResult = await this.embedMessageContent(textForEmbedding);
17533
+ dimension = embedResult.dimension;
17534
+ embeddingData.push({
17535
+ embeddings: embedResult.embeddings,
17536
+ metadata: embedResult.chunks.map(() => ({
17537
+ message_id: message.id,
17538
+ thread_id: message.threadId,
17539
+ resource_id: message.resourceId
17540
+ }))
17541
+ });
17542
+ })
17543
+ );
17544
+ if (embeddingData.length > 0 && dimension !== void 0) {
17545
+ const { indexName } = await this.createEmbeddingIndex(dimension);
17546
+ const allVectors = [];
17547
+ const allMetadata = [];
17548
+ for (const data of embeddingData) {
17549
+ allVectors.push(...data.embeddings);
17550
+ allMetadata.push(...data.metadata);
17551
+ }
17552
+ await this.vector.upsert({
17553
+ indexName,
17554
+ vectors: allVectors,
17555
+ metadata: allMetadata
17556
+ });
17557
+ }
17558
+ }
17559
+ /**
17560
+ * Check whether retrieval search (vector-based) is enabled.
17561
+ * Returns true when `retrieval: { vector: true }` and Memory has vector + embedder configured.
17562
+ */
17563
+ hasRetrievalSearch(retrieval) {
17564
+ if (!retrieval || retrieval === true) return false;
17565
+ return !!retrieval.vector && !!this.vector && !!this.embedder;
17566
+ }
17027
17567
  listTools(config) {
17028
17568
  const mergedConfig = this.getMergedThreadConfig(config);
17029
17569
  const tools = {};
@@ -17031,8 +17571,9 @@ Notes:
17031
17571
  tools.updateWorkingMemory = this.isVNextWorkingMemoryConfig(mergedConfig) ? __experimental_updateWorkingMemoryToolVNext(mergedConfig) : updateWorkingMemoryTool(mergedConfig);
17032
17572
  }
17033
17573
  const omConfig = normalizeObservationalMemoryConfig(mergedConfig.observationalMemory);
17034
- if (omConfig?.retrieval && (omConfig.scope ?? "thread") === "thread") {
17035
- tools.recall = recallTool();
17574
+ if (omConfig?.retrieval) {
17575
+ const retrievalScope = typeof omConfig.retrieval === "object" ? omConfig.retrieval.scope ?? "resource" : "resource";
17576
+ tools.recall = recallTool(mergedConfig, { retrievalScope });
17036
17577
  }
17037
17578
  return tools;
17038
17579
  }
@@ -17595,18 +18136,18 @@ Notes:
17595
18136
  if (!effectiveConfig) return null;
17596
18137
  const engine = await this.omEngine;
17597
18138
  if (!engine) return null;
17598
- const { ObservationalMemoryProcessor } = await import('./observational-memory-XIZTJN3S.cjs');
18139
+ const { ObservationalMemoryProcessor } = await import('./observational-memory-FATH657E.cjs');
17599
18140
  return new ObservationalMemoryProcessor(engine, this);
17600
18141
  }
17601
18142
  };
17602
18143
 
17603
18144
  Object.defineProperty(exports, "ModelByInputTokens", {
17604
18145
  enumerable: true,
17605
- get: function () { return chunkDTAZSLVU_cjs.ModelByInputTokens; }
18146
+ get: function () { return chunkVINRPDYQ_cjs.ModelByInputTokens; }
17606
18147
  });
17607
18148
  Object.defineProperty(exports, "getObservationsAsOf", {
17608
18149
  enumerable: true,
17609
- get: function () { return chunkDTAZSLVU_cjs.getObservationsAsOf; }
18150
+ get: function () { return chunkVINRPDYQ_cjs.getObservationsAsOf; }
17610
18151
  });
17611
18152
  Object.defineProperty(exports, "extractWorkingMemoryContent", {
17612
18153
  enumerable: true,