@mastra/memory 1.0.0-beta.8 → 1.0.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
@@ -15,7 +15,7 @@ import { createTool } from '@mastra/core/tools';
15
15
  import { convertSchemaToZod } from '@mastra/schema-compat';
16
16
  export { MessageHistory, SemanticRecall, WorkingMemory } from '@mastra/core/processors';
17
17
 
18
- // ../_vendored/ai_v4/dist/chunk-IHGBB4AL.js
18
+ // ../_vendored/ai_v4/dist/chunk-OPIPXJLE.js
19
19
  var __create = Object.create;
20
20
  var __defProp = Object.defineProperty;
21
21
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -377,7 +377,7 @@ function getErrorMessage2(error) {
377
377
  function isAbortError(error) {
378
378
  return error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError");
379
379
  }
380
- var validatorSymbol = Symbol.for("vercel.ai.validator");
380
+ var validatorSymbol = /* @__PURE__ */ Symbol.for("vercel.ai.validator");
381
381
  function validator(validate) {
382
382
  return { [validatorSymbol]: true, validate };
383
383
  }
@@ -435,7 +435,7 @@ function safeParseJSON({
435
435
  };
436
436
  }
437
437
  }
438
- var ignoreOverride = Symbol("Let zodToJsonSchema decide on which parser to use");
438
+ var ignoreOverride = /* @__PURE__ */ Symbol("Let zodToJsonSchema decide on which parser to use");
439
439
  var defaultOptions = {
440
440
  name: void 0,
441
441
  $refStrategy: "root",
@@ -2243,7 +2243,7 @@ function zodSchema(zodSchema22, options) {
2243
2243
  }
2244
2244
  );
2245
2245
  }
2246
- var schemaSymbol = Symbol.for("vercel.ai.schema");
2246
+ var schemaSymbol = /* @__PURE__ */ Symbol.for("vercel.ai.schema");
2247
2247
  function jsonSchema(jsonSchema22, {
2248
2248
  validate
2249
2249
  } = {}) {
@@ -2330,7 +2330,7 @@ function _makeCompatibilityCheck(ownVersion) {
2330
2330
  }
2331
2331
  var isCompatible = _makeCompatibilityCheck(VERSION);
2332
2332
  var major = VERSION.split(".")[0];
2333
- var GLOBAL_OPENTELEMETRY_API_KEY = Symbol.for("opentelemetry.js.api." + major);
2333
+ var GLOBAL_OPENTELEMETRY_API_KEY = /* @__PURE__ */ Symbol.for("opentelemetry.js.api." + major);
2334
2334
  var _global = _globalThis;
2335
2335
  function registerGlobal(type, instance, diag, allowOverride) {
2336
2336
  var _a172;
@@ -4693,7 +4693,7 @@ function secureJsonParse(text4) {
4693
4693
  Error.stackTraceLimit = stackTraceLimit;
4694
4694
  }
4695
4695
  }
4696
- var validatorSymbol2 = Symbol.for("vercel.ai.validator");
4696
+ var validatorSymbol2 = /* @__PURE__ */ Symbol.for("vercel.ai.validator");
4697
4697
  function validator2(validate) {
4698
4698
  return { [validatorSymbol2]: true, validate };
4699
4699
  }
@@ -5007,7 +5007,7 @@ var getRelativePath2 = (pathA, pathB) => {
5007
5007
  }
5008
5008
  return [(pathA.length - i).toString(), ...pathB.slice(i)].join("/");
5009
5009
  };
5010
- var ignoreOverride2 = Symbol(
5010
+ var ignoreOverride2 = /* @__PURE__ */ Symbol(
5011
5011
  "Let zodToJsonSchema decide on which parser to use"
5012
5012
  );
5013
5013
  var defaultOptions2 = {
@@ -6127,7 +6127,7 @@ function zodSchema2(zodSchema22, options) {
6127
6127
  return zod3Schema(zodSchema22);
6128
6128
  }
6129
6129
  }
6130
- var schemaSymbol2 = Symbol.for("vercel.ai.schema");
6130
+ var schemaSymbol2 = /* @__PURE__ */ Symbol.for("vercel.ai.schema");
6131
6131
  function jsonSchema2(jsonSchema22, {
6132
6132
  validate
6133
6133
  } = {}) {
@@ -6182,7 +6182,7 @@ var require_get_context = __commonJS({
6182
6182
  getContext: () => getContext3
6183
6183
  });
6184
6184
  module.exports = __toCommonJS(get_context_exports);
6185
- var SYMBOL_FOR_REQ_CONTEXT = Symbol.for("@vercel/request-context");
6185
+ var SYMBOL_FOR_REQ_CONTEXT = /* @__PURE__ */ Symbol.for("@vercel/request-context");
6186
6186
  function getContext3() {
6187
6187
  const fromSymbol = globalThis;
6188
6188
  return fromSymbol[SYMBOL_FOR_REQ_CONTEXT]?.get?.() ?? {};
@@ -7211,7 +7211,7 @@ function _makeCompatibilityCheck2(ownVersion) {
7211
7211
  }
7212
7212
  var isCompatible2 = _makeCompatibilityCheck2(VERSION22);
7213
7213
  var major2 = VERSION22.split(".")[0];
7214
- var GLOBAL_OPENTELEMETRY_API_KEY2 = Symbol.for("opentelemetry.js.api." + major2);
7214
+ var GLOBAL_OPENTELEMETRY_API_KEY2 = /* @__PURE__ */ Symbol.for("opentelemetry.js.api." + major2);
7215
7215
  var _global2 = _globalThis2;
7216
7216
  function registerGlobal2(type, instance, diag, allowOverride) {
7217
7217
  var _a162;
@@ -9657,7 +9657,7 @@ function addAdditionalPropertiesToJsonSchema(jsonSchema22) {
9657
9657
  }
9658
9658
  return jsonSchema22;
9659
9659
  }
9660
- var ignoreOverride3 = Symbol(
9660
+ var ignoreOverride3 = /* @__PURE__ */ Symbol(
9661
9661
  "Let zodToJsonSchema decide on which parser to use"
9662
9662
  );
9663
9663
  var defaultOptions3 = {
@@ -10739,7 +10739,7 @@ var zod3ToJsonSchema = (schema, options) => {
10739
10739
  combined.$schema = "http://json-schema.org/draft-07/schema#";
10740
10740
  return combined;
10741
10741
  };
10742
- var schemaSymbol3 = Symbol.for("vercel.ai.schema");
10742
+ var schemaSymbol3 = /* @__PURE__ */ Symbol.for("vercel.ai.schema");
10743
10743
  function lazySchema(createSchema) {
10744
10744
  let schema;
10745
10745
  return () => {
@@ -11140,7 +11140,7 @@ var require_get_context2 = __commonJS$1({
11140
11140
  getContext: () => getContext3
11141
11141
  });
11142
11142
  module.exports = __toCommonJS(get_context_exports);
11143
- var SYMBOL_FOR_REQ_CONTEXT = Symbol.for("@vercel/request-context");
11143
+ var SYMBOL_FOR_REQ_CONTEXT = /* @__PURE__ */ Symbol.for("@vercel/request-context");
11144
11144
  function getContext3() {
11145
11145
  const fromSymbol = globalThis;
11146
11146
  return fromSymbol[SYMBOL_FOR_REQ_CONTEXT]?.get?.() ?? {};
@@ -12173,7 +12173,7 @@ function _makeCompatibilityCheck3(ownVersion) {
12173
12173
  }
12174
12174
  var isCompatible3 = _makeCompatibilityCheck3(VERSION23);
12175
12175
  var major3 = VERSION23.split(".")[0];
12176
- var GLOBAL_OPENTELEMETRY_API_KEY3 = Symbol.for("opentelemetry.js.api." + major3);
12176
+ var GLOBAL_OPENTELEMETRY_API_KEY3 = /* @__PURE__ */ Symbol.for("opentelemetry.js.api." + major3);
12177
12177
  var _global3 = _globalThis3;
12178
12178
  function registerGlobal3(type, instance, diag, allowOverride) {
12179
12179
  var _a146;
@@ -14408,6 +14408,9 @@ var DefaultEmbedManyResult3 = class {
14408
14408
  createIdGenerator3({ prefix: "aiobj", size: 24 });
14409
14409
  createIdGenerator3({ prefix: "aiobj", size: 24 });
14410
14410
  function deepMergeWorkingMemory(existing, update) {
14411
+ if (!update || typeof update !== "object" || Object.keys(update).length === 0) {
14412
+ return existing && typeof existing === "object" ? { ...existing } : {};
14413
+ }
14411
14414
  if (!existing || typeof existing !== "object") {
14412
14415
  return update;
14413
14416
  }
@@ -14481,6 +14484,9 @@ var updateWorkingMemoryTool = (memoryConfig) => {
14481
14484
  existingData = null;
14482
14485
  }
14483
14486
  }
14487
+ if (inputData.memory === void 0 || inputData.memory === null) {
14488
+ return { success: true, message: "No memory data provided, existing memory unchanged." };
14489
+ }
14484
14490
  let newData;
14485
14491
  if (typeof inputData.memory === "string") {
14486
14492
  try {
@@ -14693,9 +14699,9 @@ var Memory = class extends MastraMemory {
14693
14699
  const memoryStore = await this.getMemoryStore();
14694
14700
  return memoryStore.getThreadById({ threadId });
14695
14701
  }
14696
- async listThreadsByResourceId(args) {
14702
+ async listThreads(args) {
14697
14703
  const memoryStore = await this.getMemoryStore();
14698
- return memoryStore.listThreadsByResourceId(args);
14704
+ return memoryStore.listThreads(args);
14699
14705
  }
14700
14706
  async handleWorkingMemoryFromMetadata({
14701
14707
  workingMemory,
@@ -15223,15 +15229,102 @@ ${template.content !== this.defaultWorkingMemoryTemplate ? `- Only store informa
15223
15229
  return {};
15224
15230
  }
15225
15231
  /**
15226
- * Updates the metadata of a list of messages
15227
- * @param messages - The list of messages to update
15232
+ * Updates a list of messages and syncs the vector database for semantic recall.
15233
+ * When message content is updated, the corresponding vector embeddings are also updated
15234
+ * to ensure semantic recall stays in sync with the message content.
15235
+ *
15236
+ * @param messages - The list of messages to update (must include id, can include partial content)
15237
+ * @param memoryConfig - Optional memory configuration to determine if semantic recall is enabled
15228
15238
  * @returns The list of updated messages
15229
15239
  */
15230
15240
  async updateMessages({
15231
- messages
15241
+ messages,
15242
+ memoryConfig
15232
15243
  }) {
15233
15244
  if (messages.length === 0) return [];
15234
15245
  const memoryStore = await this.getMemoryStore();
15246
+ const config = this.getMergedThreadConfig(memoryConfig);
15247
+ if (this.vector && config.semanticRecall) {
15248
+ const messagesWithContent = messages.filter((m) => m.content !== void 0);
15249
+ if (messagesWithContent.length > 0) {
15250
+ const existingMessagesResult = await memoryStore.listMessagesById({
15251
+ messageIds: messagesWithContent.map((m) => m.id)
15252
+ });
15253
+ const existingMessagesMap = new Map(existingMessagesResult.messages.map((m) => [m.id, m]));
15254
+ const embeddingData = [];
15255
+ let dimension;
15256
+ const messageIdsWithNewEmbeddings = /* @__PURE__ */ new Set();
15257
+ const messageIdsWithClearedContent = /* @__PURE__ */ new Set();
15258
+ await Promise.all(
15259
+ messagesWithContent.map(async (message) => {
15260
+ const existingMessage = existingMessagesMap.get(message.id);
15261
+ if (!existingMessage) return;
15262
+ let textForEmbedding = null;
15263
+ const content = message.content;
15264
+ if (content) {
15265
+ if ("content" in content && content.content && typeof content.content === "string" && content.content.trim() !== "") {
15266
+ textForEmbedding = content.content;
15267
+ } else if ("parts" in content && content.parts && Array.isArray(content.parts) && content.parts.length > 0) {
15268
+ const joined = content.parts.filter((part) => part?.type === "text").map((part) => part.text).join(" ").trim();
15269
+ if (joined) textForEmbedding = joined;
15270
+ }
15271
+ }
15272
+ if (textForEmbedding) {
15273
+ const result = await this.embedMessageContent(textForEmbedding);
15274
+ dimension = result.dimension;
15275
+ embeddingData.push({
15276
+ embeddings: result.embeddings,
15277
+ metadata: result.chunks.map(() => ({
15278
+ message_id: message.id,
15279
+ thread_id: existingMessage.threadId,
15280
+ resource_id: existingMessage.resourceId
15281
+ }))
15282
+ });
15283
+ messageIdsWithNewEmbeddings.add(message.id);
15284
+ } else {
15285
+ messageIdsWithClearedContent.add(message.id);
15286
+ }
15287
+ })
15288
+ );
15289
+ const messageIdsNeedingDeletion = /* @__PURE__ */ new Set([...messageIdsWithClearedContent, ...messageIdsWithNewEmbeddings]);
15290
+ if (messageIdsNeedingDeletion.size > 0) {
15291
+ try {
15292
+ const indexes = await this.vector.listIndexes();
15293
+ const memoryIndexes = indexes.filter((name21) => name21.startsWith("memory_messages"));
15294
+ for (const indexName of memoryIndexes) {
15295
+ for (const messageId of messageIdsNeedingDeletion) {
15296
+ try {
15297
+ await this.vector.deleteVectors({
15298
+ indexName,
15299
+ filter: { message_id: messageId }
15300
+ });
15301
+ } catch {
15302
+ this.logger.debug(
15303
+ `No existing vectors found for message ${messageId} in ${indexName}, skipping delete`
15304
+ );
15305
+ }
15306
+ }
15307
+ }
15308
+ } catch {
15309
+ this.logger.debug(`No memory indexes found to delete from`);
15310
+ }
15311
+ }
15312
+ if (embeddingData.length > 0 && dimension !== void 0) {
15313
+ const { indexName } = await this.createEmbeddingIndex(dimension, config);
15314
+ const allVectors = [];
15315
+ const allMetadata = [];
15316
+ for (const data of embeddingData) {
15317
+ allVectors.push(...data.embeddings);
15318
+ allMetadata.push(...data.metadata);
15319
+ }
15320
+ await this.vector.upsert({
15321
+ indexName,
15322
+ vectors: allVectors,
15323
+ metadata: allMetadata
15324
+ });
15325
+ }
15326
+ }
15327
+ }
15235
15328
  return memoryStore.updateMessages({ messages });
15236
15329
  }
15237
15330
  /**
@@ -15265,6 +15358,233 @@ ${template.content !== this.defaultWorkingMemoryTemplate ? `- Only store informa
15265
15358
  const memoryStore = await this.getMemoryStore();
15266
15359
  await memoryStore.deleteMessages(messageIds);
15267
15360
  }
15361
+ /**
15362
+ * Clone a thread and its messages to create a new independent thread.
15363
+ * The cloned thread will have metadata tracking its source.
15364
+ *
15365
+ * If semantic recall is enabled, the cloned messages will also be embedded
15366
+ * and added to the vector store for semantic search.
15367
+ *
15368
+ * @param args - Clone configuration options
15369
+ * @param args.sourceThreadId - ID of the thread to clone
15370
+ * @param args.newThreadId - ID for the new cloned thread (if not provided, a random UUID will be generated)
15371
+ * @param args.resourceId - Resource ID for the new thread (defaults to source thread's resourceId)
15372
+ * @param args.title - Title for the new cloned thread
15373
+ * @param args.metadata - Additional metadata to merge with clone metadata
15374
+ * @param args.options - Options for filtering which messages to include
15375
+ * @param args.options.messageLimit - Maximum number of messages to copy (from most recent)
15376
+ * @param args.options.messageFilter - Filter messages by date range or specific IDs
15377
+ * @param memoryConfig - Optional memory configuration override
15378
+ * @returns The newly created thread and the cloned messages
15379
+ *
15380
+ * @example
15381
+ * ```typescript
15382
+ * // Clone entire thread
15383
+ * const { thread, clonedMessages } = await memory.cloneThread({
15384
+ * sourceThreadId: 'thread-123',
15385
+ * });
15386
+ *
15387
+ * // Clone with custom ID
15388
+ * const { thread, clonedMessages } = await memory.cloneThread({
15389
+ * sourceThreadId: 'thread-123',
15390
+ * newThreadId: 'my-custom-thread-id',
15391
+ * });
15392
+ *
15393
+ * // Clone with message limit
15394
+ * const { thread, clonedMessages } = await memory.cloneThread({
15395
+ * sourceThreadId: 'thread-123',
15396
+ * title: 'My cloned conversation',
15397
+ * options: {
15398
+ * messageLimit: 10, // Only clone last 10 messages
15399
+ * },
15400
+ * });
15401
+ *
15402
+ * // Clone with date filter
15403
+ * const { thread, clonedMessages } = await memory.cloneThread({
15404
+ * sourceThreadId: 'thread-123',
15405
+ * options: {
15406
+ * messageFilter: {
15407
+ * startDate: new Date('2024-01-01'),
15408
+ * endDate: new Date('2024-06-01'),
15409
+ * },
15410
+ * },
15411
+ * });
15412
+ * ```
15413
+ */
15414
+ async cloneThread(args, memoryConfig) {
15415
+ const memoryStore = await this.getMemoryStore();
15416
+ const result = await memoryStore.cloneThread(args);
15417
+ const config = this.getMergedThreadConfig(memoryConfig);
15418
+ if (this.vector && config.semanticRecall && result.clonedMessages.length > 0) {
15419
+ await this.embedClonedMessages(result.clonedMessages, config);
15420
+ }
15421
+ return result;
15422
+ }
15423
+ /**
15424
+ * Embed cloned messages for semantic recall.
15425
+ * This is similar to the embedding logic in saveMessages but operates on already-saved messages.
15426
+ */
15427
+ async embedClonedMessages(messages, config) {
15428
+ if (!this.vector || !this.embedder) {
15429
+ return;
15430
+ }
15431
+ const embeddingData = [];
15432
+ let dimension;
15433
+ await Promise.all(
15434
+ messages.map(async (message) => {
15435
+ let textForEmbedding = null;
15436
+ if (message.content?.content && typeof message.content.content === "string" && message.content.content.trim() !== "") {
15437
+ textForEmbedding = message.content.content;
15438
+ } else if (message.content?.parts && message.content.parts.length > 0) {
15439
+ const joined = message.content.parts.filter((part) => part.type === "text").map((part) => part.text).join(" ").trim();
15440
+ if (joined) textForEmbedding = joined;
15441
+ }
15442
+ if (!textForEmbedding) return;
15443
+ const result = await this.embedMessageContent(textForEmbedding);
15444
+ dimension = result.dimension;
15445
+ embeddingData.push({
15446
+ embeddings: result.embeddings,
15447
+ metadata: result.chunks.map(() => ({
15448
+ message_id: message.id,
15449
+ thread_id: message.threadId,
15450
+ resource_id: message.resourceId
15451
+ }))
15452
+ });
15453
+ })
15454
+ );
15455
+ if (embeddingData.length > 0 && dimension !== void 0) {
15456
+ const { indexName } = await this.createEmbeddingIndex(dimension, config);
15457
+ const allVectors = [];
15458
+ const allMetadata = [];
15459
+ for (const data of embeddingData) {
15460
+ allVectors.push(...data.embeddings);
15461
+ allMetadata.push(...data.metadata);
15462
+ }
15463
+ await this.vector.upsert({
15464
+ indexName,
15465
+ vectors: allVectors,
15466
+ metadata: allMetadata
15467
+ });
15468
+ }
15469
+ }
15470
+ /**
15471
+ * Get the clone metadata from a thread if it was cloned from another thread.
15472
+ *
15473
+ * @param thread - The thread to check
15474
+ * @returns The clone metadata if the thread is a clone, null otherwise
15475
+ *
15476
+ * @example
15477
+ * ```typescript
15478
+ * const thread = await memory.getThreadById({ threadId: 'thread-123' });
15479
+ * const cloneInfo = memory.getCloneMetadata(thread);
15480
+ * if (cloneInfo) {
15481
+ * console.log(`This thread was cloned from ${cloneInfo.sourceThreadId}`);
15482
+ * }
15483
+ * ```
15484
+ */
15485
+ getCloneMetadata(thread) {
15486
+ if (!thread?.metadata?.clone) {
15487
+ return null;
15488
+ }
15489
+ return thread.metadata.clone;
15490
+ }
15491
+ /**
15492
+ * Check if a thread is a clone of another thread.
15493
+ *
15494
+ * @param thread - The thread to check
15495
+ * @returns True if the thread is a clone, false otherwise
15496
+ *
15497
+ * @example
15498
+ * ```typescript
15499
+ * const thread = await memory.getThreadById({ threadId: 'thread-123' });
15500
+ * if (memory.isClone(thread)) {
15501
+ * console.log('This is a cloned thread');
15502
+ * }
15503
+ * ```
15504
+ */
15505
+ isClone(thread) {
15506
+ return this.getCloneMetadata(thread) !== null;
15507
+ }
15508
+ /**
15509
+ * Get the source thread that a cloned thread was created from.
15510
+ *
15511
+ * @param threadId - ID of the cloned thread
15512
+ * @returns The source thread if found, null if the thread is not a clone or source doesn't exist
15513
+ *
15514
+ * @example
15515
+ * ```typescript
15516
+ * const sourceThread = await memory.getSourceThread('cloned-thread-123');
15517
+ * if (sourceThread) {
15518
+ * console.log(`Original thread: ${sourceThread.title}`);
15519
+ * }
15520
+ * ```
15521
+ */
15522
+ async getSourceThread(threadId) {
15523
+ const thread = await this.getThreadById({ threadId });
15524
+ const cloneMetadata = this.getCloneMetadata(thread);
15525
+ if (!cloneMetadata) {
15526
+ return null;
15527
+ }
15528
+ return this.getThreadById({ threadId: cloneMetadata.sourceThreadId });
15529
+ }
15530
+ /**
15531
+ * List all threads that were cloned from a specific source thread.
15532
+ *
15533
+ * @param sourceThreadId - ID of the source thread
15534
+ * @param resourceId - Optional resource ID to filter by
15535
+ * @returns Array of threads that are clones of the source thread
15536
+ *
15537
+ * @example
15538
+ * ```typescript
15539
+ * const clones = await memory.listClones('original-thread-123', 'user-456');
15540
+ * console.log(`Found ${clones.length} clones of this thread`);
15541
+ * ```
15542
+ */
15543
+ async listClones(sourceThreadId, resourceId) {
15544
+ let targetResourceId = resourceId;
15545
+ if (!targetResourceId) {
15546
+ const sourceThread = await this.getThreadById({ threadId: sourceThreadId });
15547
+ if (!sourceThread) {
15548
+ return [];
15549
+ }
15550
+ targetResourceId = sourceThread.resourceId;
15551
+ }
15552
+ const { threads } = await this.listThreads({
15553
+ filter: { resourceId: targetResourceId },
15554
+ perPage: false
15555
+ // Get all threads
15556
+ });
15557
+ return threads.filter((thread) => {
15558
+ const cloneMetadata = this.getCloneMetadata(thread);
15559
+ return cloneMetadata?.sourceThreadId === sourceThreadId;
15560
+ });
15561
+ }
15562
+ /**
15563
+ * Get the clone history chain for a thread (all ancestors back to the original).
15564
+ *
15565
+ * @param threadId - ID of the thread to get history for
15566
+ * @returns Array of threads from oldest ancestor to the given thread (inclusive)
15567
+ *
15568
+ * @example
15569
+ * ```typescript
15570
+ * const history = await memory.getCloneHistory('deeply-cloned-thread');
15571
+ * // Returns: [originalThread, firstClone, secondClone, deeplyClonedThread]
15572
+ * ```
15573
+ */
15574
+ async getCloneHistory(threadId) {
15575
+ const history = [];
15576
+ let currentThreadId = threadId;
15577
+ while (currentThreadId) {
15578
+ const thread = await this.getThreadById({ threadId: currentThreadId });
15579
+ if (!thread) {
15580
+ break;
15581
+ }
15582
+ history.unshift(thread);
15583
+ const cloneMetadata = this.getCloneMetadata(thread);
15584
+ currentThreadId = cloneMetadata?.sourceThreadId ?? null;
15585
+ }
15586
+ return history;
15587
+ }
15268
15588
  };
15269
15589
 
15270
15590
  export { Memory, deepMergeWorkingMemory };