@mastra/upstash 0.10.3-alpha.1 → 0.10.3-alpha.2

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.
@@ -1,39 +1,39 @@
1
1
 
2
- > @mastra/upstash@0.10.3-alpha.1 build /home/runner/work/mastra/mastra/stores/upstash
2
+ > @mastra/upstash@0.10.3-alpha.2 build /home/runner/work/mastra/mastra/stores/upstash
3
3
  > tsup src/index.ts --format esm,cjs --experimental-dts --clean --treeshake=smallest --splitting
4
4
 
5
5
  CLI Building entry: src/index.ts
6
6
  CLI Using tsconfig: tsconfig.json
7
7
  CLI tsup v8.5.0
8
8
  TSC Build start
9
- TSC ⚡️ Build success in 10151ms
9
+ TSC ⚡️ Build success in 10231ms
10
10
  DTS Build start
11
11
  CLI Target: es2022
12
12
  Analysis will use the bundled TypeScript version 5.8.3
13
13
  Writing package typings: /home/runner/work/mastra/mastra/stores/upstash/dist/_tsup-dts-rollup.d.ts
14
14
  Analysis will use the bundled TypeScript version 5.8.3
15
15
  Writing package typings: /home/runner/work/mastra/mastra/stores/upstash/dist/_tsup-dts-rollup.d.cts
16
- DTS ⚡️ Build success in 11729ms
16
+ DTS ⚡️ Build success in 10756ms
17
17
  CLI Cleaning output folder
18
18
  ESM Build start
19
19
  CJS Build start
20
- dist/index.cjs (1651:18): Use of eval in "dist/index.cjs" is strongly discouraged as it poses security risks and may cause issues with minification.
21
20
  dist/index.js (1651:18): Use of eval in "dist/index.js" is strongly discouraged as it poses security risks and may cause issues with minification.
21
+ dist/index.cjs (1651:18): Use of eval in "dist/index.cjs" is strongly discouraged as it poses security risks and may cause issues with minification.
22
+ ESM dist/getMachineId-darwin-UTKBTJ2U.js 1.08 KB
23
+ ESM dist/getMachineId-linux-K3QXQYAB.js 740.00 B
24
+ ESM dist/getMachineId-bsd-KKIDU47O.js 896.00 B
25
+ ESM dist/getMachineId-win-L2EYIM5A.js 1.04 KB
26
+ ESM dist/chunk-IGKEDEDE.js 452.00 B
27
+ ESM dist/getMachineId-unsupported-VPWBQCK7.js 700.00 B
28
+ ESM dist/chunk-HSTZWXH7.js 61.52 KB
29
+ ESM dist/index.js 1.64 MB
30
+ ESM ⚡️ Build success in 23117ms
22
31
  CJS dist/getMachineId-darwin-3PL23DL6.cjs 1.19 KB
23
32
  CJS dist/getMachineId-linux-KYLPK3HC.cjs 813.00 B
24
33
  CJS dist/getMachineId-bsd-HDZ73WR7.cjs 1015.00 B
25
- CJS dist/chunk-N2CPQVE3.cjs 1.09 KB
26
34
  CJS dist/getMachineId-win-ZTI2LRDJ.cjs 1.66 KB
35
+ CJS dist/chunk-N2CPQVE3.cjs 1.09 KB
27
36
  CJS dist/getMachineId-unsupported-DEDJN4ZS.cjs 777.00 B
28
37
  CJS dist/chunk-U74OJRHU.cjs 62.08 KB
29
38
  CJS dist/index.cjs 1.65 MB
30
- CJS ⚡️ Build success in 21940ms
31
- ESM dist/getMachineId-linux-K3QXQYAB.js 740.00 B
32
- ESM dist/getMachineId-darwin-UTKBTJ2U.js 1.08 KB
33
- ESM dist/getMachineId-win-L2EYIM5A.js 1.04 KB
34
- ESM dist/getMachineId-bsd-KKIDU47O.js 896.00 B
35
- ESM dist/chunk-IGKEDEDE.js 452.00 B
36
- ESM dist/getMachineId-unsupported-VPWBQCK7.js 700.00 B
37
- ESM dist/chunk-HSTZWXH7.js 61.52 KB
38
- ESM dist/index.js 1.64 MB
39
- ESM ⚡️ Build success in 21983ms
39
+ CJS ⚡️ Build success in 23122ms
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @mastra/upstash
2
2
 
3
+ ## 0.10.3-alpha.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 48eddb9: update filter logic in Memory class to support semantic recall search scope
8
+ - Updated dependencies [48eddb9]
9
+ - @mastra/core@0.10.4-alpha.2
10
+
3
11
  ## 0.10.3-alpha.1
4
12
 
5
13
  ### Patch Changes
@@ -55,6 +55,9 @@ export declare class UpstashFilterTranslator extends BaseFilterTranslator {
55
55
  declare class UpstashStore extends MastraStorage {
56
56
  private redis;
57
57
  constructor(config: UpstashConfig);
58
+ get supports(): {
59
+ selectByIncludeResourceScope: boolean;
60
+ };
58
61
  private transformEvalRecord;
59
62
  private parseJSON;
60
63
  private getKey;
@@ -55,6 +55,9 @@ export declare class UpstashFilterTranslator extends BaseFilterTranslator {
55
55
  declare class UpstashStore extends MastraStorage {
56
56
  private redis;
57
57
  constructor(config: UpstashConfig);
58
+ get supports(): {
59
+ selectByIncludeResourceScope: boolean;
60
+ };
58
61
  private transformEvalRecord;
59
62
  private parseJSON;
60
63
  private getKey;
package/dist/index.cjs CHANGED
@@ -31172,9 +31172,9 @@ var require_chunk_QVROTSA5 = chunkU74OJRHU_cjs.__commonJS({
31172
31172
  }
31173
31173
  });
31174
31174
 
31175
- // ../../packages/core/dist/chunk-H5IRI46R.cjs
31176
- var require_chunk_H5IRI46R = chunkU74OJRHU_cjs.__commonJS({
31177
- "../../packages/core/dist/chunk-H5IRI46R.cjs"(exports2) {
31175
+ // ../../packages/core/dist/chunk-XUODQRSL.cjs
31176
+ var require_chunk_XUODQRSL = chunkU74OJRHU_cjs.__commonJS({
31177
+ "../../packages/core/dist/chunk-XUODQRSL.cjs"(exports2) {
31178
31178
  var chunkQVROTSA5_cjs = require_chunk_QVROTSA5();
31179
31179
  var crypto2 = chunkU74OJRHU_cjs.__require("crypto");
31180
31180
  var ai = require_dist4();
@@ -31746,7 +31746,7 @@ ${JSON.stringify(message, null, 2)}`
31746
31746
  }
31747
31747
  const latestMessagePartType = latestMessage?.content?.parts?.filter((p) => p.type !== `step-start`)?.at?.(-1)?.type;
31748
31748
  const newMessageFirstPartType = messageV2.content.parts.filter((p) => p.type !== `step-start`).at(0)?.type;
31749
- const shouldAppendToLastAssistantMessage = latestMessage?.role === "assistant" && messageV2.role === "assistant";
31749
+ const shouldAppendToLastAssistantMessage = latestMessage?.role === "assistant" && messageV2.role === "assistant" && latestMessage.threadId === messageV2.threadId;
31750
31750
  const shouldAppendToLastAssistantMessageParts = shouldAppendToLastAssistantMessage && newMessageFirstPartType && (newMessageFirstPartType === `tool-invocation` && latestMessagePartType !== `text` || newMessageFirstPartType === latestMessagePartType);
31751
31751
  if (
31752
31752
  // backwards compat check!
@@ -31824,7 +31824,11 @@ ${JSON.stringify(message, null, 2)}`
31824
31824
  return this;
31825
31825
  }
31826
31826
  inputToMastraMessageV2(message, messageSource) {
31827
- if (`threadId` in message && message.threadId && this.memoryInfo && message.threadId !== this.memoryInfo.threadId) {
31827
+ if (
31828
+ // we can't throw if the threadId doesn't match and this message came from memory
31829
+ // this is because per-user semantic recall can retrieve messages from other threads
31830
+ messageSource !== `memory` && `threadId` in message && message.threadId && this.memoryInfo && message.threadId !== this.memoryInfo.threadId
31831
+ ) {
31828
31832
  throw new Error(
31829
31833
  `Received input message with wrong threadId. Input ${message.threadId}, expected ${this.memoryInfo.threadId}`
31830
31834
  );
@@ -38437,11 +38441,11 @@ ${err.message}`);
38437
38441
  }
38438
38442
  });
38439
38443
 
38440
- // ../../packages/core/dist/chunk-OKHKG5C7.cjs
38441
- var require_chunk_OKHKG5C7 = chunkU74OJRHU_cjs.__commonJS({
38442
- "../../packages/core/dist/chunk-OKHKG5C7.cjs"(exports2) {
38444
+ // ../../packages/core/dist/chunk-ZWYZGIV3.cjs
38445
+ var require_chunk_ZWYZGIV3 = chunkU74OJRHU_cjs.__commonJS({
38446
+ "../../packages/core/dist/chunk-ZWYZGIV3.cjs"(exports2) {
38443
38447
  var chunkJYLH5IY3_cjs = require_chunk_JYLH5IY3();
38444
- var chunkH5IRI46R_cjs = require_chunk_H5IRI46R();
38448
+ var chunkXUODQRSL_cjs = require_chunk_XUODQRSL();
38445
38449
  var chunkST5RMVLG_cjs = require_chunk_ST5RMVLG();
38446
38450
  var chunkRO5VPM3P_cjs = require_chunk_RO5VPM3P();
38447
38451
  var chunkZAXPU6F2_cjs = require_chunk_ZAXPU6F2();
@@ -38847,7 +38851,7 @@ var require_chunk_OKHKG5C7 = chunkU74OJRHU_cjs.__commonJS({
38847
38851
  memoryConfig,
38848
38852
  resourceId,
38849
38853
  runId,
38850
- messageList = new chunkH5IRI46R_cjs.MessageList({
38854
+ messageList = new chunkXUODQRSL_cjs.MessageList({
38851
38855
  threadId,
38852
38856
  resourceId
38853
38857
  })
@@ -38862,14 +38866,11 @@ var require_chunk_OKHKG5C7 = chunkU74OJRHU_cjs.__commonJS({
38862
38866
  threadId: threadId || ""
38863
38867
  };
38864
38868
  }
38865
- const allUIMessages = messageList.get.all.ui();
38866
- const currentUserMessages = allUIMessages.filter((m) => m.role === "user");
38867
- const lastUserMessageContent = currentUserMessages.at(-1)?.content ?? "";
38868
38869
  const [memoryMessages, memorySystemMessage] = threadId && memory ? await Promise.all([memory.rememberMessages({
38869
38870
  threadId,
38870
38871
  resourceId,
38871
38872
  config: memoryConfig,
38872
- vectorMessageSearch: lastUserMessageContent
38873
+ vectorMessageSearch: messageList.getLatestUserContent() || ""
38873
38874
  }).then((r) => r.messagesV2), memory.getSystemMessage({
38874
38875
  threadId,
38875
38876
  memoryConfig
@@ -39218,7 +39219,7 @@ var require_chunk_OKHKG5C7 = chunkU74OJRHU_cjs.__commonJS({
39218
39219
  runId,
39219
39220
  runtimeContext
39220
39221
  });
39221
- const messageList = new chunkH5IRI46R_cjs.MessageList({
39222
+ const messageList = new chunkXUODQRSL_cjs.MessageList({
39222
39223
  threadId,
39223
39224
  resourceId,
39224
39225
  generateMessageId
@@ -39251,12 +39252,13 @@ var require_chunk_OKHKG5C7 = chunkU74OJRHU_cjs.__commonJS({
39251
39252
  resourceId,
39252
39253
  memoryConfig
39253
39254
  });
39254
- const [memoryMessages, memorySystemMessage] = threadId && memory ? await Promise.all([memory.rememberMessages({
39255
+ let [memoryMessages, memorySystemMessage] = threadId && memory ? await Promise.all([memory.rememberMessages({
39255
39256
  threadId,
39256
39257
  resourceId,
39257
39258
  config: memoryConfig,
39258
- vectorMessageSearch: messageList.getLatestUserContent() || ""
39259
- }).then((r) => r.messages), memory.getSystemMessage({
39259
+ // The new user messages aren't in the list yet cause we add memory messages first to try to make sure ordering is correct (memory comes before new user messages)
39260
+ vectorMessageSearch: new chunkXUODQRSL_cjs.MessageList().add(messages, `user`).getLatestUserContent() || ""
39261
+ }).then((r) => r.messagesV2), memory.getSystemMessage({
39260
39262
  threadId,
39261
39263
  memoryConfig
39262
39264
  })]) : [[], null];
@@ -39265,10 +39267,28 @@ var require_chunk_OKHKG5C7 = chunkU74OJRHU_cjs.__commonJS({
39265
39267
  runId,
39266
39268
  fetchedCount: memoryMessages.length
39267
39269
  });
39270
+ const resultsFromOtherThreads = memoryMessages.filter((m) => m.threadId !== threadId);
39271
+ if (resultsFromOtherThreads.length && !memorySystemMessage) {
39272
+ memorySystemMessage = ``;
39273
+ }
39274
+ if (resultsFromOtherThreads.length) {
39275
+ memorySystemMessage += `
39276
+ The following messages were remembered from a different conversation:
39277
+ <remembered_from_other_conversation>
39278
+ ${JSON.stringify(
39279
+ // get v1 since they're closer to CoreMessages (which get sent to the LLM) but also include timestamps
39280
+ new chunkXUODQRSL_cjs.MessageList().add(resultsFromOtherThreads, "memory").get.all.v1()
39281
+ )}
39282
+ <end_remembered_from_other_conversation>`;
39283
+ }
39268
39284
  if (memorySystemMessage) {
39269
39285
  messageList.addSystem(memorySystemMessage, "memory");
39270
39286
  }
39271
- messageList.add(memoryMessages, "memory").add(messages, "user");
39287
+ messageList.add(
39288
+ memoryMessages.filter((m) => m.threadId === threadId),
39289
+ // filter out messages from other threads. those are added to system message above
39290
+ "memory"
39291
+ ).add(messages, "user");
39272
39292
  const systemMessage = messageList.getSystemMessages()?.map((m) => m.content)?.join(`
39273
39293
  `) ?? void 0;
39274
39294
  const processedMemoryMessages = memory.processMessages({
@@ -39280,7 +39300,7 @@ var require_chunk_OKHKG5C7 = chunkU74OJRHU_cjs.__commonJS({
39280
39300
  systemMessage,
39281
39301
  memorySystemMessage: memorySystemMessage || void 0
39282
39302
  });
39283
- const processedList = new chunkH5IRI46R_cjs.MessageList({
39303
+ const processedList = new chunkXUODQRSL_cjs.MessageList({
39284
39304
  threadId,
39285
39305
  resourceId
39286
39306
  }).addSystem(instructions || `${this.instructions}.`).addSystem(memorySystemMessage).add(context2 || [], "context").add(processedMemoryMessages, "memory").add(messageList.get.input.v2(), "user").get.all.prompt();
@@ -42755,18 +42775,18 @@ var require_chunk_OKHKG5C7 = chunkU74OJRHU_cjs.__commonJS({
42755
42775
  // ../../packages/core/dist/agent/index.cjs
42756
42776
  var require_agent = chunkU74OJRHU_cjs.__commonJS({
42757
42777
  "../../packages/core/dist/agent/index.cjs"(exports2) {
42758
- var chunkOKHKG5C7_cjs = require_chunk_OKHKG5C7();
42759
- var chunkH5IRI46R_cjs = require_chunk_H5IRI46R();
42778
+ var chunkZWYZGIV3_cjs = require_chunk_ZWYZGIV3();
42779
+ var chunkXUODQRSL_cjs = require_chunk_XUODQRSL();
42760
42780
  Object.defineProperty(exports2, "Agent", {
42761
42781
  enumerable: true,
42762
42782
  get: function() {
42763
- return chunkOKHKG5C7_cjs.Agent;
42783
+ return chunkZWYZGIV3_cjs.Agent;
42764
42784
  }
42765
42785
  });
42766
42786
  Object.defineProperty(exports2, "MessageList", {
42767
42787
  enumerable: true,
42768
42788
  get: function() {
42769
- return chunkH5IRI46R_cjs.MessageList;
42789
+ return chunkXUODQRSL_cjs.MessageList;
42770
42790
  }
42771
42791
  });
42772
42792
  }
@@ -42783,6 +42803,11 @@ var UpstashStore = class extends storage.MastraStorage {
42783
42803
  token: config.token
42784
42804
  });
42785
42805
  }
42806
+ get supports() {
42807
+ return {
42808
+ selectByIncludeResourceScope: true
42809
+ };
42810
+ }
42786
42811
  transformEvalRecord(record) {
42787
42812
  let result = record.result;
42788
42813
  if (typeof result === "string") {
@@ -42867,7 +42892,8 @@ var UpstashStore = class extends storage.MastraStorage {
42867
42892
  return totalDeleted;
42868
42893
  }
42869
42894
  getMessageKey(threadId, messageId) {
42870
- return this.getKey(storage.TABLE_MESSAGES, { threadId, id: messageId });
42895
+ const key = this.getKey(storage.TABLE_MESSAGES, { threadId, id: messageId });
42896
+ return key;
42871
42897
  }
42872
42898
  getThreadMessagesKey(threadId) {
42873
42899
  return `thread:${threadId}:messages`;
@@ -43240,6 +43266,14 @@ var UpstashStore = class extends storage.MastraStorage {
43240
43266
  async saveMessages(args) {
43241
43267
  const { messages, format = "v1" } = args;
43242
43268
  if (messages.length === 0) return [];
43269
+ const threadId = messages[0]?.threadId;
43270
+ if (!threadId) {
43271
+ throw new Error("Thread ID is required");
43272
+ }
43273
+ const thread = await this.getThreadById({ threadId });
43274
+ if (!thread) {
43275
+ throw new Error(`Thread ${threadId} not found`);
43276
+ }
43243
43277
  const messagesWithIndex = messages.map((message, index) => ({
43244
43278
  ...message,
43245
43279
  _index: index
@@ -43250,7 +43284,8 @@ var UpstashStore = class extends storage.MastraStorage {
43250
43284
  const pipeline = this.redis.pipeline();
43251
43285
  for (const message of batch) {
43252
43286
  const key = this.getMessageKey(message.threadId, message.id);
43253
- const score = message._index !== void 0 ? message._index : new Date(message.createdAt).getTime();
43287
+ const createdAtScore = new Date(message.createdAt).getTime();
43288
+ const score = message._index !== void 0 ? message._index : createdAtScore;
43254
43289
  pipeline.set(key, message);
43255
43290
  pipeline.zadd(this.getThreadMessagesKey(message.threadId), {
43256
43291
  score,
@@ -43345,38 +43380,55 @@ var UpstashStore = class extends storage.MastraStorage {
43345
43380
  limit = Number.MAX_SAFE_INTEGER;
43346
43381
  }
43347
43382
  const messageIds = /* @__PURE__ */ new Set();
43383
+ const messageIdToThreadIds = {};
43348
43384
  if (limit === 0 && !selectBy?.include) {
43349
43385
  return [];
43350
43386
  }
43351
43387
  if (selectBy?.include?.length) {
43352
43388
  for (const item of selectBy.include) {
43353
43389
  messageIds.add(item.id);
43354
- if (item.withPreviousMessages || item.withNextMessages) {
43355
- const rank = await this.redis.zrank(threadMessagesKey, item.id);
43356
- if (rank === null) continue;
43357
- if (item.withPreviousMessages) {
43358
- const start = Math.max(0, rank - item.withPreviousMessages);
43359
- const prevIds = rank === 0 ? [] : await this.redis.zrange(threadMessagesKey, start, rank - 1);
43360
- prevIds.forEach((id) => messageIds.add(id));
43361
- }
43362
- if (item.withNextMessages) {
43363
- const nextIds = await this.redis.zrange(threadMessagesKey, rank + 1, rank + item.withNextMessages);
43364
- nextIds.forEach((id) => messageIds.add(id));
43365
- }
43390
+ const itemThreadId = item.threadId || threadId;
43391
+ messageIdToThreadIds[item.id] = itemThreadId;
43392
+ const itemThreadMessagesKey = this.getThreadMessagesKey(itemThreadId);
43393
+ const rank = await this.redis.zrank(itemThreadMessagesKey, item.id);
43394
+ if (rank === null) continue;
43395
+ if (item.withPreviousMessages) {
43396
+ const start = Math.max(0, rank - item.withPreviousMessages);
43397
+ const prevIds = rank === 0 ? [] : await this.redis.zrange(itemThreadMessagesKey, start, rank - 1);
43398
+ prevIds.forEach((id) => {
43399
+ messageIds.add(id);
43400
+ messageIdToThreadIds[id] = itemThreadId;
43401
+ });
43402
+ }
43403
+ if (item.withNextMessages) {
43404
+ const nextIds = await this.redis.zrange(itemThreadMessagesKey, rank + 1, rank + item.withNextMessages);
43405
+ nextIds.forEach((id) => {
43406
+ messageIds.add(id);
43407
+ messageIdToThreadIds[id] = itemThreadId;
43408
+ });
43366
43409
  }
43367
43410
  }
43368
43411
  }
43369
43412
  if (limit === Number.MAX_SAFE_INTEGER) {
43370
43413
  const allIds = await this.redis.zrange(threadMessagesKey, 0, -1);
43371
- allIds.forEach((id) => messageIds.add(id));
43414
+ allIds.forEach((id) => {
43415
+ messageIds.add(id);
43416
+ messageIdToThreadIds[id] = threadId;
43417
+ });
43372
43418
  } else if (limit > 0) {
43373
43419
  const latestIds = await this.redis.zrange(threadMessagesKey, -limit, -1);
43374
- latestIds.forEach((id) => messageIds.add(id));
43420
+ latestIds.forEach((id) => {
43421
+ messageIds.add(id);
43422
+ messageIdToThreadIds[id] = threadId;
43423
+ });
43375
43424
  }
43376
43425
  const messages = (await Promise.all(
43377
- Array.from(messageIds).map(
43378
- async (id) => this.redis.get(this.getMessageKey(threadId, id))
43379
- )
43426
+ Array.from(messageIds).map(async (id) => {
43427
+ const tId = messageIdToThreadIds[id] || threadId;
43428
+ const byThreadId = await this.redis.get(this.getMessageKey(tId, id));
43429
+ if (byThreadId) return byThreadId;
43430
+ return null;
43431
+ })
43380
43432
  )).filter((msg) => msg !== null);
43381
43433
  messages.sort((a, b) => allMessageIds.indexOf(a.id) - allMessageIds.indexOf(b.id));
43382
43434
  const prepared = messages.filter((message) => message !== null && message !== void 0).map((message) => {
package/dist/index.js CHANGED
@@ -31170,9 +31170,9 @@ var require_chunk_QVROTSA5 = __commonJS({
31170
31170
  }
31171
31171
  });
31172
31172
 
31173
- // ../../packages/core/dist/chunk-H5IRI46R.cjs
31174
- var require_chunk_H5IRI46R = __commonJS({
31175
- "../../packages/core/dist/chunk-H5IRI46R.cjs"(exports2) {
31173
+ // ../../packages/core/dist/chunk-XUODQRSL.cjs
31174
+ var require_chunk_XUODQRSL = __commonJS({
31175
+ "../../packages/core/dist/chunk-XUODQRSL.cjs"(exports2) {
31176
31176
  var chunkQVROTSA5_cjs = require_chunk_QVROTSA5();
31177
31177
  var crypto2 = __require("crypto");
31178
31178
  var ai = require_dist4();
@@ -31744,7 +31744,7 @@ ${JSON.stringify(message, null, 2)}`
31744
31744
  }
31745
31745
  const latestMessagePartType = latestMessage?.content?.parts?.filter((p) => p.type !== `step-start`)?.at?.(-1)?.type;
31746
31746
  const newMessageFirstPartType = messageV2.content.parts.filter((p) => p.type !== `step-start`).at(0)?.type;
31747
- const shouldAppendToLastAssistantMessage = latestMessage?.role === "assistant" && messageV2.role === "assistant";
31747
+ const shouldAppendToLastAssistantMessage = latestMessage?.role === "assistant" && messageV2.role === "assistant" && latestMessage.threadId === messageV2.threadId;
31748
31748
  const shouldAppendToLastAssistantMessageParts = shouldAppendToLastAssistantMessage && newMessageFirstPartType && (newMessageFirstPartType === `tool-invocation` && latestMessagePartType !== `text` || newMessageFirstPartType === latestMessagePartType);
31749
31749
  if (
31750
31750
  // backwards compat check!
@@ -31822,7 +31822,11 @@ ${JSON.stringify(message, null, 2)}`
31822
31822
  return this;
31823
31823
  }
31824
31824
  inputToMastraMessageV2(message, messageSource) {
31825
- if (`threadId` in message && message.threadId && this.memoryInfo && message.threadId !== this.memoryInfo.threadId) {
31825
+ if (
31826
+ // we can't throw if the threadId doesn't match and this message came from memory
31827
+ // this is because per-user semantic recall can retrieve messages from other threads
31828
+ messageSource !== `memory` && `threadId` in message && message.threadId && this.memoryInfo && message.threadId !== this.memoryInfo.threadId
31829
+ ) {
31826
31830
  throw new Error(
31827
31831
  `Received input message with wrong threadId. Input ${message.threadId}, expected ${this.memoryInfo.threadId}`
31828
31832
  );
@@ -38435,11 +38439,11 @@ ${err.message}`);
38435
38439
  }
38436
38440
  });
38437
38441
 
38438
- // ../../packages/core/dist/chunk-OKHKG5C7.cjs
38439
- var require_chunk_OKHKG5C7 = __commonJS({
38440
- "../../packages/core/dist/chunk-OKHKG5C7.cjs"(exports2) {
38442
+ // ../../packages/core/dist/chunk-ZWYZGIV3.cjs
38443
+ var require_chunk_ZWYZGIV3 = __commonJS({
38444
+ "../../packages/core/dist/chunk-ZWYZGIV3.cjs"(exports2) {
38441
38445
  var chunkJYLH5IY3_cjs = require_chunk_JYLH5IY3();
38442
- var chunkH5IRI46R_cjs = require_chunk_H5IRI46R();
38446
+ var chunkXUODQRSL_cjs = require_chunk_XUODQRSL();
38443
38447
  var chunkST5RMVLG_cjs = require_chunk_ST5RMVLG();
38444
38448
  var chunkRO5VPM3P_cjs = require_chunk_RO5VPM3P();
38445
38449
  var chunkZAXPU6F2_cjs = require_chunk_ZAXPU6F2();
@@ -38845,7 +38849,7 @@ var require_chunk_OKHKG5C7 = __commonJS({
38845
38849
  memoryConfig,
38846
38850
  resourceId,
38847
38851
  runId,
38848
- messageList = new chunkH5IRI46R_cjs.MessageList({
38852
+ messageList = new chunkXUODQRSL_cjs.MessageList({
38849
38853
  threadId,
38850
38854
  resourceId
38851
38855
  })
@@ -38860,14 +38864,11 @@ var require_chunk_OKHKG5C7 = __commonJS({
38860
38864
  threadId: threadId || ""
38861
38865
  };
38862
38866
  }
38863
- const allUIMessages = messageList.get.all.ui();
38864
- const currentUserMessages = allUIMessages.filter((m) => m.role === "user");
38865
- const lastUserMessageContent = currentUserMessages.at(-1)?.content ?? "";
38866
38867
  const [memoryMessages, memorySystemMessage] = threadId && memory ? await Promise.all([memory.rememberMessages({
38867
38868
  threadId,
38868
38869
  resourceId,
38869
38870
  config: memoryConfig,
38870
- vectorMessageSearch: lastUserMessageContent
38871
+ vectorMessageSearch: messageList.getLatestUserContent() || ""
38871
38872
  }).then((r) => r.messagesV2), memory.getSystemMessage({
38872
38873
  threadId,
38873
38874
  memoryConfig
@@ -39216,7 +39217,7 @@ var require_chunk_OKHKG5C7 = __commonJS({
39216
39217
  runId,
39217
39218
  runtimeContext
39218
39219
  });
39219
- const messageList = new chunkH5IRI46R_cjs.MessageList({
39220
+ const messageList = new chunkXUODQRSL_cjs.MessageList({
39220
39221
  threadId,
39221
39222
  resourceId,
39222
39223
  generateMessageId
@@ -39249,12 +39250,13 @@ var require_chunk_OKHKG5C7 = __commonJS({
39249
39250
  resourceId,
39250
39251
  memoryConfig
39251
39252
  });
39252
- const [memoryMessages, memorySystemMessage] = threadId && memory ? await Promise.all([memory.rememberMessages({
39253
+ let [memoryMessages, memorySystemMessage] = threadId && memory ? await Promise.all([memory.rememberMessages({
39253
39254
  threadId,
39254
39255
  resourceId,
39255
39256
  config: memoryConfig,
39256
- vectorMessageSearch: messageList.getLatestUserContent() || ""
39257
- }).then((r) => r.messages), memory.getSystemMessage({
39257
+ // The new user messages aren't in the list yet cause we add memory messages first to try to make sure ordering is correct (memory comes before new user messages)
39258
+ vectorMessageSearch: new chunkXUODQRSL_cjs.MessageList().add(messages, `user`).getLatestUserContent() || ""
39259
+ }).then((r) => r.messagesV2), memory.getSystemMessage({
39258
39260
  threadId,
39259
39261
  memoryConfig
39260
39262
  })]) : [[], null];
@@ -39263,10 +39265,28 @@ var require_chunk_OKHKG5C7 = __commonJS({
39263
39265
  runId,
39264
39266
  fetchedCount: memoryMessages.length
39265
39267
  });
39268
+ const resultsFromOtherThreads = memoryMessages.filter((m) => m.threadId !== threadId);
39269
+ if (resultsFromOtherThreads.length && !memorySystemMessage) {
39270
+ memorySystemMessage = ``;
39271
+ }
39272
+ if (resultsFromOtherThreads.length) {
39273
+ memorySystemMessage += `
39274
+ The following messages were remembered from a different conversation:
39275
+ <remembered_from_other_conversation>
39276
+ ${JSON.stringify(
39277
+ // get v1 since they're closer to CoreMessages (which get sent to the LLM) but also include timestamps
39278
+ new chunkXUODQRSL_cjs.MessageList().add(resultsFromOtherThreads, "memory").get.all.v1()
39279
+ )}
39280
+ <end_remembered_from_other_conversation>`;
39281
+ }
39266
39282
  if (memorySystemMessage) {
39267
39283
  messageList.addSystem(memorySystemMessage, "memory");
39268
39284
  }
39269
- messageList.add(memoryMessages, "memory").add(messages, "user");
39285
+ messageList.add(
39286
+ memoryMessages.filter((m) => m.threadId === threadId),
39287
+ // filter out messages from other threads. those are added to system message above
39288
+ "memory"
39289
+ ).add(messages, "user");
39270
39290
  const systemMessage = messageList.getSystemMessages()?.map((m) => m.content)?.join(`
39271
39291
  `) ?? void 0;
39272
39292
  const processedMemoryMessages = memory.processMessages({
@@ -39278,7 +39298,7 @@ var require_chunk_OKHKG5C7 = __commonJS({
39278
39298
  systemMessage,
39279
39299
  memorySystemMessage: memorySystemMessage || void 0
39280
39300
  });
39281
- const processedList = new chunkH5IRI46R_cjs.MessageList({
39301
+ const processedList = new chunkXUODQRSL_cjs.MessageList({
39282
39302
  threadId,
39283
39303
  resourceId
39284
39304
  }).addSystem(instructions || `${this.instructions}.`).addSystem(memorySystemMessage).add(context2 || [], "context").add(processedMemoryMessages, "memory").add(messageList.get.input.v2(), "user").get.all.prompt();
@@ -42753,18 +42773,18 @@ var require_chunk_OKHKG5C7 = __commonJS({
42753
42773
  // ../../packages/core/dist/agent/index.cjs
42754
42774
  var require_agent = __commonJS({
42755
42775
  "../../packages/core/dist/agent/index.cjs"(exports2) {
42756
- var chunkOKHKG5C7_cjs = require_chunk_OKHKG5C7();
42757
- var chunkH5IRI46R_cjs = require_chunk_H5IRI46R();
42776
+ var chunkZWYZGIV3_cjs = require_chunk_ZWYZGIV3();
42777
+ var chunkXUODQRSL_cjs = require_chunk_XUODQRSL();
42758
42778
  Object.defineProperty(exports2, "Agent", {
42759
42779
  enumerable: true,
42760
42780
  get: function() {
42761
- return chunkOKHKG5C7_cjs.Agent;
42781
+ return chunkZWYZGIV3_cjs.Agent;
42762
42782
  }
42763
42783
  });
42764
42784
  Object.defineProperty(exports2, "MessageList", {
42765
42785
  enumerable: true,
42766
42786
  get: function() {
42767
- return chunkH5IRI46R_cjs.MessageList;
42787
+ return chunkXUODQRSL_cjs.MessageList;
42768
42788
  }
42769
42789
  });
42770
42790
  }
@@ -42781,6 +42801,11 @@ var UpstashStore = class extends MastraStorage {
42781
42801
  token: config.token
42782
42802
  });
42783
42803
  }
42804
+ get supports() {
42805
+ return {
42806
+ selectByIncludeResourceScope: true
42807
+ };
42808
+ }
42784
42809
  transformEvalRecord(record) {
42785
42810
  let result = record.result;
42786
42811
  if (typeof result === "string") {
@@ -42865,7 +42890,8 @@ var UpstashStore = class extends MastraStorage {
42865
42890
  return totalDeleted;
42866
42891
  }
42867
42892
  getMessageKey(threadId, messageId) {
42868
- return this.getKey(TABLE_MESSAGES, { threadId, id: messageId });
42893
+ const key = this.getKey(TABLE_MESSAGES, { threadId, id: messageId });
42894
+ return key;
42869
42895
  }
42870
42896
  getThreadMessagesKey(threadId) {
42871
42897
  return `thread:${threadId}:messages`;
@@ -43238,6 +43264,14 @@ var UpstashStore = class extends MastraStorage {
43238
43264
  async saveMessages(args) {
43239
43265
  const { messages, format = "v1" } = args;
43240
43266
  if (messages.length === 0) return [];
43267
+ const threadId = messages[0]?.threadId;
43268
+ if (!threadId) {
43269
+ throw new Error("Thread ID is required");
43270
+ }
43271
+ const thread = await this.getThreadById({ threadId });
43272
+ if (!thread) {
43273
+ throw new Error(`Thread ${threadId} not found`);
43274
+ }
43241
43275
  const messagesWithIndex = messages.map((message, index) => ({
43242
43276
  ...message,
43243
43277
  _index: index
@@ -43248,7 +43282,8 @@ var UpstashStore = class extends MastraStorage {
43248
43282
  const pipeline = this.redis.pipeline();
43249
43283
  for (const message of batch) {
43250
43284
  const key = this.getMessageKey(message.threadId, message.id);
43251
- const score = message._index !== void 0 ? message._index : new Date(message.createdAt).getTime();
43285
+ const createdAtScore = new Date(message.createdAt).getTime();
43286
+ const score = message._index !== void 0 ? message._index : createdAtScore;
43252
43287
  pipeline.set(key, message);
43253
43288
  pipeline.zadd(this.getThreadMessagesKey(message.threadId), {
43254
43289
  score,
@@ -43343,38 +43378,55 @@ var UpstashStore = class extends MastraStorage {
43343
43378
  limit = Number.MAX_SAFE_INTEGER;
43344
43379
  }
43345
43380
  const messageIds = /* @__PURE__ */ new Set();
43381
+ const messageIdToThreadIds = {};
43346
43382
  if (limit === 0 && !selectBy?.include) {
43347
43383
  return [];
43348
43384
  }
43349
43385
  if (selectBy?.include?.length) {
43350
43386
  for (const item of selectBy.include) {
43351
43387
  messageIds.add(item.id);
43352
- if (item.withPreviousMessages || item.withNextMessages) {
43353
- const rank = await this.redis.zrank(threadMessagesKey, item.id);
43354
- if (rank === null) continue;
43355
- if (item.withPreviousMessages) {
43356
- const start = Math.max(0, rank - item.withPreviousMessages);
43357
- const prevIds = rank === 0 ? [] : await this.redis.zrange(threadMessagesKey, start, rank - 1);
43358
- prevIds.forEach((id) => messageIds.add(id));
43359
- }
43360
- if (item.withNextMessages) {
43361
- const nextIds = await this.redis.zrange(threadMessagesKey, rank + 1, rank + item.withNextMessages);
43362
- nextIds.forEach((id) => messageIds.add(id));
43363
- }
43388
+ const itemThreadId = item.threadId || threadId;
43389
+ messageIdToThreadIds[item.id] = itemThreadId;
43390
+ const itemThreadMessagesKey = this.getThreadMessagesKey(itemThreadId);
43391
+ const rank = await this.redis.zrank(itemThreadMessagesKey, item.id);
43392
+ if (rank === null) continue;
43393
+ if (item.withPreviousMessages) {
43394
+ const start = Math.max(0, rank - item.withPreviousMessages);
43395
+ const prevIds = rank === 0 ? [] : await this.redis.zrange(itemThreadMessagesKey, start, rank - 1);
43396
+ prevIds.forEach((id) => {
43397
+ messageIds.add(id);
43398
+ messageIdToThreadIds[id] = itemThreadId;
43399
+ });
43400
+ }
43401
+ if (item.withNextMessages) {
43402
+ const nextIds = await this.redis.zrange(itemThreadMessagesKey, rank + 1, rank + item.withNextMessages);
43403
+ nextIds.forEach((id) => {
43404
+ messageIds.add(id);
43405
+ messageIdToThreadIds[id] = itemThreadId;
43406
+ });
43364
43407
  }
43365
43408
  }
43366
43409
  }
43367
43410
  if (limit === Number.MAX_SAFE_INTEGER) {
43368
43411
  const allIds = await this.redis.zrange(threadMessagesKey, 0, -1);
43369
- allIds.forEach((id) => messageIds.add(id));
43412
+ allIds.forEach((id) => {
43413
+ messageIds.add(id);
43414
+ messageIdToThreadIds[id] = threadId;
43415
+ });
43370
43416
  } else if (limit > 0) {
43371
43417
  const latestIds = await this.redis.zrange(threadMessagesKey, -limit, -1);
43372
- latestIds.forEach((id) => messageIds.add(id));
43418
+ latestIds.forEach((id) => {
43419
+ messageIds.add(id);
43420
+ messageIdToThreadIds[id] = threadId;
43421
+ });
43373
43422
  }
43374
43423
  const messages = (await Promise.all(
43375
- Array.from(messageIds).map(
43376
- async (id) => this.redis.get(this.getMessageKey(threadId, id))
43377
- )
43424
+ Array.from(messageIds).map(async (id) => {
43425
+ const tId = messageIdToThreadIds[id] || threadId;
43426
+ const byThreadId = await this.redis.get(this.getMessageKey(tId, id));
43427
+ if (byThreadId) return byThreadId;
43428
+ return null;
43429
+ })
43378
43430
  )).filter((msg) => msg !== null);
43379
43431
  messages.sort((a, b) => allMessageIds.indexOf(a.id) - allMessageIds.indexOf(b.id));
43380
43432
  const prepared = messages.filter((message) => message !== null && message !== void 0).map((message) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mastra/upstash",
3
- "version": "0.10.3-alpha.1",
3
+ "version": "0.10.3-alpha.2",
4
4
  "description": "Upstash provider for Mastra - includes both vector and db storage capabilities",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -31,9 +31,9 @@
31
31
  "tsup": "^8.5.0",
32
32
  "typescript": "^5.8.2",
33
33
  "vitest": "^3.2.2",
34
- "@internal/storage-test-utils": "0.0.6",
35
34
  "@internal/lint": "0.0.10",
36
- "@mastra/core": "0.10.4-alpha.1"
35
+ "@internal/storage-test-utils": "0.0.6",
36
+ "@mastra/core": "0.10.4-alpha.2"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@mastra/core": "^0.10.2-alpha.0"
@@ -36,6 +36,14 @@ export class UpstashStore extends MastraStorage {
36
36
  });
37
37
  }
38
38
 
39
+ public get supports(): {
40
+ selectByIncludeResourceScope: boolean;
41
+ } {
42
+ return {
43
+ selectByIncludeResourceScope: true,
44
+ };
45
+ }
46
+
39
47
  private transformEvalRecord(record: Record<string, any>): EvalRow {
40
48
  // Parse JSON strings if needed
41
49
  let result = record.result;
@@ -136,7 +144,8 @@ export class UpstashStore extends MastraStorage {
136
144
  }
137
145
 
138
146
  private getMessageKey(threadId: string, messageId: string): string {
139
- return this.getKey(TABLE_MESSAGES, { threadId, id: messageId });
147
+ const key = this.getKey(TABLE_MESSAGES, { threadId, id: messageId });
148
+ return key;
140
149
  }
141
150
 
142
151
  private getThreadMessagesKey(threadId: string): string {
@@ -664,6 +673,17 @@ export class UpstashStore extends MastraStorage {
664
673
  const { messages, format = 'v1' } = args;
665
674
  if (messages.length === 0) return [];
666
675
 
676
+ const threadId = messages[0]?.threadId;
677
+ if (!threadId) {
678
+ throw new Error('Thread ID is required');
679
+ }
680
+
681
+ // Check if thread exists
682
+ const thread = await this.getThreadById({ threadId });
683
+ if (!thread) {
684
+ throw new Error(`Thread ${threadId} not found`);
685
+ }
686
+
667
687
  // Add an index to each message to maintain order
668
688
  const messagesWithIndex = messages.map((message, index) => ({
669
689
  ...message,
@@ -676,7 +696,8 @@ export class UpstashStore extends MastraStorage {
676
696
  const pipeline = this.redis.pipeline();
677
697
  for (const message of batch) {
678
698
  const key = this.getMessageKey(message.threadId!, message.id);
679
- const score = message._index !== undefined ? message._index : new Date(message.createdAt).getTime();
699
+ const createdAtScore = new Date(message.createdAt).getTime();
700
+ const score = message._index !== undefined ? message._index : createdAtScore;
680
701
 
681
702
  // Store the message data
682
703
  pipeline.set(key, message);
@@ -844,6 +865,7 @@ export class UpstashStore extends MastraStorage {
844
865
  }
845
866
 
846
867
  const messageIds = new Set<string>();
868
+ const messageIdToThreadIds: Record<string, string> = {};
847
869
 
848
870
  if (limit === 0 && !selectBy?.include) {
849
871
  return [];
@@ -854,23 +876,32 @@ export class UpstashStore extends MastraStorage {
854
876
  for (const item of selectBy.include) {
855
877
  messageIds.add(item.id);
856
878
 
857
- if (item.withPreviousMessages || item.withNextMessages) {
858
- // Get the rank of this message in the sorted set
859
- const rank = await this.redis.zrank(threadMessagesKey, item.id);
860
- if (rank === null) continue;
861
-
862
- // Get previous messages if requested
863
- if (item.withPreviousMessages) {
864
- const start = Math.max(0, rank - item.withPreviousMessages);
865
- const prevIds = rank === 0 ? [] : await this.redis.zrange(threadMessagesKey, start, rank - 1);
866
- prevIds.forEach(id => messageIds.add(id as string));
867
- }
879
+ // Use per-include threadId if present, else fallback to main threadId
880
+ const itemThreadId = item.threadId || threadId;
881
+ messageIdToThreadIds[item.id] = itemThreadId;
882
+ const itemThreadMessagesKey = this.getThreadMessagesKey(itemThreadId);
883
+
884
+ // Get the rank of this message in the sorted set
885
+ const rank = await this.redis.zrank(itemThreadMessagesKey, item.id);
886
+ if (rank === null) continue;
887
+
888
+ // Get previous messages if requested
889
+ if (item.withPreviousMessages) {
890
+ const start = Math.max(0, rank - item.withPreviousMessages);
891
+ const prevIds = rank === 0 ? [] : await this.redis.zrange(itemThreadMessagesKey, start, rank - 1);
892
+ prevIds.forEach(id => {
893
+ messageIds.add(id as string);
894
+ messageIdToThreadIds[id as string] = itemThreadId;
895
+ });
896
+ }
868
897
 
869
- // Get next messages if requested
870
- if (item.withNextMessages) {
871
- const nextIds = await this.redis.zrange(threadMessagesKey, rank + 1, rank + item.withNextMessages);
872
- nextIds.forEach(id => messageIds.add(id as string));
873
- }
898
+ // Get next messages if requested
899
+ if (item.withNextMessages) {
900
+ const nextIds = await this.redis.zrange(itemThreadMessagesKey, rank + 1, rank + item.withNextMessages);
901
+ nextIds.forEach(id => {
902
+ messageIds.add(id as string);
903
+ messageIdToThreadIds[id as string] = itemThreadId;
904
+ });
874
905
  }
875
906
  }
876
907
  }
@@ -879,19 +910,29 @@ export class UpstashStore extends MastraStorage {
879
910
  if (limit === Number.MAX_SAFE_INTEGER) {
880
911
  // Get all messages
881
912
  const allIds = await this.redis.zrange(threadMessagesKey, 0, -1);
882
- allIds.forEach(id => messageIds.add(id as string));
913
+ allIds.forEach(id => {
914
+ messageIds.add(id as string);
915
+ messageIdToThreadIds[id as string] = threadId;
916
+ });
883
917
  } else if (limit > 0) {
884
918
  // Get limited number of recent messages
885
919
  const latestIds = await this.redis.zrange(threadMessagesKey, -limit, -1);
886
- latestIds.forEach(id => messageIds.add(id as string));
920
+ latestIds.forEach(id => {
921
+ messageIds.add(id as string);
922
+ messageIdToThreadIds[id as string] = threadId;
923
+ });
887
924
  }
888
925
 
889
926
  // Fetch all needed messages in parallel
890
927
  const messages = (
891
928
  await Promise.all(
892
- Array.from(messageIds).map(async id =>
893
- this.redis.get<MastraMessageV2 & { _index?: number }>(this.getMessageKey(threadId, id)),
894
- ),
929
+ Array.from(messageIds).map(async id => {
930
+ const tId = messageIdToThreadIds[id] || threadId;
931
+ const byThreadId = await this.redis.get<MastraMessageV2 & { _index?: number }>(this.getMessageKey(tId, id));
932
+ if (byThreadId) return byThreadId;
933
+
934
+ return null;
935
+ }),
895
936
  )
896
937
  ).filter(msg => msg !== null) as (MastraMessageV2 & { _index?: number })[];
897
938
 
@@ -1,5 +1,10 @@
1
1
  import { randomUUID } from 'crypto';
2
- import { createSampleMessageV2, createSampleThread, createSampleWorkflowSnapshot } from '@internal/storage-test-utils';
2
+ import {
3
+ checkWorkflowSnapshot,
4
+ createSampleMessageV2,
5
+ createSampleThread,
6
+ createSampleWorkflowSnapshot,
7
+ } from '@internal/storage-test-utils';
3
8
  import type { MastraMessageV2 } from '@mastra/core';
4
9
  import type { TABLE_NAMES } from '@mastra/core/storage';
5
10
  import {
@@ -51,13 +56,6 @@ const createSampleEval = (agentName: string, isTest = false) => {
51
56
  };
52
57
  };
53
58
 
54
- const checkWorkflowSnapshot = (snapshot: WorkflowRunState | string, stepId: string, status: string) => {
55
- if (typeof snapshot === 'string') {
56
- throw new Error('Expected WorkflowRunState, got string');
57
- }
58
- expect(snapshot.context?.[stepId]?.status).toBe(status);
59
- };
60
-
61
59
  describe('UpstashStore', () => {
62
60
  let store: UpstashStore;
63
61
  const testTableName = 'test_table';
@@ -154,7 +152,7 @@ describe('UpstashStore', () => {
154
152
 
155
153
  it('should get threads by resource ID', async () => {
156
154
  const thread1 = createSampleThread();
157
- const thread2 = { ...createSampleThread(), resourceId: thread1.resourceId };
155
+ const thread2 = createSampleThread({ resourceId: thread1.resourceId });
158
156
  const threads = [thread1, thread2];
159
157
 
160
158
  const resourceId = threads[0].resourceId;
@@ -187,7 +185,7 @@ describe('UpstashStore', () => {
187
185
  it('should fetch >100000 threads by resource ID', async () => {
188
186
  const resourceId = `resource-${randomUUID()}`;
189
187
  const total = 100_000;
190
- const threads = Array.from({ length: total }, () => ({ ...createSampleThread(), resourceId }));
188
+ const threads = Array.from({ length: total }, () => createSampleThread({ resourceId }));
191
189
 
192
190
  await store.batchInsert({ tableName: TABLE_THREADS, records: threads });
193
191
 
@@ -300,13 +298,117 @@ describe('UpstashStore', () => {
300
298
  createSampleMessageV2({ threadId, content: 'Third' }),
301
299
  ];
302
300
 
303
- await store.saveMessages({ messages: messages, format: 'v2' });
301
+ await store.saveMessages({ messages, format: 'v2' });
304
302
 
305
303
  const retrievedMessages = await store.getMessages({ threadId, format: 'v2' });
306
304
  expect(retrievedMessages).toHaveLength(3);
307
305
  expect(retrievedMessages.map((m: any) => m.content.parts[0].text)).toEqual(['First', 'Second', 'Third']);
308
306
  });
309
307
 
308
+ it('should retrieve messages w/ next/prev messages by message id + resource id', async () => {
309
+ const thread = createSampleThread({ id: 'thread-one' });
310
+ await store.saveThread({ thread });
311
+
312
+ const thread2 = createSampleThread({ id: 'thread-two' });
313
+ await store.saveThread({ thread: thread2 });
314
+
315
+ const thread3 = createSampleThread({ id: 'thread-three' });
316
+ await store.saveThread({ thread: thread3 });
317
+
318
+ const messages: MastraMessageV2[] = [
319
+ createSampleMessageV2({ threadId: 'thread-one', content: 'First', resourceId: 'cross-thread-resource' }),
320
+ createSampleMessageV2({ threadId: 'thread-one', content: 'Second', resourceId: 'cross-thread-resource' }),
321
+ createSampleMessageV2({ threadId: 'thread-one', content: 'Third', resourceId: 'cross-thread-resource' }),
322
+
323
+ createSampleMessageV2({ threadId: 'thread-two', content: 'Fourth', resourceId: 'cross-thread-resource' }),
324
+ createSampleMessageV2({ threadId: 'thread-two', content: 'Fifth', resourceId: 'cross-thread-resource' }),
325
+ createSampleMessageV2({ threadId: 'thread-two', content: 'Sixth', resourceId: 'cross-thread-resource' }),
326
+
327
+ createSampleMessageV2({ threadId: 'thread-three', content: 'Seventh', resourceId: 'other-resource' }),
328
+ createSampleMessageV2({ threadId: 'thread-three', content: 'Eighth', resourceId: 'other-resource' }),
329
+ ];
330
+
331
+ await store.saveMessages({ messages: messages, format: 'v2' });
332
+
333
+ const retrievedMessages = await store.getMessages({ threadId: 'thread-one', format: 'v2' });
334
+ expect(retrievedMessages).toHaveLength(3);
335
+ expect(retrievedMessages.map((m: any) => m.content.parts[0].text)).toEqual(['First', 'Second', 'Third']);
336
+
337
+ const retrievedMessages2 = await store.getMessages({ threadId: 'thread-two', format: 'v2' });
338
+ expect(retrievedMessages2).toHaveLength(3);
339
+ expect(retrievedMessages2.map((m: any) => m.content.parts[0].text)).toEqual(['Fourth', 'Fifth', 'Sixth']);
340
+
341
+ const retrievedMessages3 = await store.getMessages({ threadId: 'thread-three', format: 'v2' });
342
+ expect(retrievedMessages3).toHaveLength(2);
343
+ expect(retrievedMessages3.map((m: any) => m.content.parts[0].text)).toEqual(['Seventh', 'Eighth']);
344
+
345
+ const crossThreadMessages = await store.getMessages({
346
+ threadId: 'thread-doesnt-exist',
347
+ format: 'v2',
348
+ selectBy: {
349
+ last: 0,
350
+ include: [
351
+ {
352
+ id: messages[1].id,
353
+ threadId: 'thread-one',
354
+ withNextMessages: 2,
355
+ withPreviousMessages: 2,
356
+ },
357
+ {
358
+ id: messages[4].id,
359
+ threadId: 'thread-two',
360
+ withPreviousMessages: 2,
361
+ withNextMessages: 2,
362
+ },
363
+ ],
364
+ },
365
+ });
366
+
367
+ expect(crossThreadMessages).toHaveLength(6);
368
+ expect(crossThreadMessages.filter(m => m.threadId === `thread-one`)).toHaveLength(3);
369
+ expect(crossThreadMessages.filter(m => m.threadId === `thread-two`)).toHaveLength(3);
370
+
371
+ const crossThreadMessages2 = await store.getMessages({
372
+ threadId: 'thread-one',
373
+ format: 'v2',
374
+ selectBy: {
375
+ last: 0,
376
+ include: [
377
+ {
378
+ id: messages[4].id,
379
+ threadId: 'thread-two',
380
+ withPreviousMessages: 1,
381
+ withNextMessages: 1,
382
+ },
383
+ ],
384
+ },
385
+ });
386
+
387
+ expect(crossThreadMessages2).toHaveLength(3);
388
+ expect(crossThreadMessages2.filter(m => m.threadId === `thread-one`)).toHaveLength(0);
389
+ expect(crossThreadMessages2.filter(m => m.threadId === `thread-two`)).toHaveLength(3);
390
+
391
+ const crossThreadMessages3 = await store.getMessages({
392
+ threadId: 'thread-two',
393
+ format: 'v2',
394
+ selectBy: {
395
+ last: 0,
396
+ include: [
397
+ {
398
+ id: messages[1].id,
399
+ threadId: 'thread-one',
400
+ withNextMessages: 1,
401
+ withPreviousMessages: 1,
402
+ },
403
+ ],
404
+ },
405
+ });
406
+
407
+ expect(crossThreadMessages3).toHaveLength(3);
408
+ expect(crossThreadMessages3.filter(m => m.threadId === `thread-one`)).toHaveLength(3);
409
+ expect(crossThreadMessages3.filter(m => m.threadId === `thread-two`)).toHaveLength(0);
410
+ });
411
+
310
412
  it('should handle empty message array', async () => {
311
413
  const result = await store.saveMessages({ messages: [] });
312
414
  expect(result).toEqual([]);
@@ -1219,10 +1321,7 @@ describe('UpstashStore', () => {
1219
1321
  describe('Enhanced existing methods with pagination', () => {
1220
1322
  it('should support pagination in getThreadsByResourceId', async () => {
1221
1323
  const resourceId = 'enhanced-resource';
1222
- const threads = Array.from({ length: 17 }, () => ({
1223
- ...createSampleThread(),
1224
- resourceId,
1225
- }));
1324
+ const threads = Array.from({ length: 17 }, () => createSampleThread({ resourceId }));
1226
1325
 
1227
1326
  for (const thread of threads) {
1228
1327
  await store.saveThread({ thread });