@mastra/memory 0.2.10 → 0.2.11-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,29 +1,29 @@
1
1
 
2
- > @mastra/memory@0.2.10-alpha.5 build /home/runner/work/mastra/mastra/packages/memory
2
+ > @mastra/memory@0.2.11-alpha.2 build /home/runner/work/mastra/mastra/packages/memory
3
3
  > pnpm run check && tsup src/index.ts src/processors/index.ts --format esm,cjs --experimental-dts --clean --treeshake=smallest --splitting
4
4
 
5
5
 
6
- > @mastra/memory@0.2.10-alpha.5 check /home/runner/work/mastra/mastra/packages/memory
6
+ > @mastra/memory@0.2.11-alpha.2 check /home/runner/work/mastra/mastra/packages/memory
7
7
  > tsc --noEmit
8
8
 
9
9
  CLI Building entry: src/index.ts, src/processors/index.ts
10
10
  CLI Using tsconfig: tsconfig.json
11
11
  CLI tsup v8.4.0
12
12
  TSC Build start
13
- TSC ⚡️ Build success in 9816ms
13
+ TSC ⚡️ Build success in 10363ms
14
14
  DTS Build start
15
15
  CLI Target: es2022
16
16
  Analysis will use the bundled TypeScript version 5.8.2
17
17
  Writing package typings: /home/runner/work/mastra/mastra/packages/memory/dist/_tsup-dts-rollup.d.ts
18
18
  Analysis will use the bundled TypeScript version 5.8.2
19
19
  Writing package typings: /home/runner/work/mastra/mastra/packages/memory/dist/_tsup-dts-rollup.d.cts
20
- DTS ⚡️ Build success in 9724ms
20
+ DTS ⚡️ Build success in 12416ms
21
21
  CLI Cleaning output folder
22
22
  ESM Build start
23
23
  CJS Build start
24
- ESM dist/index.js 15.44 KB
25
- ESM dist/processors/index.js 5.33 KB
26
- ESM ⚡️ Build success in 696ms
27
- CJS dist/index.cjs 15.71 KB
24
+ CJS dist/index.cjs 17.58 KB
28
25
  CJS dist/processors/index.cjs 5.54 KB
29
- CJS ⚡️ Build success in 696ms
26
+ CJS ⚡️ Build success in 946ms
27
+ ESM dist/index.js 17.39 KB
28
+ ESM dist/processors/index.js 5.33 KB
29
+ ESM ⚡️ Build success in 950ms
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # @mastra/memory
2
2
 
3
+ ## 0.2.11-alpha.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 5c6825c: [MASTRA-2782] removed tiktoken from memory chunktext
8
+
9
+ ## 0.2.11-alpha.1
10
+
11
+ ### Patch Changes
12
+
13
+ - 6f92295: Fixed an issue where some user messages and llm messages would have the exact same createdAt date, leading to incorrect message ordering. Added a fix for new messages as well as any that were saved before the fix in the wrong order
14
+ - Updated dependencies [8a8a73b]
15
+ - Updated dependencies [6f92295]
16
+ - @mastra/core@0.8.4-alpha.1
17
+
18
+ ## 0.2.11-alpha.0
19
+
20
+ ### Patch Changes
21
+
22
+ - Updated dependencies [03f3cd0]
23
+ - @mastra/core@0.8.4-alpha.0
24
+
3
25
  ## 0.2.10
4
26
 
5
27
  ### Patch Changes
@@ -76,6 +76,12 @@ export declare class Memory extends MastraMemory {
76
76
  getTools(config?: MemoryConfig): Record<string, CoreTool>;
77
77
  }
78
78
 
79
+ /**
80
+ * Self-heals message ordering to ensure tool calls are directly before their matching tool results.
81
+ * This is needed due to a bug where messages were saved in the wrong order. That bug is fixed, but this code ensures any tool calls saved in the wrong order in the past will still be usable now.
82
+ */
83
+ export declare function reorderToolCallsAndResults(messages: MessageType[]): MessageType[];
84
+
79
85
  /**
80
86
  * Limits the total number of tokens in the messages.
81
87
  * Uses js-tiktoken with o200k_base encoding by default for accurate token counting with modern models.
@@ -76,6 +76,12 @@ export declare class Memory extends MastraMemory {
76
76
  getTools(config?: MemoryConfig): Record<string, CoreTool>;
77
77
  }
78
78
 
79
+ /**
80
+ * Self-heals message ordering to ensure tool calls are directly before their matching tool results.
81
+ * This is needed due to a bug where messages were saved in the wrong order. That bug is fixed, but this code ensures any tool calls saved in the wrong order in the past will still be usable now.
82
+ */
83
+ export declare function reorderToolCallsAndResults(messages: MessageType[]): MessageType[];
84
+
79
85
  /**
80
86
  * Limits the total number of tokens in the messages.
81
87
  * Uses js-tiktoken with o200k_base encoding by default for accurate token counting with modern models.
package/dist/index.cjs CHANGED
@@ -3,14 +3,11 @@
3
3
  var core = require('@mastra/core');
4
4
  var memory = require('@mastra/core/memory');
5
5
  var ai = require('ai');
6
- var lite = require('js-tiktoken/lite');
7
- var o200k_base = require('js-tiktoken/ranks/o200k_base');
8
6
  var xxhash = require('xxhash-wasm');
9
7
  var zod = require('zod');
10
8
 
11
9
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
12
10
 
13
- var o200k_base__default = /*#__PURE__*/_interopDefault(o200k_base);
14
11
  var xxhash__default = /*#__PURE__*/_interopDefault(xxhash);
15
12
 
16
13
  // src/index.ts
@@ -41,8 +38,50 @@ var updateWorkingMemoryTool = {
41
38
  }
42
39
  };
43
40
 
41
+ // src/utils/index.ts
42
+ var isToolCallWithId = (message, targetToolCallId) => {
43
+ if (!message || !Array.isArray(message.content)) return false;
44
+ return message.content.some(
45
+ (part) => part && typeof part === "object" && "type" in part && part.type === "tool-call" && "toolCallId" in part && part.toolCallId === targetToolCallId
46
+ );
47
+ };
48
+ var getToolResultIndexById = (id, results) => results.findIndex((message) => {
49
+ if (!Array.isArray(message?.content)) return false;
50
+ return message.content.some(
51
+ (part) => part && typeof part === "object" && "type" in part && part.type === "tool-result" && "toolCallId" in part && part.toolCallId === id
52
+ );
53
+ });
54
+ function reorderToolCallsAndResults(messages) {
55
+ if (!messages.length) return messages;
56
+ const results = [...messages];
57
+ const toolCallIds = /* @__PURE__ */ new Set();
58
+ for (const message of results) {
59
+ if (!Array.isArray(message.content)) continue;
60
+ for (const part of message.content) {
61
+ if (part && typeof part === "object" && "type" in part && part.type === "tool-result" && "toolCallId" in part && part.toolCallId) {
62
+ toolCallIds.add(part.toolCallId);
63
+ }
64
+ }
65
+ }
66
+ for (const toolCallId of toolCallIds) {
67
+ const resultIndex = getToolResultIndexById(toolCallId, results);
68
+ const oneMessagePrev = results[resultIndex - 1];
69
+ if (isToolCallWithId(oneMessagePrev, toolCallId)) {
70
+ continue;
71
+ }
72
+ const toolCallIndex = results.findIndex((message) => isToolCallWithId(message, toolCallId));
73
+ if (toolCallIndex !== -1 && toolCallIndex !== resultIndex - 1) {
74
+ const toolCall = results[toolCallIndex];
75
+ if (!toolCall) continue;
76
+ results.splice(toolCallIndex, 1);
77
+ results.splice(getToolResultIndexById(toolCallId, results), 0, toolCall);
78
+ }
79
+ }
80
+ return results;
81
+ }
82
+
44
83
  // src/index.ts
45
- var encoder = new lite.Tiktoken(o200k_base__default.default);
84
+ var CHARS_PER_TOKEN = 4;
46
85
  var Memory = class extends memory.MastraMemory {
47
86
  constructor(config = {}) {
48
87
  super({ name: "Memory", ...config });
@@ -123,8 +162,10 @@ var Memory = class extends memory.MastraMemory {
123
162
  },
124
163
  threadConfig: config
125
164
  });
126
- const messages = this.parseMessages(rawMessages);
127
- const uiMessages = this.convertToUIMessages(rawMessages);
165
+ const orderedByDate = rawMessages.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
166
+ const reorderedToolCalls = reorderToolCallsAndResults(orderedByDate);
167
+ const messages = this.parseMessages(reorderedToolCalls);
168
+ const uiMessages = this.convertToUIMessages(reorderedToolCalls);
128
169
  return { messages, uiMessages };
129
170
  }
130
171
  async rememberMessages({
@@ -193,19 +234,22 @@ var Memory = class extends memory.MastraMemory {
193
234
  async deleteThread(threadId) {
194
235
  await this.storage.__deleteThread({ threadId });
195
236
  }
196
- chunkText(text, size = 4096) {
197
- const tokens = encoder.encode(text);
237
+ chunkText(text, tokenSize = 4096) {
238
+ const charSize = tokenSize * CHARS_PER_TOKEN;
198
239
  const chunks = [];
199
- let currentChunk = [];
200
- for (const token of tokens) {
201
- currentChunk.push(token);
202
- if (currentChunk.length >= size) {
203
- chunks.push(encoder.decode(currentChunk));
204
- currentChunk = [];
240
+ let currentChunk = "";
241
+ const words = text.split(/\s+/);
242
+ for (const word of words) {
243
+ const wordWithSpace = currentChunk ? " " + word : word;
244
+ if (currentChunk.length + wordWithSpace.length > charSize) {
245
+ chunks.push(currentChunk);
246
+ currentChunk = word;
247
+ } else {
248
+ currentChunk += wordWithSpace;
205
249
  }
206
250
  }
207
- if (currentChunk.length > 0) {
208
- chunks.push(encoder.decode(currentChunk));
251
+ if (currentChunk) {
252
+ chunks.push(currentChunk);
209
253
  }
210
254
  return chunks;
211
255
  }
package/dist/index.js CHANGED
@@ -1,8 +1,6 @@
1
1
  import { deepMerge } from '@mastra/core';
2
2
  import { MastraMemory } from '@mastra/core/memory';
3
3
  import { embedMany } from 'ai';
4
- import { Tiktoken } from 'js-tiktoken/lite';
5
- import o200k_base from 'js-tiktoken/ranks/o200k_base';
6
4
  import xxhash from 'xxhash-wasm';
7
5
  import { z } from 'zod';
8
6
 
@@ -34,8 +32,50 @@ var updateWorkingMemoryTool = {
34
32
  }
35
33
  };
36
34
 
35
+ // src/utils/index.ts
36
+ var isToolCallWithId = (message, targetToolCallId) => {
37
+ if (!message || !Array.isArray(message.content)) return false;
38
+ return message.content.some(
39
+ (part) => part && typeof part === "object" && "type" in part && part.type === "tool-call" && "toolCallId" in part && part.toolCallId === targetToolCallId
40
+ );
41
+ };
42
+ var getToolResultIndexById = (id, results) => results.findIndex((message) => {
43
+ if (!Array.isArray(message?.content)) return false;
44
+ return message.content.some(
45
+ (part) => part && typeof part === "object" && "type" in part && part.type === "tool-result" && "toolCallId" in part && part.toolCallId === id
46
+ );
47
+ });
48
+ function reorderToolCallsAndResults(messages) {
49
+ if (!messages.length) return messages;
50
+ const results = [...messages];
51
+ const toolCallIds = /* @__PURE__ */ new Set();
52
+ for (const message of results) {
53
+ if (!Array.isArray(message.content)) continue;
54
+ for (const part of message.content) {
55
+ if (part && typeof part === "object" && "type" in part && part.type === "tool-result" && "toolCallId" in part && part.toolCallId) {
56
+ toolCallIds.add(part.toolCallId);
57
+ }
58
+ }
59
+ }
60
+ for (const toolCallId of toolCallIds) {
61
+ const resultIndex = getToolResultIndexById(toolCallId, results);
62
+ const oneMessagePrev = results[resultIndex - 1];
63
+ if (isToolCallWithId(oneMessagePrev, toolCallId)) {
64
+ continue;
65
+ }
66
+ const toolCallIndex = results.findIndex((message) => isToolCallWithId(message, toolCallId));
67
+ if (toolCallIndex !== -1 && toolCallIndex !== resultIndex - 1) {
68
+ const toolCall = results[toolCallIndex];
69
+ if (!toolCall) continue;
70
+ results.splice(toolCallIndex, 1);
71
+ results.splice(getToolResultIndexById(toolCallId, results), 0, toolCall);
72
+ }
73
+ }
74
+ return results;
75
+ }
76
+
37
77
  // src/index.ts
38
- var encoder = new Tiktoken(o200k_base);
78
+ var CHARS_PER_TOKEN = 4;
39
79
  var Memory = class extends MastraMemory {
40
80
  constructor(config = {}) {
41
81
  super({ name: "Memory", ...config });
@@ -116,8 +156,10 @@ var Memory = class extends MastraMemory {
116
156
  },
117
157
  threadConfig: config
118
158
  });
119
- const messages = this.parseMessages(rawMessages);
120
- const uiMessages = this.convertToUIMessages(rawMessages);
159
+ const orderedByDate = rawMessages.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
160
+ const reorderedToolCalls = reorderToolCallsAndResults(orderedByDate);
161
+ const messages = this.parseMessages(reorderedToolCalls);
162
+ const uiMessages = this.convertToUIMessages(reorderedToolCalls);
121
163
  return { messages, uiMessages };
122
164
  }
123
165
  async rememberMessages({
@@ -186,19 +228,22 @@ var Memory = class extends MastraMemory {
186
228
  async deleteThread(threadId) {
187
229
  await this.storage.__deleteThread({ threadId });
188
230
  }
189
- chunkText(text, size = 4096) {
190
- const tokens = encoder.encode(text);
231
+ chunkText(text, tokenSize = 4096) {
232
+ const charSize = tokenSize * CHARS_PER_TOKEN;
191
233
  const chunks = [];
192
- let currentChunk = [];
193
- for (const token of tokens) {
194
- currentChunk.push(token);
195
- if (currentChunk.length >= size) {
196
- chunks.push(encoder.decode(currentChunk));
197
- currentChunk = [];
234
+ let currentChunk = "";
235
+ const words = text.split(/\s+/);
236
+ for (const word of words) {
237
+ const wordWithSpace = currentChunk ? " " + word : word;
238
+ if (currentChunk.length + wordWithSpace.length > charSize) {
239
+ chunks.push(currentChunk);
240
+ currentChunk = word;
241
+ } else {
242
+ currentChunk += wordWithSpace;
198
243
  }
199
244
  }
200
- if (currentChunk.length > 0) {
201
- chunks.push(encoder.decode(currentChunk));
245
+ if (currentChunk) {
246
+ chunks.push(currentChunk);
202
247
  }
203
248
  return chunks;
204
249
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mastra/memory",
3
- "version": "0.2.10",
3
+ "version": "0.2.11-alpha.2",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -41,7 +41,7 @@
41
41
  "redis": "^4.7.0",
42
42
  "xxhash-wasm": "^1.1.0",
43
43
  "zod": "^3.24.2",
44
- "@mastra/core": "^0.8.3"
44
+ "@mastra/core": "^0.8.4-alpha.1"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@ai-sdk/openai": "^1.3.3",
package/src/index.ts CHANGED
@@ -4,12 +4,13 @@ import { MastraMemory } from '@mastra/core/memory';
4
4
  import type { MessageType, MemoryConfig, SharedMemoryConfig, StorageThreadType } from '@mastra/core/memory';
5
5
  import type { StorageGetMessagesArg } from '@mastra/core/storage';
6
6
  import { embedMany } from 'ai';
7
- import { Tiktoken } from 'js-tiktoken/lite';
8
- import o200k_base from 'js-tiktoken/ranks/o200k_base';
7
+
9
8
  import xxhash from 'xxhash-wasm';
10
9
  import { updateWorkingMemoryTool } from './tools/working-memory';
10
+ import { reorderToolCallsAndResults } from './utils';
11
11
 
12
- const encoder = new Tiktoken(o200k_base);
12
+ // Average characters per token based on OpenAI's tokenization
13
+ const CHARS_PER_TOKEN = 4;
13
14
 
14
15
  /**
15
16
  * Concrete implementation of MastraMemory that adds support for thread configuration
@@ -126,9 +127,14 @@ export class Memory extends MastraMemory {
126
127
  threadConfig: config,
127
128
  });
128
129
 
130
+ // First sort messages by date
131
+ const orderedByDate = rawMessages.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
132
+ // Then reorder tool calls to be directly before their results
133
+ const reorderedToolCalls = reorderToolCallsAndResults(orderedByDate);
134
+
129
135
  // Parse and convert messages
130
- const messages = this.parseMessages(rawMessages);
131
- const uiMessages = this.convertToUIMessages(rawMessages);
136
+ const messages = this.parseMessages(reorderedToolCalls);
137
+ const uiMessages = this.convertToUIMessages(reorderedToolCalls);
132
138
 
133
139
  return { messages, uiMessages };
134
140
  }
@@ -227,24 +233,31 @@ export class Memory extends MastraMemory {
227
233
  await this.storage.__deleteThread({ threadId });
228
234
  }
229
235
 
230
- private chunkText(text: string, size = 4096) {
231
- const tokens = encoder.encode(text);
236
+ private chunkText(text: string, tokenSize = 4096) {
237
+ // Convert token size to character size with some buffer
238
+ const charSize = tokenSize * CHARS_PER_TOKEN;
232
239
  const chunks: string[] = [];
233
- let currentChunk: number[] = [];
240
+ let currentChunk = '';
241
+
242
+ // Split text into words to avoid breaking words
243
+ const words = text.split(/\s+/);
234
244
 
235
- for (const token of tokens) {
236
- currentChunk.push(token);
245
+ for (const word of words) {
246
+ // Add space before word unless it's the first word in the chunk
247
+ const wordWithSpace = currentChunk ? ' ' + word : word;
237
248
 
238
- // If current chunk reaches size limit, add it to chunks and start a new one
239
- if (currentChunk.length >= size) {
240
- chunks.push(encoder.decode(currentChunk));
241
- currentChunk = [];
249
+ // If adding this word would exceed the chunk size, start a new chunk
250
+ if (currentChunk.length + wordWithSpace.length > charSize) {
251
+ chunks.push(currentChunk);
252
+ currentChunk = word;
253
+ } else {
254
+ currentChunk += wordWithSpace;
242
255
  }
243
256
  }
244
257
 
245
- // Add any remaining tokens as the final chunk
246
- if (currentChunk.length > 0) {
247
- chunks.push(encoder.decode(currentChunk));
258
+ // Add the final chunk if not empty
259
+ if (currentChunk) {
260
+ chunks.push(currentChunk);
248
261
  }
249
262
 
250
263
  return chunks;
@@ -0,0 +1,88 @@
1
+ import type { MessageType } from '@mastra/core/memory';
2
+
3
+ const isToolCallWithId = (message: MessageType | undefined, targetToolCallId: string): boolean => {
4
+ if (!message || !Array.isArray(message.content)) return false;
5
+ return message.content.some(
6
+ part =>
7
+ part &&
8
+ typeof part === 'object' &&
9
+ 'type' in part &&
10
+ part.type === 'tool-call' &&
11
+ 'toolCallId' in part &&
12
+ part.toolCallId === targetToolCallId,
13
+ );
14
+ };
15
+
16
+ const getToolResultIndexById = (id: string, results: MessageType[]) =>
17
+ results.findIndex(message => {
18
+ if (!Array.isArray(message?.content)) return false;
19
+ return message.content.some(
20
+ part =>
21
+ part &&
22
+ typeof part === 'object' &&
23
+ 'type' in part &&
24
+ part.type === 'tool-result' &&
25
+ 'toolCallId' in part &&
26
+ part.toolCallId === id,
27
+ );
28
+ });
29
+
30
+ /**
31
+ * Self-heals message ordering to ensure tool calls are directly before their matching tool results.
32
+ * This is needed due to a bug where messages were saved in the wrong order. That bug is fixed, but this code ensures any tool calls saved in the wrong order in the past will still be usable now.
33
+ */
34
+ export function reorderToolCallsAndResults(messages: MessageType[]): MessageType[] {
35
+ if (!messages.length) return messages;
36
+
37
+ // Create a copy of messages to avoid modifying the original
38
+ const results = [...messages];
39
+
40
+ const toolCallIds = new Set<string>();
41
+
42
+ // First loop: collect all tool result IDs in a set
43
+ for (const message of results) {
44
+ if (!Array.isArray(message.content)) continue;
45
+
46
+ for (const part of message.content) {
47
+ if (
48
+ part &&
49
+ typeof part === 'object' &&
50
+ 'type' in part &&
51
+ part.type === 'tool-result' &&
52
+ 'toolCallId' in part &&
53
+ part.toolCallId
54
+ ) {
55
+ toolCallIds.add(part.toolCallId);
56
+ }
57
+ }
58
+ }
59
+
60
+ // Second loop: for each tool ID, ensure tool calls come before tool results
61
+ for (const toolCallId of toolCallIds) {
62
+ // Find tool result index
63
+ const resultIndex = getToolResultIndexById(toolCallId, results);
64
+
65
+ // Check if tool call is at resultIndex - 1
66
+ const oneMessagePrev = results[resultIndex - 1];
67
+ if (isToolCallWithId(oneMessagePrev, toolCallId)) {
68
+ continue; // Tool call is already in the correct position
69
+ }
70
+
71
+ // Find the tool call anywhere in the array
72
+ const toolCallIndex = results.findIndex(message => isToolCallWithId(message, toolCallId));
73
+
74
+ if (toolCallIndex !== -1 && toolCallIndex !== resultIndex - 1) {
75
+ // Store the tool call message
76
+ const toolCall = results[toolCallIndex];
77
+ if (!toolCall) continue;
78
+
79
+ // Remove the tool call from its current position
80
+ results.splice(toolCallIndex, 1);
81
+
82
+ // Insert right before the tool result
83
+ results.splice(getToolResultIndexById(toolCallId, results), 0, toolCall);
84
+ }
85
+ }
86
+
87
+ return results;
88
+ }