@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.cjs CHANGED
@@ -38,7 +38,7 @@ function _interopNamespace(e) {
38
38
  var z4__namespace = /*#__PURE__*/_interopNamespace(z4);
39
39
  var xxhash__default = /*#__PURE__*/_interopDefault(xxhash);
40
40
 
41
- // ../_vendored/ai_v4/dist/chunk-IHGBB4AL.js
41
+ // ../_vendored/ai_v4/dist/chunk-OPIPXJLE.js
42
42
  var __create = Object.create;
43
43
  var __defProp = Object.defineProperty;
44
44
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -400,7 +400,7 @@ function getErrorMessage2(error) {
400
400
  function isAbortError(error) {
401
401
  return error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError");
402
402
  }
403
- var validatorSymbol = Symbol.for("vercel.ai.validator");
403
+ var validatorSymbol = /* @__PURE__ */ Symbol.for("vercel.ai.validator");
404
404
  function validator(validate) {
405
405
  return { [validatorSymbol]: true, validate };
406
406
  }
@@ -458,7 +458,7 @@ function safeParseJSON({
458
458
  };
459
459
  }
460
460
  }
461
- var ignoreOverride = Symbol("Let zodToJsonSchema decide on which parser to use");
461
+ var ignoreOverride = /* @__PURE__ */ Symbol("Let zodToJsonSchema decide on which parser to use");
462
462
  var defaultOptions = {
463
463
  name: void 0,
464
464
  $refStrategy: "root",
@@ -2266,7 +2266,7 @@ function zodSchema(zodSchema22, options) {
2266
2266
  }
2267
2267
  );
2268
2268
  }
2269
- var schemaSymbol = Symbol.for("vercel.ai.schema");
2269
+ var schemaSymbol = /* @__PURE__ */ Symbol.for("vercel.ai.schema");
2270
2270
  function jsonSchema(jsonSchema22, {
2271
2271
  validate
2272
2272
  } = {}) {
@@ -2353,7 +2353,7 @@ function _makeCompatibilityCheck(ownVersion) {
2353
2353
  }
2354
2354
  var isCompatible = _makeCompatibilityCheck(VERSION);
2355
2355
  var major = VERSION.split(".")[0];
2356
- var GLOBAL_OPENTELEMETRY_API_KEY = Symbol.for("opentelemetry.js.api." + major);
2356
+ var GLOBAL_OPENTELEMETRY_API_KEY = /* @__PURE__ */ Symbol.for("opentelemetry.js.api." + major);
2357
2357
  var _global = _globalThis;
2358
2358
  function registerGlobal(type, instance, diag, allowOverride) {
2359
2359
  var _a172;
@@ -4716,7 +4716,7 @@ function secureJsonParse(text4) {
4716
4716
  Error.stackTraceLimit = stackTraceLimit;
4717
4717
  }
4718
4718
  }
4719
- var validatorSymbol2 = Symbol.for("vercel.ai.validator");
4719
+ var validatorSymbol2 = /* @__PURE__ */ Symbol.for("vercel.ai.validator");
4720
4720
  function validator2(validate) {
4721
4721
  return { [validatorSymbol2]: true, validate };
4722
4722
  }
@@ -5030,7 +5030,7 @@ var getRelativePath2 = (pathA, pathB) => {
5030
5030
  }
5031
5031
  return [(pathA.length - i).toString(), ...pathB.slice(i)].join("/");
5032
5032
  };
5033
- var ignoreOverride2 = Symbol(
5033
+ var ignoreOverride2 = /* @__PURE__ */ Symbol(
5034
5034
  "Let zodToJsonSchema decide on which parser to use"
5035
5035
  );
5036
5036
  var defaultOptions2 = {
@@ -6150,7 +6150,7 @@ function zodSchema2(zodSchema22, options) {
6150
6150
  return zod3Schema(zodSchema22);
6151
6151
  }
6152
6152
  }
6153
- var schemaSymbol2 = Symbol.for("vercel.ai.schema");
6153
+ var schemaSymbol2 = /* @__PURE__ */ Symbol.for("vercel.ai.schema");
6154
6154
  function jsonSchema2(jsonSchema22, {
6155
6155
  validate
6156
6156
  } = {}) {
@@ -6205,7 +6205,7 @@ var require_get_context = chunkSG3GRV3O_cjs.__commonJS({
6205
6205
  getContext: () => getContext3
6206
6206
  });
6207
6207
  module.exports = __toCommonJS(get_context_exports);
6208
- var SYMBOL_FOR_REQ_CONTEXT = Symbol.for("@vercel/request-context");
6208
+ var SYMBOL_FOR_REQ_CONTEXT = /* @__PURE__ */ Symbol.for("@vercel/request-context");
6209
6209
  function getContext3() {
6210
6210
  const fromSymbol = globalThis;
6211
6211
  return fromSymbol[SYMBOL_FOR_REQ_CONTEXT]?.get?.() ?? {};
@@ -7234,7 +7234,7 @@ function _makeCompatibilityCheck2(ownVersion) {
7234
7234
  }
7235
7235
  var isCompatible2 = _makeCompatibilityCheck2(VERSION22);
7236
7236
  var major2 = VERSION22.split(".")[0];
7237
- var GLOBAL_OPENTELEMETRY_API_KEY2 = Symbol.for("opentelemetry.js.api." + major2);
7237
+ var GLOBAL_OPENTELEMETRY_API_KEY2 = /* @__PURE__ */ Symbol.for("opentelemetry.js.api." + major2);
7238
7238
  var _global2 = _globalThis2;
7239
7239
  function registerGlobal2(type, instance, diag, allowOverride) {
7240
7240
  var _a162;
@@ -9680,7 +9680,7 @@ function addAdditionalPropertiesToJsonSchema(jsonSchema22) {
9680
9680
  }
9681
9681
  return jsonSchema22;
9682
9682
  }
9683
- var ignoreOverride3 = Symbol(
9683
+ var ignoreOverride3 = /* @__PURE__ */ Symbol(
9684
9684
  "Let zodToJsonSchema decide on which parser to use"
9685
9685
  );
9686
9686
  var defaultOptions3 = {
@@ -10762,7 +10762,7 @@ var zod3ToJsonSchema = (schema, options) => {
10762
10762
  combined.$schema = "http://json-schema.org/draft-07/schema#";
10763
10763
  return combined;
10764
10764
  };
10765
- var schemaSymbol3 = Symbol.for("vercel.ai.schema");
10765
+ var schemaSymbol3 = /* @__PURE__ */ Symbol.for("vercel.ai.schema");
10766
10766
  function lazySchema(createSchema) {
10767
10767
  let schema;
10768
10768
  return () => {
@@ -11163,7 +11163,7 @@ var require_get_context2 = chunkZUQPUTTO_cjs.__commonJS({
11163
11163
  getContext: () => getContext3
11164
11164
  });
11165
11165
  module.exports = __toCommonJS(get_context_exports);
11166
- var SYMBOL_FOR_REQ_CONTEXT = Symbol.for("@vercel/request-context");
11166
+ var SYMBOL_FOR_REQ_CONTEXT = /* @__PURE__ */ Symbol.for("@vercel/request-context");
11167
11167
  function getContext3() {
11168
11168
  const fromSymbol = globalThis;
11169
11169
  return fromSymbol[SYMBOL_FOR_REQ_CONTEXT]?.get?.() ?? {};
@@ -12196,7 +12196,7 @@ function _makeCompatibilityCheck3(ownVersion) {
12196
12196
  }
12197
12197
  var isCompatible3 = _makeCompatibilityCheck3(VERSION23);
12198
12198
  var major3 = VERSION23.split(".")[0];
12199
- var GLOBAL_OPENTELEMETRY_API_KEY3 = Symbol.for("opentelemetry.js.api." + major3);
12199
+ var GLOBAL_OPENTELEMETRY_API_KEY3 = /* @__PURE__ */ Symbol.for("opentelemetry.js.api." + major3);
12200
12200
  var _global3 = _globalThis3;
12201
12201
  function registerGlobal3(type, instance, diag, allowOverride) {
12202
12202
  var _a146;
@@ -14431,6 +14431,9 @@ var DefaultEmbedManyResult3 = class {
14431
14431
  createIdGenerator3({ prefix: "aiobj", size: 24 });
14432
14432
  createIdGenerator3({ prefix: "aiobj", size: 24 });
14433
14433
  function deepMergeWorkingMemory(existing, update) {
14434
+ if (!update || typeof update !== "object" || Object.keys(update).length === 0) {
14435
+ return existing && typeof existing === "object" ? { ...existing } : {};
14436
+ }
14434
14437
  if (!existing || typeof existing !== "object") {
14435
14438
  return update;
14436
14439
  }
@@ -14504,6 +14507,9 @@ var updateWorkingMemoryTool = (memoryConfig) => {
14504
14507
  existingData = null;
14505
14508
  }
14506
14509
  }
14510
+ if (inputData.memory === void 0 || inputData.memory === null) {
14511
+ return { success: true, message: "No memory data provided, existing memory unchanged." };
14512
+ }
14507
14513
  let newData;
14508
14514
  if (typeof inputData.memory === "string") {
14509
14515
  try {
@@ -14716,9 +14722,9 @@ var Memory = class extends memory.MastraMemory {
14716
14722
  const memoryStore = await this.getMemoryStore();
14717
14723
  return memoryStore.getThreadById({ threadId });
14718
14724
  }
14719
- async listThreadsByResourceId(args) {
14725
+ async listThreads(args) {
14720
14726
  const memoryStore = await this.getMemoryStore();
14721
- return memoryStore.listThreadsByResourceId(args);
14727
+ return memoryStore.listThreads(args);
14722
14728
  }
14723
14729
  async handleWorkingMemoryFromMetadata({
14724
14730
  workingMemory,
@@ -15246,15 +15252,102 @@ ${template.content !== this.defaultWorkingMemoryTemplate ? `- Only store informa
15246
15252
  return {};
15247
15253
  }
15248
15254
  /**
15249
- * Updates the metadata of a list of messages
15250
- * @param messages - The list of messages to update
15255
+ * Updates a list of messages and syncs the vector database for semantic recall.
15256
+ * When message content is updated, the corresponding vector embeddings are also updated
15257
+ * to ensure semantic recall stays in sync with the message content.
15258
+ *
15259
+ * @param messages - The list of messages to update (must include id, can include partial content)
15260
+ * @param memoryConfig - Optional memory configuration to determine if semantic recall is enabled
15251
15261
  * @returns The list of updated messages
15252
15262
  */
15253
15263
  async updateMessages({
15254
- messages
15264
+ messages,
15265
+ memoryConfig
15255
15266
  }) {
15256
15267
  if (messages.length === 0) return [];
15257
15268
  const memoryStore = await this.getMemoryStore();
15269
+ const config = this.getMergedThreadConfig(memoryConfig);
15270
+ if (this.vector && config.semanticRecall) {
15271
+ const messagesWithContent = messages.filter((m) => m.content !== void 0);
15272
+ if (messagesWithContent.length > 0) {
15273
+ const existingMessagesResult = await memoryStore.listMessagesById({
15274
+ messageIds: messagesWithContent.map((m) => m.id)
15275
+ });
15276
+ const existingMessagesMap = new Map(existingMessagesResult.messages.map((m) => [m.id, m]));
15277
+ const embeddingData = [];
15278
+ let dimension;
15279
+ const messageIdsWithNewEmbeddings = /* @__PURE__ */ new Set();
15280
+ const messageIdsWithClearedContent = /* @__PURE__ */ new Set();
15281
+ await Promise.all(
15282
+ messagesWithContent.map(async (message) => {
15283
+ const existingMessage = existingMessagesMap.get(message.id);
15284
+ if (!existingMessage) return;
15285
+ let textForEmbedding = null;
15286
+ const content = message.content;
15287
+ if (content) {
15288
+ if ("content" in content && content.content && typeof content.content === "string" && content.content.trim() !== "") {
15289
+ textForEmbedding = content.content;
15290
+ } else if ("parts" in content && content.parts && Array.isArray(content.parts) && content.parts.length > 0) {
15291
+ const joined = content.parts.filter((part) => part?.type === "text").map((part) => part.text).join(" ").trim();
15292
+ if (joined) textForEmbedding = joined;
15293
+ }
15294
+ }
15295
+ if (textForEmbedding) {
15296
+ const result = await this.embedMessageContent(textForEmbedding);
15297
+ dimension = result.dimension;
15298
+ embeddingData.push({
15299
+ embeddings: result.embeddings,
15300
+ metadata: result.chunks.map(() => ({
15301
+ message_id: message.id,
15302
+ thread_id: existingMessage.threadId,
15303
+ resource_id: existingMessage.resourceId
15304
+ }))
15305
+ });
15306
+ messageIdsWithNewEmbeddings.add(message.id);
15307
+ } else {
15308
+ messageIdsWithClearedContent.add(message.id);
15309
+ }
15310
+ })
15311
+ );
15312
+ const messageIdsNeedingDeletion = /* @__PURE__ */ new Set([...messageIdsWithClearedContent, ...messageIdsWithNewEmbeddings]);
15313
+ if (messageIdsNeedingDeletion.size > 0) {
15314
+ try {
15315
+ const indexes = await this.vector.listIndexes();
15316
+ const memoryIndexes = indexes.filter((name21) => name21.startsWith("memory_messages"));
15317
+ for (const indexName of memoryIndexes) {
15318
+ for (const messageId of messageIdsNeedingDeletion) {
15319
+ try {
15320
+ await this.vector.deleteVectors({
15321
+ indexName,
15322
+ filter: { message_id: messageId }
15323
+ });
15324
+ } catch {
15325
+ this.logger.debug(
15326
+ `No existing vectors found for message ${messageId} in ${indexName}, skipping delete`
15327
+ );
15328
+ }
15329
+ }
15330
+ }
15331
+ } catch {
15332
+ this.logger.debug(`No memory indexes found to delete from`);
15333
+ }
15334
+ }
15335
+ if (embeddingData.length > 0 && dimension !== void 0) {
15336
+ const { indexName } = await this.createEmbeddingIndex(dimension, config);
15337
+ const allVectors = [];
15338
+ const allMetadata = [];
15339
+ for (const data of embeddingData) {
15340
+ allVectors.push(...data.embeddings);
15341
+ allMetadata.push(...data.metadata);
15342
+ }
15343
+ await this.vector.upsert({
15344
+ indexName,
15345
+ vectors: allVectors,
15346
+ metadata: allMetadata
15347
+ });
15348
+ }
15349
+ }
15350
+ }
15258
15351
  return memoryStore.updateMessages({ messages });
15259
15352
  }
15260
15353
  /**
@@ -15288,6 +15381,233 @@ ${template.content !== this.defaultWorkingMemoryTemplate ? `- Only store informa
15288
15381
  const memoryStore = await this.getMemoryStore();
15289
15382
  await memoryStore.deleteMessages(messageIds);
15290
15383
  }
15384
+ /**
15385
+ * Clone a thread and its messages to create a new independent thread.
15386
+ * The cloned thread will have metadata tracking its source.
15387
+ *
15388
+ * If semantic recall is enabled, the cloned messages will also be embedded
15389
+ * and added to the vector store for semantic search.
15390
+ *
15391
+ * @param args - Clone configuration options
15392
+ * @param args.sourceThreadId - ID of the thread to clone
15393
+ * @param args.newThreadId - ID for the new cloned thread (if not provided, a random UUID will be generated)
15394
+ * @param args.resourceId - Resource ID for the new thread (defaults to source thread's resourceId)
15395
+ * @param args.title - Title for the new cloned thread
15396
+ * @param args.metadata - Additional metadata to merge with clone metadata
15397
+ * @param args.options - Options for filtering which messages to include
15398
+ * @param args.options.messageLimit - Maximum number of messages to copy (from most recent)
15399
+ * @param args.options.messageFilter - Filter messages by date range or specific IDs
15400
+ * @param memoryConfig - Optional memory configuration override
15401
+ * @returns The newly created thread and the cloned messages
15402
+ *
15403
+ * @example
15404
+ * ```typescript
15405
+ * // Clone entire thread
15406
+ * const { thread, clonedMessages } = await memory.cloneThread({
15407
+ * sourceThreadId: 'thread-123',
15408
+ * });
15409
+ *
15410
+ * // Clone with custom ID
15411
+ * const { thread, clonedMessages } = await memory.cloneThread({
15412
+ * sourceThreadId: 'thread-123',
15413
+ * newThreadId: 'my-custom-thread-id',
15414
+ * });
15415
+ *
15416
+ * // Clone with message limit
15417
+ * const { thread, clonedMessages } = await memory.cloneThread({
15418
+ * sourceThreadId: 'thread-123',
15419
+ * title: 'My cloned conversation',
15420
+ * options: {
15421
+ * messageLimit: 10, // Only clone last 10 messages
15422
+ * },
15423
+ * });
15424
+ *
15425
+ * // Clone with date filter
15426
+ * const { thread, clonedMessages } = await memory.cloneThread({
15427
+ * sourceThreadId: 'thread-123',
15428
+ * options: {
15429
+ * messageFilter: {
15430
+ * startDate: new Date('2024-01-01'),
15431
+ * endDate: new Date('2024-06-01'),
15432
+ * },
15433
+ * },
15434
+ * });
15435
+ * ```
15436
+ */
15437
+ async cloneThread(args, memoryConfig) {
15438
+ const memoryStore = await this.getMemoryStore();
15439
+ const result = await memoryStore.cloneThread(args);
15440
+ const config = this.getMergedThreadConfig(memoryConfig);
15441
+ if (this.vector && config.semanticRecall && result.clonedMessages.length > 0) {
15442
+ await this.embedClonedMessages(result.clonedMessages, config);
15443
+ }
15444
+ return result;
15445
+ }
15446
+ /**
15447
+ * Embed cloned messages for semantic recall.
15448
+ * This is similar to the embedding logic in saveMessages but operates on already-saved messages.
15449
+ */
15450
+ async embedClonedMessages(messages, config) {
15451
+ if (!this.vector || !this.embedder) {
15452
+ return;
15453
+ }
15454
+ const embeddingData = [];
15455
+ let dimension;
15456
+ await Promise.all(
15457
+ messages.map(async (message) => {
15458
+ let textForEmbedding = null;
15459
+ if (message.content?.content && typeof message.content.content === "string" && message.content.content.trim() !== "") {
15460
+ textForEmbedding = message.content.content;
15461
+ } else if (message.content?.parts && message.content.parts.length > 0) {
15462
+ const joined = message.content.parts.filter((part) => part.type === "text").map((part) => part.text).join(" ").trim();
15463
+ if (joined) textForEmbedding = joined;
15464
+ }
15465
+ if (!textForEmbedding) return;
15466
+ const result = await this.embedMessageContent(textForEmbedding);
15467
+ dimension = result.dimension;
15468
+ embeddingData.push({
15469
+ embeddings: result.embeddings,
15470
+ metadata: result.chunks.map(() => ({
15471
+ message_id: message.id,
15472
+ thread_id: message.threadId,
15473
+ resource_id: message.resourceId
15474
+ }))
15475
+ });
15476
+ })
15477
+ );
15478
+ if (embeddingData.length > 0 && dimension !== void 0) {
15479
+ const { indexName } = await this.createEmbeddingIndex(dimension, config);
15480
+ const allVectors = [];
15481
+ const allMetadata = [];
15482
+ for (const data of embeddingData) {
15483
+ allVectors.push(...data.embeddings);
15484
+ allMetadata.push(...data.metadata);
15485
+ }
15486
+ await this.vector.upsert({
15487
+ indexName,
15488
+ vectors: allVectors,
15489
+ metadata: allMetadata
15490
+ });
15491
+ }
15492
+ }
15493
+ /**
15494
+ * Get the clone metadata from a thread if it was cloned from another thread.
15495
+ *
15496
+ * @param thread - The thread to check
15497
+ * @returns The clone metadata if the thread is a clone, null otherwise
15498
+ *
15499
+ * @example
15500
+ * ```typescript
15501
+ * const thread = await memory.getThreadById({ threadId: 'thread-123' });
15502
+ * const cloneInfo = memory.getCloneMetadata(thread);
15503
+ * if (cloneInfo) {
15504
+ * console.log(`This thread was cloned from ${cloneInfo.sourceThreadId}`);
15505
+ * }
15506
+ * ```
15507
+ */
15508
+ getCloneMetadata(thread) {
15509
+ if (!thread?.metadata?.clone) {
15510
+ return null;
15511
+ }
15512
+ return thread.metadata.clone;
15513
+ }
15514
+ /**
15515
+ * Check if a thread is a clone of another thread.
15516
+ *
15517
+ * @param thread - The thread to check
15518
+ * @returns True if the thread is a clone, false otherwise
15519
+ *
15520
+ * @example
15521
+ * ```typescript
15522
+ * const thread = await memory.getThreadById({ threadId: 'thread-123' });
15523
+ * if (memory.isClone(thread)) {
15524
+ * console.log('This is a cloned thread');
15525
+ * }
15526
+ * ```
15527
+ */
15528
+ isClone(thread) {
15529
+ return this.getCloneMetadata(thread) !== null;
15530
+ }
15531
+ /**
15532
+ * Get the source thread that a cloned thread was created from.
15533
+ *
15534
+ * @param threadId - ID of the cloned thread
15535
+ * @returns The source thread if found, null if the thread is not a clone or source doesn't exist
15536
+ *
15537
+ * @example
15538
+ * ```typescript
15539
+ * const sourceThread = await memory.getSourceThread('cloned-thread-123');
15540
+ * if (sourceThread) {
15541
+ * console.log(`Original thread: ${sourceThread.title}`);
15542
+ * }
15543
+ * ```
15544
+ */
15545
+ async getSourceThread(threadId) {
15546
+ const thread = await this.getThreadById({ threadId });
15547
+ const cloneMetadata = this.getCloneMetadata(thread);
15548
+ if (!cloneMetadata) {
15549
+ return null;
15550
+ }
15551
+ return this.getThreadById({ threadId: cloneMetadata.sourceThreadId });
15552
+ }
15553
+ /**
15554
+ * List all threads that were cloned from a specific source thread.
15555
+ *
15556
+ * @param sourceThreadId - ID of the source thread
15557
+ * @param resourceId - Optional resource ID to filter by
15558
+ * @returns Array of threads that are clones of the source thread
15559
+ *
15560
+ * @example
15561
+ * ```typescript
15562
+ * const clones = await memory.listClones('original-thread-123', 'user-456');
15563
+ * console.log(`Found ${clones.length} clones of this thread`);
15564
+ * ```
15565
+ */
15566
+ async listClones(sourceThreadId, resourceId) {
15567
+ let targetResourceId = resourceId;
15568
+ if (!targetResourceId) {
15569
+ const sourceThread = await this.getThreadById({ threadId: sourceThreadId });
15570
+ if (!sourceThread) {
15571
+ return [];
15572
+ }
15573
+ targetResourceId = sourceThread.resourceId;
15574
+ }
15575
+ const { threads } = await this.listThreads({
15576
+ filter: { resourceId: targetResourceId },
15577
+ perPage: false
15578
+ // Get all threads
15579
+ });
15580
+ return threads.filter((thread) => {
15581
+ const cloneMetadata = this.getCloneMetadata(thread);
15582
+ return cloneMetadata?.sourceThreadId === sourceThreadId;
15583
+ });
15584
+ }
15585
+ /**
15586
+ * Get the clone history chain for a thread (all ancestors back to the original).
15587
+ *
15588
+ * @param threadId - ID of the thread to get history for
15589
+ * @returns Array of threads from oldest ancestor to the given thread (inclusive)
15590
+ *
15591
+ * @example
15592
+ * ```typescript
15593
+ * const history = await memory.getCloneHistory('deeply-cloned-thread');
15594
+ * // Returns: [originalThread, firstClone, secondClone, deeplyClonedThread]
15595
+ * ```
15596
+ */
15597
+ async getCloneHistory(threadId) {
15598
+ const history = [];
15599
+ let currentThreadId = threadId;
15600
+ while (currentThreadId) {
15601
+ const thread = await this.getThreadById({ threadId: currentThreadId });
15602
+ if (!thread) {
15603
+ break;
15604
+ }
15605
+ history.unshift(thread);
15606
+ const cloneMetadata = this.getCloneMetadata(thread);
15607
+ currentThreadId = cloneMetadata?.sourceThreadId ?? null;
15608
+ }
15609
+ return history;
15610
+ }
15291
15611
  };
15292
15612
 
15293
15613
  Object.defineProperty(exports, "extractWorkingMemoryContent", {