@mastra/server 1.18.0-alpha.2 → 1.18.0-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.
Files changed (26) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/{chunk-NTZOZIKF.cjs → chunk-GDWCOWNR.cjs} +584 -43
  3. package/dist/chunk-GDWCOWNR.cjs.map +1 -0
  4. package/dist/{chunk-DJQT32SV.cjs → chunk-HUAXEKGI.cjs} +353 -105
  5. package/dist/chunk-HUAXEKGI.cjs.map +1 -0
  6. package/dist/{chunk-FGZC4JP7.js → chunk-SDKSW2BQ.js} +571 -30
  7. package/dist/chunk-SDKSW2BQ.js.map +1 -0
  8. package/dist/{chunk-PVUR75AM.js → chunk-YUTITKH2.js} +353 -105
  9. package/dist/chunk-YUTITKH2.js.map +1 -0
  10. package/dist/docs/SKILL.md +1 -1
  11. package/dist/docs/assets/SOURCE_MAP.json +1 -1
  12. package/dist/{observational-memory-UGDENJPE-NVMIXNI4.js → observational-memory-SN7GKMHZ-IWVBFBS6.js} +3 -3
  13. package/dist/{observational-memory-UGDENJPE-NVMIXNI4.js.map → observational-memory-SN7GKMHZ-IWVBFBS6.js.map} +1 -1
  14. package/dist/{observational-memory-UGDENJPE-DM3C7ZXI.cjs → observational-memory-SN7GKMHZ-WOK4TGDH.cjs} +26 -26
  15. package/dist/{observational-memory-UGDENJPE-DM3C7ZXI.cjs.map → observational-memory-SN7GKMHZ-WOK4TGDH.cjs.map} +1 -1
  16. package/dist/server/handlers/agent-builder.cjs +16 -16
  17. package/dist/server/handlers/agent-builder.js +1 -1
  18. package/dist/server/handlers.cjs +2 -2
  19. package/dist/server/handlers.js +1 -1
  20. package/dist/server/server-adapter/index.cjs +16 -16
  21. package/dist/server/server-adapter/index.js +1 -1
  22. package/package.json +4 -4
  23. package/dist/chunk-DJQT32SV.cjs.map +0 -1
  24. package/dist/chunk-FGZC4JP7.js.map +0 -1
  25. package/dist/chunk-NTZOZIKF.cjs.map +0 -1
  26. package/dist/chunk-PVUR75AM.js.map +0 -1
@@ -5,7 +5,7 @@ var chunk5N66PU3H_cjs = require('./chunk-5N66PU3H.cjs');
5
5
  var chunkDOHUOYZS_cjs = require('./chunk-DOHUOYZS.cjs');
6
6
  var chunkEXKS4QPI_cjs = require('./chunk-EXKS4QPI.cjs');
7
7
  var chunkFPURK3UW_cjs = require('./chunk-FPURK3UW.cjs');
8
- var chunkDJQT32SV_cjs = require('./chunk-DJQT32SV.cjs');
8
+ var chunkHUAXEKGI_cjs = require('./chunk-HUAXEKGI.cjs');
9
9
  var chunk3W54ZNYP_cjs = require('./chunk-3W54ZNYP.cjs');
10
10
  var chunkHITLRKIU_cjs = require('./chunk-HITLRKIU.cjs');
11
11
  var chunkVTPTMQFA_cjs = require('./chunk-VTPTMQFA.cjs');
@@ -16164,7 +16164,7 @@ function parseRangeFormat(cursor) {
16164
16164
  }
16165
16165
  return null;
16166
16166
  }
16167
- async function resolveCursorMessage(memory, cursor) {
16167
+ async function resolveCursorMessage(memory, cursor, access) {
16168
16168
  const normalized = cursor.trim();
16169
16169
  if (!normalized) {
16170
16170
  throw new Error("Cursor is required");
@@ -16182,8 +16182,171 @@ async function resolveCursorMessage(memory, cursor) {
16182
16182
  if (!message) {
16183
16183
  throw new Error(`Could not resolve cursor message: ${cursor}`);
16184
16184
  }
16185
+ if (access?.resourceId && message.resourceId !== access.resourceId) {
16186
+ throw new Error(`Could not resolve cursor message: ${cursor}`);
16187
+ }
16188
+ if (access?.threadScope && message.threadId !== access.threadScope) {
16189
+ throw new Error(`Could not resolve cursor message: ${cursor}`);
16190
+ }
16185
16191
  return message;
16186
16192
  }
16193
+ async function listThreadsForResource({
16194
+ memory,
16195
+ resourceId,
16196
+ currentThreadId,
16197
+ page = 0,
16198
+ limit = 20,
16199
+ before,
16200
+ after
16201
+ }) {
16202
+ if (!resourceId) {
16203
+ throw new Error("Resource ID is required to list threads");
16204
+ }
16205
+ const MAX_LIMIT = 50;
16206
+ const normalizedLimit = Math.min(Math.max(limit, 1), MAX_LIMIT);
16207
+ const hasDateFilter = !!(before || after);
16208
+ const beforeDate = before ? new Date(before) : null;
16209
+ const afterDate = after ? new Date(after) : null;
16210
+ const result = await memory.listThreads({
16211
+ filter: { resourceId },
16212
+ page: hasDateFilter ? 0 : page,
16213
+ perPage: hasDateFilter ? false : normalizedLimit,
16214
+ orderBy: { field: "updatedAt", direction: "DESC" }
16215
+ });
16216
+ let threads = result.threads;
16217
+ if (beforeDate) {
16218
+ threads = threads.filter((t) => t.createdAt < beforeDate);
16219
+ }
16220
+ if (afterDate) {
16221
+ threads = threads.filter((t) => t.createdAt > afterDate);
16222
+ }
16223
+ let hasMore;
16224
+ if (hasDateFilter) {
16225
+ const offset = page * normalizedLimit;
16226
+ hasMore = offset + normalizedLimit < threads.length;
16227
+ threads = threads.slice(offset, offset + normalizedLimit);
16228
+ } else {
16229
+ hasMore = result.hasMore;
16230
+ }
16231
+ if (threads.length === 0) {
16232
+ return {
16233
+ threads: "No threads found matching the criteria.",
16234
+ count: 0,
16235
+ page,
16236
+ hasMore: false
16237
+ };
16238
+ }
16239
+ const lines = [];
16240
+ for (const thread of threads) {
16241
+ const isCurrent = thread.id === currentThreadId;
16242
+ const title = thread.title || "(untitled)";
16243
+ const updated = formatTimestamp(thread.updatedAt);
16244
+ const created = formatTimestamp(thread.createdAt);
16245
+ const marker21 = isCurrent ? " \u2190 current" : "";
16246
+ lines.push(`- **${title}**${marker21}`);
16247
+ lines.push(` id: ${thread.id}`);
16248
+ lines.push(` updated: ${updated} | created: ${created}`);
16249
+ }
16250
+ return {
16251
+ threads: lines.join("\n"),
16252
+ count: threads.length,
16253
+ page,
16254
+ hasMore
16255
+ };
16256
+ }
16257
+ async function searchMessagesForResource({
16258
+ memory,
16259
+ resourceId,
16260
+ currentThreadId,
16261
+ query,
16262
+ topK = 10,
16263
+ maxTokens = DEFAULT_MAX_RESULT_TOKENS,
16264
+ before,
16265
+ after,
16266
+ threadScope
16267
+ }) {
16268
+ if (!memory.searchMessages) {
16269
+ return {
16270
+ results: "Search is not configured. Enable it with `retrieval: { vector: true }` and configure a vector store and embedder on your Memory instance.",
16271
+ count: 0
16272
+ };
16273
+ }
16274
+ const MAX_TOPK = 20;
16275
+ const clampedTopK = Math.min(Math.max(topK, 1), MAX_TOPK);
16276
+ const effectiveTopK = threadScope || before || after ? Math.max(clampedTopK * 3, clampedTopK + 10) : clampedTopK;
16277
+ const searchTopK = Math.min(MAX_TOPK, effectiveTopK);
16278
+ const beforeDate = before ? new Date(before) : void 0;
16279
+ const afterDate = after ? new Date(after) : void 0;
16280
+ const { results } = await memory.searchMessages({
16281
+ query,
16282
+ resourceId,
16283
+ topK: searchTopK,
16284
+ filter: {
16285
+ ...threadScope ? { threadId: threadScope } : {},
16286
+ ...afterDate ? { observedAfter: afterDate } : {},
16287
+ ...beforeDate ? { observedBefore: beforeDate } : {}
16288
+ }
16289
+ });
16290
+ if (results.length === 0) {
16291
+ return {
16292
+ results: "No matching messages found.",
16293
+ count: 0
16294
+ };
16295
+ }
16296
+ const threadIds = [...new Set(results.map((r) => r.threadId))];
16297
+ const threadMap = /* @__PURE__ */ new Map();
16298
+ if (memory.getThreadById) {
16299
+ await Promise.all(
16300
+ threadIds.map(async (id) => {
16301
+ const thread = await memory.getThreadById({ threadId: id });
16302
+ if (thread) threadMap.set(id, thread);
16303
+ })
16304
+ );
16305
+ }
16306
+ const filteredMatches = results.filter((match) => {
16307
+ if (threadScope && match.threadId !== threadScope) return false;
16308
+ if (beforeDate && match.observedAt && match.observedAt >= beforeDate) return false;
16309
+ if (afterDate && match.observedAt && match.observedAt <= afterDate) return false;
16310
+ return true;
16311
+ });
16312
+ if (filteredMatches.length === 0) {
16313
+ return { results: "No matching messages found.", count: 0 };
16314
+ }
16315
+ const limitedMatches = filteredMatches.slice(0, clampedTopK);
16316
+ const sections = limitedMatches.map((match) => {
16317
+ const thread = threadMap.get(match.threadId);
16318
+ const title = thread?.title || "(untitled)";
16319
+ const isCurrentThread = match.threadId === currentThreadId;
16320
+ const generationLabel = isCurrentThread ? "Current thread memory" : "Older memory from another thread";
16321
+ const generationDetail = isCurrentThread ? "This result came from the current thread." : "This result came from an older memory generation in another thread.";
16322
+ const threadLine = `- thread: ${match.threadId}${thread ? ` (${title})` : ""}`;
16323
+ 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";
16324
+ const updatedLine = thread ? `- thread updated: ${formatTimestamp(thread.updatedAt)}` : void 0;
16325
+ const groupLine = match.groupId ? `- observation group: ${match.groupId}` : void 0;
16326
+ const scoreLine = `- score: ${match.score.toFixed(2)}`;
16327
+ const body = (match.text || "").trim() || "_Observation text unavailable._";
16328
+ return [
16329
+ `### ${generationLabel}`,
16330
+ "",
16331
+ generationDetail,
16332
+ threadLine,
16333
+ sourceLine,
16334
+ updatedLine,
16335
+ groupLine,
16336
+ scoreLine,
16337
+ "",
16338
+ "```text",
16339
+ body,
16340
+ "```"
16341
+ ].filter(Boolean).join("\n");
16342
+ });
16343
+ const assembled = sections.join("\n\n");
16344
+ const { text: limited } = truncateByTokens(assembled, maxTokens);
16345
+ return {
16346
+ results: limited,
16347
+ count: limitedMatches.length
16348
+ };
16349
+ }
16187
16350
  var LOW_DETAIL_PART_TOKENS = 30;
16188
16351
  var AUTO_EXPAND_TEXT_TOKENS = 100;
16189
16352
  var AUTO_EXPAND_TOOL_TOKENS = 20;
@@ -16193,8 +16356,8 @@ function formatTimestamp(date) {
16193
16356
  return date.toISOString().replace("T", " ").replace(/\.\d{3}Z$/, "Z");
16194
16357
  }
16195
16358
  function truncateByTokens(text42, maxTokens, hint) {
16196
- if (chunkDJQT32SV_cjs.estimateTokenCount(text42) <= maxTokens) return { text: text42, wasTruncated: false };
16197
- const truncated = chunkDJQT32SV_cjs.truncateStringByTokens(text42, maxTokens);
16359
+ if (chunkHUAXEKGI_cjs.estimateTokenCount(text42) <= maxTokens) return { text: text42, wasTruncated: false };
16360
+ const truncated = chunkHUAXEKGI_cjs.truncateStringByTokens(text42, maxTokens);
16198
16361
  const suffix = hint ? ` [${hint} for more]` : "";
16199
16362
  return { text: truncated + suffix, wasTruncated: true };
16200
16363
  }
@@ -16227,11 +16390,11 @@ function formatMessageParts(msg, detail) {
16227
16390
  } else if (partType === "tool-invocation") {
16228
16391
  const inv = part.toolInvocation;
16229
16392
  if (inv.state === "result") {
16230
- const { value: resultValue } = chunkDJQT32SV_cjs.resolveToolResultValue(
16393
+ const { value: resultValue } = chunkHUAXEKGI_cjs.resolveToolResultValue(
16231
16394
  part,
16232
16395
  inv.result
16233
16396
  );
16234
- const resultStr = chunkDJQT32SV_cjs.formatToolResultForObserver(resultValue, { maxTokens: HIGH_DETAIL_TOOL_RESULT_TOKENS });
16397
+ const resultStr = chunkHUAXEKGI_cjs.formatToolResultForObserver(resultValue, { maxTokens: HIGH_DETAIL_TOOL_RESULT_TOKENS });
16235
16398
  const fullText = `[Tool Result: ${inv.toolName}]
16236
16399
  ${resultStr}`;
16237
16400
  parts.push(makePart(msg, i, "tool-result", fullText, detail));
@@ -16293,9 +16456,9 @@ function expandPriority(part) {
16293
16456
  }
16294
16457
  function renderFormattedParts(parts, timestamps, options) {
16295
16458
  const text42 = buildRenderedText(parts, timestamps);
16296
- let totalTokens = chunkDJQT32SV_cjs.estimateTokenCount(text42);
16459
+ let totalTokens = chunkHUAXEKGI_cjs.estimateTokenCount(text42);
16297
16460
  if (totalTokens > options.maxTokens) {
16298
- const truncated = chunkDJQT32SV_cjs.truncateStringByTokens(text42, options.maxTokens);
16461
+ const truncated = chunkHUAXEKGI_cjs.truncateStringByTokens(text42, options.maxTokens);
16299
16462
  return { text: truncated, truncated: true, tokenOffset: totalTokens - options.maxTokens };
16300
16463
  }
16301
16464
  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));
@@ -16306,8 +16469,8 @@ function renderFormattedParts(parts, timestamps, options) {
16306
16469
  for (const { part, index } of truncatedIndices) {
16307
16470
  if (remaining <= 0) break;
16308
16471
  const maxTokens = expandLimit(part);
16309
- const fullTokens = chunkDJQT32SV_cjs.estimateTokenCount(part.fullText);
16310
- const currentTokens = chunkDJQT32SV_cjs.estimateTokenCount(part.text);
16472
+ const fullTokens = chunkHUAXEKGI_cjs.estimateTokenCount(part.fullText);
16473
+ const currentTokens = chunkHUAXEKGI_cjs.estimateTokenCount(part.text);
16311
16474
  const targetTokens = Math.min(fullTokens, maxTokens);
16312
16475
  const delta = targetTokens - currentTokens;
16313
16476
  if (delta <= 0) continue;
@@ -16318,24 +16481,26 @@ function renderFormattedParts(parts, timestamps, options) {
16318
16481
  const expandedLimit = Math.min(currentTokens + remaining, maxTokens);
16319
16482
  const hint = `recall cursor="${part.messageId}" partIndex=${part.partIndex} detail="high"`;
16320
16483
  const { text: expanded2 } = truncateByTokens(part.fullText, expandedLimit, hint);
16321
- const expandedDelta = chunkDJQT32SV_cjs.estimateTokenCount(expanded2) - currentTokens;
16484
+ const expandedDelta = chunkHUAXEKGI_cjs.estimateTokenCount(expanded2) - currentTokens;
16322
16485
  parts[index] = { ...part, text: expanded2 };
16323
16486
  remaining -= expandedDelta;
16324
16487
  }
16325
16488
  }
16326
16489
  const expanded = buildRenderedText(parts, timestamps);
16327
- const expandedTokens = chunkDJQT32SV_cjs.estimateTokenCount(expanded);
16490
+ const expandedTokens = chunkHUAXEKGI_cjs.estimateTokenCount(expanded);
16328
16491
  if (expandedTokens <= options.maxTokens) {
16329
16492
  return { text: expanded, truncated: false, tokenOffset: 0 };
16330
16493
  }
16331
- const hardTruncated = chunkDJQT32SV_cjs.truncateStringByTokens(expanded, options.maxTokens);
16494
+ const hardTruncated = chunkHUAXEKGI_cjs.truncateStringByTokens(expanded, options.maxTokens);
16332
16495
  return { text: hardTruncated, truncated: true, tokenOffset: expandedTokens - options.maxTokens };
16333
16496
  }
16334
16497
  async function recallPart({
16335
16498
  memory,
16336
16499
  threadId,
16500
+ resourceId,
16337
16501
  cursor,
16338
16502
  partIndex,
16503
+ threadScope,
16339
16504
  maxTokens = DEFAULT_MAX_RESULT_TOKENS
16340
16505
  }) {
16341
16506
  if (!memory || typeof memory.getMemoryStore !== "function") {
@@ -16344,13 +16509,10 @@ async function recallPart({
16344
16509
  if (!threadId) {
16345
16510
  throw new Error("Thread ID is required for recall");
16346
16511
  }
16347
- const resolved = await resolveCursorMessage(memory, cursor);
16512
+ const resolved = await resolveCursorMessage(memory, cursor, { resourceId, threadScope });
16348
16513
  if ("hint" in resolved) {
16349
16514
  throw new Error(resolved.hint);
16350
16515
  }
16351
- if (resolved.threadId !== threadId) {
16352
- throw new Error("The requested cursor does not belong to the current thread");
16353
- }
16354
16516
  const allParts = formatMessageParts(resolved, "high");
16355
16517
  if (allParts.length === 0) {
16356
16518
  throw new Error(
@@ -16363,7 +16525,7 @@ async function recallPart({
16363
16525
  `Part index ${partIndex} not found in message ${cursor}. Available indices: ${allParts.map((p) => p.partIndex).join(", ")}`
16364
16526
  );
16365
16527
  }
16366
- const truncatedText = chunkDJQT32SV_cjs.truncateStringByTokens(target.text, maxTokens);
16528
+ const truncatedText = chunkHUAXEKGI_cjs.truncateStringByTokens(target.text, maxTokens);
16367
16529
  const wasTruncated = truncatedText !== target.text;
16368
16530
  return {
16369
16531
  text: truncatedText,
@@ -16382,6 +16544,7 @@ async function recallMessages({
16382
16544
  page = 1,
16383
16545
  limit = 20,
16384
16546
  detail = "low",
16547
+ threadScope,
16385
16548
  maxTokens = DEFAULT_MAX_RESULT_TOKENS
16386
16549
  }) {
16387
16550
  if (!memory) {
@@ -16398,7 +16561,7 @@ async function recallMessages({
16398
16561
  const rawPage = page === 0 ? 1 : page;
16399
16562
  const normalizedPage = Math.max(Math.min(rawPage, MAX_PAGE), -MAX_PAGE);
16400
16563
  const normalizedLimit = Math.min(limit, MAX_LIMIT);
16401
- const resolved = await resolveCursorMessage(memory, cursor);
16564
+ const resolved = await resolveCursorMessage(memory, cursor, { resourceId, threadScope });
16402
16565
  if ("hint" in resolved) {
16403
16566
  return {
16404
16567
  messages: resolved.hint,
@@ -16414,15 +16577,27 @@ async function recallMessages({
16414
16577
  };
16415
16578
  }
16416
16579
  const anchor = resolved;
16417
- if (anchor.threadId !== threadId) {
16418
- throw new Error("The requested cursor does not belong to the current thread");
16580
+ if (anchor.threadId && anchor.threadId !== threadId) {
16581
+ return {
16582
+ messages: `Cursor does not belong to the active thread. Expected thread "${threadId}" but cursor "${cursor}" belongs to "${anchor.threadId}".`,
16583
+ count: 0,
16584
+ cursor,
16585
+ page: normalizedPage,
16586
+ limit: normalizedLimit,
16587
+ detail,
16588
+ hasNextPage: false,
16589
+ hasPrevPage: false,
16590
+ truncated: false,
16591
+ tokenOffset: 0
16592
+ };
16419
16593
  }
16594
+ const resolvedThreadId = threadId;
16420
16595
  const isForward = normalizedPage > 0;
16421
16596
  const pageIndex = Math.max(Math.abs(normalizedPage), 1) - 1;
16422
16597
  const skip = pageIndex * normalizedLimit;
16423
16598
  const fetchCount = skip + normalizedLimit + 1;
16424
16599
  const result = await memory.recall({
16425
- threadId,
16600
+ threadId: resolvedThreadId,
16426
16601
  resourceId,
16427
16602
  page: 0,
16428
16603
  perPage: fetchCount,
@@ -16505,55 +16680,233 @@ High detail returns 1 part at a time. To continue: ${hints.join(", or ")}.`;
16505
16680
  tokenOffset: rendered.tokenOffset
16506
16681
  };
16507
16682
  }
16508
- var recallTool = (_memoryConfig) => {
16683
+ async function recallThreadFromStart({
16684
+ memory,
16685
+ threadId,
16686
+ resourceId,
16687
+ page = 1,
16688
+ limit = 20,
16689
+ detail = "low",
16690
+ maxTokens = DEFAULT_MAX_RESULT_TOKENS
16691
+ }) {
16692
+ if (!memory) {
16693
+ throw new Error("Memory instance is required for recall");
16694
+ }
16695
+ if (!threadId) {
16696
+ throw new Error("Thread ID is required for recall");
16697
+ }
16698
+ if (resourceId && memory.getThreadById) {
16699
+ const thread = await memory.getThreadById({ threadId });
16700
+ if (!thread || thread.resourceId !== resourceId) {
16701
+ throw new Error("Thread not found");
16702
+ }
16703
+ }
16704
+ const MAX_PAGE = 50;
16705
+ const MAX_LIMIT = 20;
16706
+ const normalizedPage = Math.max(Math.min(page, MAX_PAGE), 1);
16707
+ const normalizedLimit = Math.min(Math.max(limit, 1), MAX_LIMIT);
16708
+ const pageIndex = normalizedPage - 1;
16709
+ const fetchCount = pageIndex * normalizedLimit + normalizedLimit + 1;
16710
+ const result = await memory.recall({
16711
+ threadId,
16712
+ resourceId,
16713
+ page: 0,
16714
+ perPage: fetchCount,
16715
+ orderBy: { field: "createdAt", direction: "ASC" }
16716
+ });
16717
+ const visibleMessages = result.messages.filter(hasVisibleParts);
16718
+ const total = visibleMessages.length;
16719
+ const skip = pageIndex * normalizedLimit;
16720
+ const hasMore = total > skip + normalizedLimit;
16721
+ const messages = visibleMessages.slice(skip, skip + normalizedLimit);
16722
+ const allParts = [];
16723
+ const timestamps = /* @__PURE__ */ new Map();
16724
+ for (const msg of messages) {
16725
+ timestamps.set(msg.id, msg.createdAt);
16726
+ allParts.push(...formatMessageParts(msg, detail));
16727
+ }
16728
+ const rendered = renderFormattedParts(allParts, timestamps, { maxTokens });
16729
+ return {
16730
+ messages: rendered.text || "(no messages in this thread)",
16731
+ count: messages.length,
16732
+ cursor: messages[0]?.id || "",
16733
+ page: normalizedPage,
16734
+ limit: normalizedLimit,
16735
+ detail,
16736
+ hasNextPage: hasMore,
16737
+ hasPrevPage: pageIndex > 0,
16738
+ truncated: rendered.truncated,
16739
+ tokenOffset: rendered.tokenOffset
16740
+ };
16741
+ }
16742
+ var recallTool = (_memoryConfig, options) => {
16743
+ const retrievalScope = options?.retrievalScope ?? "thread";
16744
+ const isResourceScope = retrievalScope === "resource";
16745
+ 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.`;
16509
16746
  return tools.createTool({
16510
16747
  id: "recall",
16511
- 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.',
16748
+ description,
16512
16749
  inputSchema: zod.z.object({
16513
- 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."),
16750
+ ...isResourceScope ? {
16751
+ mode: zod.z.enum(["messages", "threads", "search"]).optional().describe(
16752
+ '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.'
16753
+ ),
16754
+ threadId: zod.z.string().min(1).optional().describe('Browse a different thread. Use mode="threads" first to discover thread IDs.'),
16755
+ before: zod.z.string().optional().describe(
16756
+ '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").'
16757
+ ),
16758
+ after: zod.z.string().optional().describe(
16759
+ '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").'
16760
+ )
16761
+ } : {
16762
+ mode: zod.z.enum(["messages", "threads", "search"]).optional().describe(
16763
+ '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.'
16764
+ )
16765
+ },
16766
+ query: zod.z.string().min(1).optional().describe('Search query for mode="search". Finds messages semantically similar to this text.'),
16767
+ cursor: zod.z.string().min(1).optional().describe(
16768
+ '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.'
16769
+ ),
16514
16770
  page: zod.z.number().int().min(-50).max(50).optional().describe(
16515
- "Pagination offset from the cursor. Positive pages move forward, negative pages move backward, and 0 is treated as 1."
16771
+ "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."
16516
16772
  ),
16517
- limit: zod.z.number().int().positive().max(20).optional().describe("Maximum number of messages to return. Defaults to 20."),
16773
+ limit: zod.z.number().int().positive().max(20).optional().describe("Maximum number of items to return per page. Defaults to 20."),
16518
16774
  detail: zod.z.enum(["low", "high"]).optional().describe(
16519
- 'Detail level. "low" (default) returns truncated text and tool names. "high" returns full content with tool args/results.'
16775
+ 'Detail level for messages. "low" (default) returns truncated text and tool names. "high" returns full content with tool args/results.'
16520
16776
  ),
16521
16777
  partIndex: zod.z.number().int().min(0).optional().describe(
16522
16778
  "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."
16523
16779
  )
16524
16780
  }),
16525
16781
  execute: async ({
16782
+ mode,
16783
+ query,
16526
16784
  cursor,
16785
+ threadId: explicitThreadId,
16527
16786
  page,
16528
16787
  limit,
16529
16788
  detail,
16530
- partIndex
16789
+ partIndex,
16790
+ before,
16791
+ after
16531
16792
  }, context2) => {
16532
16793
  const memory = context2?.memory;
16533
- const threadId = context2?.agent?.threadId;
16794
+ const currentThreadId = context2?.agent?.threadId;
16534
16795
  const resourceId = context2?.agent?.resourceId;
16535
16796
  if (!memory) {
16536
16797
  throw new Error("Memory instance is required for recall");
16537
16798
  }
16538
- if (!threadId) {
16799
+ if (mode === "search") {
16800
+ if (!query) {
16801
+ throw new Error('query is required for mode="search"');
16802
+ }
16803
+ if (!resourceId) {
16804
+ throw new Error("Resource ID is required for recall");
16805
+ }
16806
+ return searchMessagesForResource({
16807
+ memory,
16808
+ resourceId,
16809
+ currentThreadId: currentThreadId || void 0,
16810
+ query,
16811
+ topK: limit ?? 10,
16812
+ before,
16813
+ after,
16814
+ threadScope: !isResourceScope ? currentThreadId || void 0 : void 0
16815
+ });
16816
+ }
16817
+ if (mode === "threads") {
16818
+ if (!isResourceScope) {
16819
+ if (!currentThreadId || !memory.getThreadById) {
16820
+ return { error: "Could not resolve current thread." };
16821
+ }
16822
+ const thread = await memory.getThreadById({ threadId: currentThreadId });
16823
+ if (!thread) {
16824
+ return { error: "Could not resolve current thread." };
16825
+ }
16826
+ return {
16827
+ threads: `- **${thread.title || "(untitled)"}** \u2190 current
16828
+ id: ${thread.id}
16829
+ updated: ${formatTimestamp(thread.updatedAt)} | created: ${formatTimestamp(thread.createdAt)}`,
16830
+ count: 1,
16831
+ page: 0,
16832
+ hasMore: false
16833
+ };
16834
+ }
16835
+ if (!resourceId) {
16836
+ throw new Error("Resource ID is required for recall");
16837
+ }
16838
+ return listThreadsForResource({
16839
+ memory,
16840
+ resourceId,
16841
+ currentThreadId: currentThreadId || "",
16842
+ page: page ?? 0,
16843
+ limit: limit ?? 20,
16844
+ before,
16845
+ after
16846
+ });
16847
+ }
16848
+ const hasExplicitThreadId = typeof explicitThreadId === "string" && explicitThreadId.length > 0;
16849
+ const hasCursor = typeof cursor === "string" && cursor.length > 0;
16850
+ if (!hasExplicitThreadId && !hasCursor) {
16851
+ throw new Error('Either cursor or threadId is required for mode="messages"');
16852
+ }
16853
+ let targetThreadId;
16854
+ let threadScope;
16855
+ if (!isResourceScope) {
16856
+ targetThreadId = currentThreadId;
16857
+ threadScope = currentThreadId || void 0;
16858
+ } else if (hasExplicitThreadId) {
16859
+ if (!resourceId) {
16860
+ throw new Error("Resource ID is required for recall");
16861
+ }
16862
+ if (!memory.getThreadById) {
16863
+ throw new Error("Memory instance cannot verify thread access for recall");
16864
+ }
16865
+ const thread = await memory.getThreadById({ threadId: explicitThreadId });
16866
+ if (!thread || thread.resourceId !== resourceId) {
16867
+ throw new Error("Thread does not belong to the active resource");
16868
+ }
16869
+ targetThreadId = thread.id;
16870
+ threadScope = thread.id;
16871
+ } else {
16872
+ targetThreadId = currentThreadId;
16873
+ threadScope = currentThreadId || void 0;
16874
+ }
16875
+ if (hasCursor && !hasExplicitThreadId && !currentThreadId) {
16876
+ throw new Error("Current thread is required when browsing by cursor");
16877
+ }
16878
+ if (!targetThreadId) {
16539
16879
  throw new Error("Thread ID is required for recall");
16540
16880
  }
16881
+ if (!cursor) {
16882
+ return recallThreadFromStart({
16883
+ memory,
16884
+ threadId: targetThreadId,
16885
+ resourceId: isResourceScope ? resourceId : void 0,
16886
+ page: page ?? 1,
16887
+ limit: limit ?? 20,
16888
+ detail: detail ?? "low"
16889
+ });
16890
+ }
16541
16891
  if (partIndex !== void 0 && partIndex !== null) {
16542
16892
  return recallPart({
16543
16893
  memory,
16544
- threadId,
16894
+ threadId: targetThreadId,
16895
+ resourceId: isResourceScope ? resourceId : void 0,
16545
16896
  cursor,
16546
- partIndex
16897
+ partIndex,
16898
+ threadScope
16547
16899
  });
16548
16900
  }
16549
16901
  return recallMessages({
16550
16902
  memory,
16551
- threadId,
16552
- resourceId,
16903
+ threadId: targetThreadId,
16904
+ resourceId: isResourceScope ? resourceId : void 0,
16553
16905
  cursor,
16554
16906
  page,
16555
16907
  limit,
16556
- detail: detail ?? "low"
16908
+ detail: detail ?? "low",
16909
+ threadScope
16557
16910
  });
16558
16911
  }
16559
16912
  });
@@ -16805,6 +17158,19 @@ var Memory = class extends memory.MastraMemory {
16805
17158
  observationalMemory: config.options?.observationalMemory
16806
17159
  });
16807
17160
  this.threadConfig = mergedConfig;
17161
+ const omConfig = normalizeObservationalMemoryConfig(mergedConfig.observationalMemory);
17162
+ if (omConfig?.retrieval && typeof omConfig.retrieval === "object" && omConfig.retrieval.vector) {
17163
+ if (!this.vector) {
17164
+ throw new Error(
17165
+ "`retrieval: { vector: true }` requires a vector store. Pass a `vector` option to your Memory instance."
17166
+ );
17167
+ }
17168
+ if (!this.embedder) {
17169
+ throw new Error(
17170
+ "`retrieval: { vector: true }` requires an embedder. Pass an `embedder` option to your Memory instance."
17171
+ );
17172
+ }
17173
+ }
16808
17174
  }
16809
17175
  /**
16810
17176
  * Gets the memory storage domain, throwing if not available.
@@ -17193,7 +17559,7 @@ ${workingMemory}`;
17193
17559
  }
17194
17560
  return chunks;
17195
17561
  }
17196
- hasher = chunkDJQT32SV_cjs.e();
17562
+ hasher = chunkHUAXEKGI_cjs.e();
17197
17563
  // embedding is computationally expensive so cache content -> embeddings/chunks
17198
17564
  embeddingCache = /* @__PURE__ */ new Map();
17199
17565
  firstEmbed;
@@ -17574,13 +17940,17 @@ ${workingMemory}`;
17574
17940
  "Observational memory requires @mastra/core support for request-response-id-rotation. Please bump @mastra/core to a newer version."
17575
17941
  );
17576
17942
  }
17577
- const { ObservationalMemory: OMClass } = await import('./observational-memory-UGDENJPE-DM3C7ZXI.cjs');
17943
+ const { ObservationalMemory: OMClass } = await import('./observational-memory-SN7GKMHZ-WOK4TGDH.cjs');
17944
+ const onIndexObservations = this.hasRetrievalSearch(omConfig.retrieval) ? async (observation) => {
17945
+ await this.indexObservation(observation);
17946
+ } : void 0;
17578
17947
  return new OMClass({
17579
17948
  storage: memoryStore,
17580
17949
  scope: omConfig.scope,
17581
17950
  retrieval: omConfig.retrieval,
17582
17951
  shareTokenBudget: omConfig.shareTokenBudget,
17583
17952
  model: omConfig.model,
17953
+ onIndexObservations,
17584
17954
  observation: omConfig.observation ? {
17585
17955
  model: omConfig.observation.model,
17586
17956
  messageTokens: omConfig.observation.messageTokens,
@@ -17716,6 +18086,176 @@ Notes:
17716
18086
  const isMDWorkingMemory = !(`schema` in config.workingMemory) && (typeof config.workingMemory.template === `string` || config.workingMemory.template) && config.workingMemory;
17717
18087
  return Boolean(isMDWorkingMemory && isMDWorkingMemory.version === `vnext`);
17718
18088
  }
18089
+ getObservationEmbeddingIndexName(dimensions) {
18090
+ const defaultDimensions = 384;
18091
+ const usedDimensions = dimensions ?? defaultDimensions;
18092
+ const separator = this.vector?.indexSeparator ?? "_";
18093
+ return `memory${separator}observations${separator}${usedDimensions}`;
18094
+ }
18095
+ async createObservationEmbeddingIndex(dimensions) {
18096
+ const defaultDimensions = 384;
18097
+ const usedDimensions = dimensions ?? defaultDimensions;
18098
+ const indexName = this.getObservationEmbeddingIndexName(dimensions);
18099
+ if (typeof this.vector === `undefined`) {
18100
+ throw new Error(
18101
+ `Tried to create observation embedding index but no vector db is attached to this Memory instance.`
18102
+ );
18103
+ }
18104
+ await this.vector.createIndex({
18105
+ indexName,
18106
+ dimension: usedDimensions
18107
+ });
18108
+ return { indexName };
18109
+ }
18110
+ /**
18111
+ * Search observation groups across threads by semantic similarity.
18112
+ * Requires a vector store and embedder to be configured.
18113
+ */
18114
+ async searchMessages({
18115
+ query,
18116
+ resourceId,
18117
+ topK = 10,
18118
+ filter: filter32
18119
+ }) {
18120
+ if (!this.vector) {
18121
+ throw new Error("searchMessages requires a vector store. Configure vector and embedder on your Memory instance.");
18122
+ }
18123
+ const { embeddings, dimension } = await this.embedMessageContent(query);
18124
+ const { indexName } = await this.createObservationEmbeddingIndex(dimension);
18125
+ const vectorFilter = { resource_id: resourceId };
18126
+ if (filter32?.threadId) {
18127
+ vectorFilter.thread_id = filter32.threadId;
18128
+ }
18129
+ if (filter32?.observedAfter || filter32?.observedBefore) {
18130
+ vectorFilter.observed_at = {
18131
+ ...filter32.observedAfter ? { $gt: filter32.observedAfter.toISOString() } : {},
18132
+ ...filter32.observedBefore ? { $lt: filter32.observedBefore.toISOString() } : {}
18133
+ };
18134
+ }
18135
+ const queryResults = [];
18136
+ await Promise.all(
18137
+ embeddings.map(async (embedding) => {
18138
+ const results2 = await this.vector.query({
18139
+ indexName,
18140
+ queryVector: embedding,
18141
+ topK,
18142
+ filter: vectorFilter
18143
+ });
18144
+ for (const r of results2) {
18145
+ if (!r.metadata?.thread_id) {
18146
+ continue;
18147
+ }
18148
+ const groupId = typeof r.metadata.group_id === "string" ? r.metadata.group_id : void 0;
18149
+ if (!groupId) {
18150
+ continue;
18151
+ }
18152
+ queryResults.push({
18153
+ threadId: r.metadata.thread_id,
18154
+ score: r.score,
18155
+ groupId,
18156
+ range: typeof r.metadata.range === "string" ? r.metadata.range : void 0,
18157
+ text: typeof r.metadata.text === "string" ? r.metadata.text : void 0,
18158
+ observedAt: typeof r.metadata.observed_at === "string" || r.metadata.observed_at instanceof Date ? new Date(r.metadata.observed_at) : void 0
18159
+ });
18160
+ }
18161
+ })
18162
+ );
18163
+ const bestByGroup = /* @__PURE__ */ new Map();
18164
+ for (const result of queryResults) {
18165
+ if (!result.groupId) {
18166
+ continue;
18167
+ }
18168
+ const existing = bestByGroup.get(result.groupId);
18169
+ if (!existing || result.score > existing.score) {
18170
+ bestByGroup.set(result.groupId, result);
18171
+ }
18172
+ }
18173
+ const results = [...bestByGroup.values()].sort((a, b) => b.score - a.score);
18174
+ return { results };
18175
+ }
18176
+ /**
18177
+ * Index a single observation group into the observation vector store.
18178
+ */
18179
+ async indexObservation({
18180
+ text: text42,
18181
+ groupId,
18182
+ range,
18183
+ threadId,
18184
+ resourceId,
18185
+ observedAt
18186
+ }) {
18187
+ if (!this.vector || !this.embedder) return;
18188
+ const embedResult = await this.embedMessageContent(text42);
18189
+ if (embedResult.embeddings.length === 0 || embedResult.dimension === void 0) {
18190
+ return;
18191
+ }
18192
+ const { indexName } = await this.createObservationEmbeddingIndex(embedResult.dimension);
18193
+ await this.vector.upsert({
18194
+ indexName,
18195
+ vectors: embedResult.embeddings,
18196
+ metadata: embedResult.chunks.map((chunk) => ({
18197
+ group_id: groupId,
18198
+ range,
18199
+ thread_id: threadId,
18200
+ resource_id: resourceId,
18201
+ observed_at: observedAt?.toISOString(),
18202
+ text: chunk
18203
+ }))
18204
+ });
18205
+ }
18206
+ /**
18207
+ * Index a list of messages directly (without querying storage).
18208
+ * Used by observe-time indexing to vectorize newly-observed messages.
18209
+ */
18210
+ async indexMessagesList(messages) {
18211
+ if (!this.vector || !this.embedder) return;
18212
+ const embeddingData = [];
18213
+ let dimension;
18214
+ await Promise.all(
18215
+ messages.map(async (message) => {
18216
+ let textForEmbedding = null;
18217
+ if (message.content.content && typeof message.content.content === "string" && message.content.content.trim() !== "") {
18218
+ textForEmbedding = message.content.content;
18219
+ } else if (message.content.parts && message.content.parts.length > 0) {
18220
+ const joined = message.content.parts.filter((part) => part.type === "text").map((part) => part.text).join(" ").trim();
18221
+ if (joined) textForEmbedding = joined;
18222
+ }
18223
+ if (!textForEmbedding) return;
18224
+ const embedResult = await this.embedMessageContent(textForEmbedding);
18225
+ dimension = embedResult.dimension;
18226
+ embeddingData.push({
18227
+ embeddings: embedResult.embeddings,
18228
+ metadata: embedResult.chunks.map(() => ({
18229
+ message_id: message.id,
18230
+ thread_id: message.threadId,
18231
+ resource_id: message.resourceId
18232
+ }))
18233
+ });
18234
+ })
18235
+ );
18236
+ if (embeddingData.length > 0 && dimension !== void 0) {
18237
+ const { indexName } = await this.createEmbeddingIndex(dimension);
18238
+ const allVectors = [];
18239
+ const allMetadata = [];
18240
+ for (const data of embeddingData) {
18241
+ allVectors.push(...data.embeddings);
18242
+ allMetadata.push(...data.metadata);
18243
+ }
18244
+ await this.vector.upsert({
18245
+ indexName,
18246
+ vectors: allVectors,
18247
+ metadata: allMetadata
18248
+ });
18249
+ }
18250
+ }
18251
+ /**
18252
+ * Check whether retrieval search (vector-based) is enabled.
18253
+ * Returns true when `retrieval: { vector: true }` and Memory has vector + embedder configured.
18254
+ */
18255
+ hasRetrievalSearch(retrieval) {
18256
+ if (!retrieval || retrieval === true) return false;
18257
+ return !!retrieval.vector && !!this.vector && !!this.embedder;
18258
+ }
17719
18259
  listTools(config) {
17720
18260
  const mergedConfig = this.getMergedThreadConfig(config);
17721
18261
  const tools = {};
@@ -17723,8 +18263,9 @@ Notes:
17723
18263
  tools.updateWorkingMemory = this.isVNextWorkingMemoryConfig(mergedConfig) ? __experimental_updateWorkingMemoryToolVNext(mergedConfig) : updateWorkingMemoryTool(mergedConfig);
17724
18264
  }
17725
18265
  const omConfig = normalizeObservationalMemoryConfig(mergedConfig.observationalMemory);
17726
- if (omConfig?.retrieval && (omConfig.scope ?? "thread") === "thread") {
17727
- tools.recall = recallTool();
18266
+ if (omConfig?.retrieval) {
18267
+ const retrievalScope = typeof omConfig.retrieval === "object" ? omConfig.retrieval.scope ?? "resource" : "resource";
18268
+ tools.recall = recallTool(mergedConfig, { retrievalScope });
17728
18269
  }
17729
18270
  return tools;
17730
18271
  }
@@ -18287,7 +18828,7 @@ Notes:
18287
18828
  if (!effectiveConfig) return null;
18288
18829
  const engine = await this.omEngine;
18289
18830
  if (!engine) return null;
18290
- const { ObservationalMemoryProcessor } = await import('./observational-memory-UGDENJPE-DM3C7ZXI.cjs');
18831
+ const { ObservationalMemoryProcessor } = await import('./observational-memory-SN7GKMHZ-WOK4TGDH.cjs');
18291
18832
  return new ObservationalMemoryProcessor(engine, this);
18292
18833
  }
18293
18834
  };
@@ -29503,5 +30044,5 @@ exports.START_ASYNC_AGENT_BUILDER_ACTION_ROUTE = START_ASYNC_AGENT_BUILDER_ACTIO
29503
30044
  exports.STREAM_AGENT_BUILDER_ACTION_ROUTE = STREAM_AGENT_BUILDER_ACTION_ROUTE;
29504
30045
  exports.STREAM_LEGACY_AGENT_BUILDER_ACTION_ROUTE = STREAM_LEGACY_AGENT_BUILDER_ACTION_ROUTE;
29505
30046
  exports.agent_builder_exports = agent_builder_exports;
29506
- //# sourceMappingURL=chunk-NTZOZIKF.cjs.map
29507
- //# sourceMappingURL=chunk-NTZOZIKF.cjs.map
30047
+ //# sourceMappingURL=chunk-GDWCOWNR.cjs.map
30048
+ //# sourceMappingURL=chunk-GDWCOWNR.cjs.map