@mastra/memory 1.14.0 → 1.15.0-alpha.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.
package/dist/index.js CHANGED
@@ -15426,10 +15426,16 @@ var DefaultEmbedManyResult3 = class {
15426
15426
  };
15427
15427
  createIdGenerator3({ prefix: "aiobj", size: 24 });
15428
15428
  createIdGenerator3({ prefix: "aiobj", size: 24 });
15429
+ function getMessageParts(msg) {
15430
+ if (typeof msg.content === "string") return [];
15431
+ if (Array.isArray(msg.content)) return msg.content;
15432
+ const parts = msg.content?.parts;
15433
+ return Array.isArray(parts) ? parts : [];
15434
+ }
15429
15435
  function hasVisibleParts(msg) {
15430
15436
  if (typeof msg.content === "string") return msg.content.length > 0;
15431
- const parts = msg.content?.parts;
15432
- if (!parts || !Array.isArray(parts)) return false;
15437
+ const parts = getMessageParts(msg);
15438
+ if (parts.length === 0) return Boolean(msg.content?.content);
15433
15439
  return parts.some((p) => !p.type?.startsWith("data-"));
15434
15440
  }
15435
15441
  function parseRangeFormat(cursor) {
@@ -15690,13 +15696,13 @@ function lowDetailPartLimit(type) {
15690
15696
  if (type === "tool-result" || type === "tool-call") return AUTO_EXPAND_TOOL_TOKENS;
15691
15697
  return LOW_DETAIL_PART_TOKENS;
15692
15698
  }
15693
- function makePart(msg, partIndex, type, fullText, detail) {
15699
+ function makePart(msg, partIndex, type, fullText, detail, toolName) {
15694
15700
  if (detail === "high") {
15695
- return { messageId: msg.id, partIndex, role: msg.role, type, text: fullText, fullText };
15701
+ return { messageId: msg.id, partIndex, role: msg.role, type, text: fullText, fullText, toolName };
15696
15702
  }
15697
15703
  const hint = `recall cursor="${msg.id}" partIndex=${partIndex} detail="high"`;
15698
15704
  const { text: text4 } = truncateByTokens(fullText, lowDetailPartLimit(type), hint);
15699
- return { messageId: msg.id, partIndex, role: msg.role, type, text: text4, fullText };
15705
+ return { messageId: msg.id, partIndex, role: msg.role, type, text: text4, fullText, toolName };
15700
15706
  }
15701
15707
  function formatMessageParts(msg, detail) {
15702
15708
  const parts = [];
@@ -15704,32 +15710,73 @@ function formatMessageParts(msg, detail) {
15704
15710
  parts.push(makePart(msg, 0, "text", msg.content, detail));
15705
15711
  return parts;
15706
15712
  }
15707
- if (msg.content?.parts && Array.isArray(msg.content.parts)) {
15708
- for (let i = 0; i < msg.content.parts.length; i++) {
15709
- const part = msg.content.parts[i];
15713
+ const messageParts = getMessageParts(msg);
15714
+ if (messageParts.length > 0) {
15715
+ for (let i = 0; i < messageParts.length; i++) {
15716
+ const part = messageParts[i];
15710
15717
  const partType = part.type;
15711
15718
  if (partType === "text") {
15712
15719
  const text4 = part.text;
15713
- parts.push(makePart(msg, i, "text", text4, detail));
15720
+ if (text4) {
15721
+ parts.push(makePart(msg, i, "text", text4, detail));
15722
+ }
15714
15723
  } else if (partType === "tool-invocation") {
15715
15724
  const inv = part.toolInvocation;
15716
- if (inv.state === "result") {
15717
- const { value: resultValue } = resolveToolResultValue(
15718
- part,
15719
- inv.result
15720
- );
15721
- const resultStr = formatToolResultForObserver(resultValue, { maxTokens: HIGH_DETAIL_TOOL_RESULT_TOKENS });
15722
- const fullText = `[Tool Result: ${inv.toolName}]
15723
- ${resultStr}`;
15724
- parts.push(makePart(msg, i, "tool-result", fullText, detail));
15725
- } else {
15726
- const argsStr = detail === "low" ? "" : `
15725
+ if (inv?.toolName) {
15726
+ const hasArgs = inv.args != null;
15727
+ if (inv.state !== "partial-call" && hasArgs) {
15728
+ const argsStr = detail === "low" ? "" : `
15727
15729
  ${JSON.stringify(inv.args, null, 2)}`;
15728
- const fullText = `[Tool Call: ${inv.toolName}]${argsStr}`;
15729
- parts.push({ messageId: msg.id, partIndex: i, role: msg.role, type: "tool-call", text: fullText, fullText });
15730
+ const fullText = `[Tool Call: ${inv.toolName}]${argsStr}`;
15731
+ parts.push({
15732
+ messageId: msg.id,
15733
+ partIndex: i,
15734
+ role: msg.role,
15735
+ type: "tool-call",
15736
+ text: fullText,
15737
+ fullText,
15738
+ toolName: inv.toolName
15739
+ });
15740
+ }
15741
+ if (inv.state === "result") {
15742
+ const { value: resultValue } = resolveToolResultValue(
15743
+ part,
15744
+ inv.result
15745
+ );
15746
+ const resultStr = formatToolResultForObserver(resultValue, { maxTokens: HIGH_DETAIL_TOOL_RESULT_TOKENS });
15747
+ const fullText = `[Tool Result: ${inv.toolName}]
15748
+ ${resultStr}`;
15749
+ parts.push(makePart(msg, i, "tool-result", fullText, detail, inv.toolName));
15750
+ }
15751
+ }
15752
+ } else if (partType === "tool-call") {
15753
+ const toolName = part.toolName;
15754
+ if (toolName) {
15755
+ const rawArgs = part.input ?? part.args;
15756
+ const argsStr = detail === "low" || rawArgs == null ? "" : `
15757
+ ${typeof rawArgs === "string" ? rawArgs : JSON.stringify(rawArgs, null, 2)}`;
15758
+ const fullText = `[Tool Call: ${toolName}]${argsStr}`;
15759
+ parts.push({
15760
+ messageId: msg.id,
15761
+ partIndex: i,
15762
+ role: msg.role,
15763
+ type: "tool-call",
15764
+ text: fullText,
15765
+ fullText,
15766
+ toolName
15767
+ });
15768
+ }
15769
+ } else if (partType === "tool-result") {
15770
+ const toolName = part.toolName;
15771
+ if (toolName) {
15772
+ const rawResult = part.output ?? part.result;
15773
+ const resultStr = formatToolResultForObserver(rawResult, { maxTokens: HIGH_DETAIL_TOOL_RESULT_TOKENS });
15774
+ const fullText = `[Tool Result: ${toolName}]
15775
+ ${resultStr}`;
15776
+ parts.push(makePart(msg, i, "tool-result", fullText, detail, toolName));
15730
15777
  }
15731
15778
  } else if (partType === "reasoning") {
15732
- const reasoning = part.reasoning;
15779
+ const reasoning = part.reasoning ?? part.text;
15733
15780
  if (reasoning) {
15734
15781
  parts.push(makePart(msg, i, "reasoning", reasoning, detail));
15735
15782
  }
@@ -15867,7 +15914,7 @@ async function recallPart({
15867
15914
  `Message ${cursor} has no visible content (it may be an internal system message). Try a neighboring message ID instead.`
15868
15915
  );
15869
15916
  }
15870
- const target = allParts.find((p) => p.partIndex === partIndex);
15917
+ const target = [...allParts].reverse().find((p) => p.partIndex === partIndex);
15871
15918
  if (!target) {
15872
15919
  const availableIndices = allParts.map((p) => p.partIndex).join(", ");
15873
15920
  const highestVisiblePartIndex = Math.max(...allParts.map((p) => p.partIndex));
@@ -15920,6 +15967,8 @@ async function recallMessages({
15920
15967
  page = 1,
15921
15968
  limit = 20,
15922
15969
  detail = "low",
15970
+ partType,
15971
+ toolName,
15923
15972
  threadScope,
15924
15973
  maxTokens = DEFAULT_MAX_RESULT_TOKENS
15925
15974
  }) {
@@ -16009,12 +16058,18 @@ async function recallMessages({
16009
16058
  }
16010
16059
  const hasNextPage = isForward ? hasMore : pageIndex > 0;
16011
16060
  const hasPrevPage = isForward ? pageIndex > 0 : hasMore;
16012
- const allParts = [];
16061
+ let allParts = [];
16013
16062
  const timestamps = /* @__PURE__ */ new Map();
16014
16063
  for (const msg of messages) {
16015
16064
  timestamps.set(msg.id, msg.createdAt);
16016
16065
  allParts.push(...formatMessageParts(msg, detail));
16017
16066
  }
16067
+ if (toolName) {
16068
+ allParts = allParts.filter((p) => (p.type === "tool-call" || p.type === "tool-result") && p.toolName === toolName);
16069
+ }
16070
+ if (partType) {
16071
+ allParts = allParts.filter((p) => p.type === partType);
16072
+ }
16018
16073
  if (detail === "high" && allParts.length > 0) {
16019
16074
  const firstPart = allParts[0];
16020
16075
  const sameMsgParts = allParts.filter((p) => p.messageId === firstPart.messageId);
@@ -16051,8 +16106,9 @@ High detail returns 1 part at a time. To continue: ${hints.join(", or ")}.`;
16051
16106
  };
16052
16107
  }
16053
16108
  const rendered = renderFormattedParts(allParts, timestamps, { maxTokens });
16109
+ const emptyMessage = allParts.length === 0 ? partType || toolName ? "(no message parts matched the current filters)" : "(no visible message parts found for this page)" : "(no messages found)";
16054
16110
  return {
16055
- messages: rendered.text,
16111
+ messages: rendered.text || emptyMessage,
16056
16112
  count: messages.length,
16057
16113
  cursor,
16058
16114
  page: normalizedPage,
@@ -16071,6 +16127,9 @@ async function recallThreadFromStart({
16071
16127
  page = 1,
16072
16128
  limit = 20,
16073
16129
  detail = "low",
16130
+ partType,
16131
+ toolName,
16132
+ anchor = "start",
16074
16133
  maxTokens = DEFAULT_MAX_RESULT_TOKENS
16075
16134
  }) {
16076
16135
  if (!memory) {
@@ -16096,29 +16155,37 @@ async function recallThreadFromStart({
16096
16155
  resourceId,
16097
16156
  page: 0,
16098
16157
  perPage: fetchCount,
16099
- orderBy: { field: "createdAt", direction: "ASC" }
16158
+ orderBy: { field: "createdAt", direction: anchor === "end" ? "DESC" : "ASC" }
16100
16159
  });
16101
- const visibleMessages = result.messages.filter(hasVisibleParts);
16102
- const total = visibleMessages.length;
16160
+ const visibleMessages = anchor === "end" ? result.messages.slice(0, fetchCount).filter(hasVisibleParts).reverse() : result.messages.slice(0, fetchCount).filter(hasVisibleParts);
16103
16161
  const skip = pageIndex * normalizedLimit;
16104
- const hasMore = total > skip + normalizedLimit;
16105
16162
  const messages = visibleMessages.slice(skip, skip + normalizedLimit);
16106
- const allParts = [];
16163
+ const hasExtraMessage = visibleMessages.length > skip + messages.length;
16164
+ const hasNextPage = messages.length > 0 ? anchor === "end" ? pageIndex > 0 : hasExtraMessage : false;
16165
+ const hasPrevPage = messages.length > 0 ? anchor === "end" ? hasExtraMessage : pageIndex > 0 : pageIndex > 0;
16166
+ let allParts = [];
16107
16167
  const timestamps = /* @__PURE__ */ new Map();
16108
16168
  for (const msg of messages) {
16109
16169
  timestamps.set(msg.id, msg.createdAt);
16110
16170
  allParts.push(...formatMessageParts(msg, detail));
16111
16171
  }
16172
+ if (toolName) {
16173
+ allParts = allParts.filter((p) => (p.type === "tool-call" || p.type === "tool-result") && p.toolName === toolName);
16174
+ }
16175
+ if (partType) {
16176
+ allParts = allParts.filter((p) => p.type === partType);
16177
+ }
16112
16178
  const rendered = renderFormattedParts(allParts, timestamps, { maxTokens });
16179
+ const emptyMessage = messages.length === 0 ? pageIndex > 0 ? `(no messages found on page ${normalizedPage} for this thread)` : "(no messages in this thread)" : partType || toolName ? "(no message parts matched the current filters)" : "(no messages found)";
16113
16180
  return {
16114
- messages: rendered.text || "(no messages in this thread)",
16181
+ messages: rendered.text || emptyMessage,
16115
16182
  count: messages.length,
16116
16183
  cursor: messages[0]?.id || "",
16117
16184
  page: normalizedPage,
16118
16185
  limit: normalizedLimit,
16119
16186
  detail,
16120
- hasNextPage: hasMore,
16121
- hasPrevPage: pageIndex > 0,
16187
+ hasNextPage,
16188
+ hasPrevPage,
16122
16189
  truncated: rendered.truncated,
16123
16190
  tokenOffset: rendered.tokenOffset
16124
16191
  };
@@ -16135,7 +16202,9 @@ var recallTool = (_memoryConfig, options) => {
16135
16202
  mode: z.enum(["messages", "threads", "search"]).optional().describe(
16136
16203
  '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.'
16137
16204
  ),
16138
- threadId: z.string().min(1).optional().describe('Browse a different thread. Use mode="threads" first to discover thread IDs.'),
16205
+ threadId: z.string().min(1).optional().describe(
16206
+ 'Browse a different thread, or use "current" for the active thread. Use mode="threads" first to discover thread IDs.'
16207
+ ),
16139
16208
  before: z.string().optional().describe(
16140
16209
  '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").'
16141
16210
  ),
@@ -16151,6 +16220,9 @@ var recallTool = (_memoryConfig, options) => {
16151
16220
  cursor: z.string().min(1).optional().describe(
16152
16221
  '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.'
16153
16222
  ),
16223
+ anchor: z.enum(["start", "end"]).optional().describe(
16224
+ 'For mode="messages" without a cursor, page from the start (oldest-first) or end (newest-first) of the thread. Defaults to "start".'
16225
+ ),
16154
16226
  page: z.number().int().min(-50).max(50).optional().describe(
16155
16227
  "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."
16156
16228
  ),
@@ -16158,6 +16230,10 @@ var recallTool = (_memoryConfig, options) => {
16158
16230
  detail: z.enum(["low", "high"]).optional().describe(
16159
16231
  'Detail level for messages. "low" (default) returns truncated text and tool names. "high" returns full content with tool args/results.'
16160
16232
  ),
16233
+ partType: z.enum(["text", "tool-call", "tool-result", "reasoning", "image", "file"]).optional().describe('Filter results to only include parts of this type. Only applies to mode="messages".'),
16234
+ toolName: z.string().min(1).optional().describe(
16235
+ 'Filter results to only include tool-call and tool-result parts matching this tool name. Only applies to mode="messages".'
16236
+ ),
16161
16237
  partIndex: z.number().int().min(0).optional().describe(
16162
16238
  "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."
16163
16239
  )
@@ -16167,9 +16243,12 @@ var recallTool = (_memoryConfig, options) => {
16167
16243
  query,
16168
16244
  cursor,
16169
16245
  threadId: explicitThreadId,
16246
+ anchor,
16170
16247
  page,
16171
16248
  limit,
16172
16249
  detail,
16250
+ partType,
16251
+ toolName,
16173
16252
  partIndex,
16174
16253
  before,
16175
16254
  after
@@ -16177,9 +16256,13 @@ var recallTool = (_memoryConfig, options) => {
16177
16256
  const memory = context2?.memory;
16178
16257
  const currentThreadId = context2?.agent?.threadId;
16179
16258
  const resourceId = context2?.agent?.resourceId;
16259
+ const resolvedExplicitThreadId = explicitThreadId === "current" ? currentThreadId : explicitThreadId;
16180
16260
  if (!memory) {
16181
16261
  throw new Error("Memory instance is required for recall");
16182
16262
  }
16263
+ if (explicitThreadId === "current" && !currentThreadId) {
16264
+ throw new Error("Could not resolve current thread.");
16265
+ }
16183
16266
  if (mode === "search") {
16184
16267
  if (!query) {
16185
16268
  throw new Error('query is required for mode="search"');
@@ -16195,11 +16278,12 @@ var recallTool = (_memoryConfig, options) => {
16195
16278
  topK: limit ?? 10,
16196
16279
  before,
16197
16280
  after,
16198
- threadScope: !isResourceScope ? currentThreadId || void 0 : void 0
16281
+ threadScope: !isResourceScope ? currentThreadId || void 0 : resolvedExplicitThreadId || void 0
16199
16282
  });
16200
16283
  }
16201
16284
  if (mode === "threads") {
16202
- if (!isResourceScope) {
16285
+ const requestedCurrentThread = explicitThreadId === "current";
16286
+ if (!isResourceScope || requestedCurrentThread) {
16203
16287
  if (!currentThreadId || !memory.getThreadById) {
16204
16288
  return { error: "Could not resolve current thread." };
16205
16289
  }
@@ -16207,6 +16291,9 @@ var recallTool = (_memoryConfig, options) => {
16207
16291
  if (!thread) {
16208
16292
  return { error: "Could not resolve current thread." };
16209
16293
  }
16294
+ if (isResourceScope && resourceId && thread.resourceId !== resourceId) {
16295
+ throw new Error("Thread does not belong to the active resource");
16296
+ }
16210
16297
  return {
16211
16298
  threads: `- **${thread.title || "(untitled)"}** \u2190 current
16212
16299
  id: ${thread.id}
@@ -16229,7 +16316,7 @@ var recallTool = (_memoryConfig, options) => {
16229
16316
  after
16230
16317
  });
16231
16318
  }
16232
- const hasExplicitThreadId = typeof explicitThreadId === "string" && explicitThreadId.length > 0;
16319
+ const hasExplicitThreadId = typeof resolvedExplicitThreadId === "string" && resolvedExplicitThreadId.length > 0;
16233
16320
  const hasCursor = typeof cursor === "string" && cursor.length > 0;
16234
16321
  if (!hasExplicitThreadId && !hasCursor) {
16235
16322
  throw new Error('Either cursor or threadId is required for mode="messages"');
@@ -16246,7 +16333,7 @@ var recallTool = (_memoryConfig, options) => {
16246
16333
  if (!memory.getThreadById) {
16247
16334
  throw new Error("Memory instance cannot verify thread access for recall");
16248
16335
  }
16249
- const thread = await memory.getThreadById({ threadId: explicitThreadId });
16336
+ const thread = await memory.getThreadById({ threadId: resolvedExplicitThreadId });
16250
16337
  if (!thread || thread.resourceId !== resourceId) {
16251
16338
  throw new Error("Thread does not belong to the active resource");
16252
16339
  }
@@ -16287,7 +16374,10 @@ var recallTool = (_memoryConfig, options) => {
16287
16374
  resourceId: isResourceScope ? resourceId : void 0,
16288
16375
  page: page ?? 1,
16289
16376
  limit: limit ?? 20,
16290
- detail: detail ?? "low"
16377
+ detail: detail ?? "low",
16378
+ partType,
16379
+ toolName,
16380
+ anchor: anchor ?? "start"
16291
16381
  });
16292
16382
  }
16293
16383
  if (partIndex !== void 0 && partIndex !== null) {
@@ -16308,6 +16398,8 @@ var recallTool = (_memoryConfig, options) => {
16308
16398
  page,
16309
16399
  limit,
16310
16400
  detail: detail ?? "low",
16401
+ partType,
16402
+ toolName,
16311
16403
  threadScope
16312
16404
  });
16313
16405
  }