chat-agent-toolkit 1.2.32 → 1.2.34
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/research-agent.cjs.js +1 -1
- package/dist/research-agent.cjs.js.map +1 -1
- package/dist/research-agent.es.js +1 -1
- package/dist/research-agent.es.js.map +1 -1
- package/package.json +1 -1
- package/src/config/config-manager.ts +295 -295
- package/src/config/config-types.ts +65 -65
- package/src/config/environment-variables.ts +16 -16
- package/src/config/index.ts +26 -26
- package/src/config/language-models-database.ts +1434 -1434
- package/src/config/mcp-server-registry.ts +24 -24
- package/src/config/model-registry.ts +271 -271
- package/src/config/model-tester.ts +211 -211
- package/src/config/model-utils.ts +193 -193
- package/src/config/provider-ui-config.ts +196 -196
- package/src/connectors/open-connector.json +130 -130
- package/src/index.ts +26 -26
- package/src/memory/ARCHITECTURE.md +302 -302
- package/src/memory/README.md +224 -224
- package/src/memory/agent-memory-manager.ts +416 -416
- package/src/memory/example.ts +343 -343
- package/src/memory/index.ts +47 -47
- package/src/memory/mastra-integration.ts +604 -604
- package/src/memory/storage/drizzle-storage.ts +236 -236
- package/src/memory/storage/in-memory-storage.ts +551 -551
- package/src/memory/storage/storage-interface.ts +68 -68
- package/src/memory/types.ts +125 -125
- package/src/models/types.ts +4 -4
- package/src/provider-logos/01-ai.png +0 -0
- package/src/provider-logos/anthropic.png +0 -0
- package/src/provider-logos/aws-bedrock.png +0 -0
- package/src/provider-logos/aws-sagemaker.png +0 -0
- package/src/provider-logos/azure-openai-service.png +0 -0
- package/src/provider-logos/chatglm.png +0 -0
- package/src/provider-logos/cohere.png +0 -0
- package/src/provider-logos/gemini.png +0 -0
- package/src/provider-logos/groqcloud.png +0 -0
- package/src/provider-logos/huggingface.png +0 -0
- package/src/provider-logos/jina.png +0 -0
- package/src/provider-logos/localai.png +0 -0
- package/src/provider-logos/mistral-ai.png +0 -0
- package/src/provider-logos/moonshot-ai.png +0 -0
- package/src/provider-logos/ollama.png +0 -0
- package/src/provider-logos/openai.png +0 -0
- package/src/provider-logos/openllm.png +0 -0
- package/src/provider-logos/openrouter.png +0 -0
- package/src/provider-logos/replicate.png +0 -0
- package/src/provider-logos/together-ai.png +0 -0
- package/src/provider-logos/tongyi.png +0 -0
- package/src/provider-logos/xorbits-inference.png +0 -0
- package/src/provider-logos/zhipu-ai.png +0 -0
- package/src/tools/index.ts +13 -13
- package/src/tools/open-connector-mastra.ts +270 -270
- package/src/tools/open-connector-mcp.ts +170 -170
- package/src/tools/qwksearch-api-tools.ts +326 -326
- package/src/tools/search/doc-utils.ts +139 -139
- package/src/tools/search/document.ts +47 -47
- package/src/tools/search/index.ts +14 -14
- package/src/tools/search/link-summarizer.ts +112 -112
- package/src/tools/search/meta-search-types.ts +62 -62
- package/src/tools/search/metaSearchAgent.ts +465 -465
- package/src/tools/search/search-handlers.ts +97 -97
- package/src/tools/search/suggestionGeneratorAgent.ts +56 -56
- package/src/types.d.ts +137 -137
- package/src/utils/index.ts +2 -2
- package/src/utils/markdown-to-html.ts +53 -53
- package/src/utils/outputParser.ts +73 -73
|
@@ -1,604 +1,604 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Mastra Memory Integration for Cloudflare Workers
|
|
3
|
-
*
|
|
4
|
-
* Integrates Mastra's memory system with Cloudflare Workers environment.
|
|
5
|
-
* Provides persistent conversation memory using D1, KV, or Durable Objects.
|
|
6
|
-
*
|
|
7
|
-
* Features:
|
|
8
|
-
* - Multi-storage backend support (D1, KV, Durable Objects)
|
|
9
|
-
* - Thread-based conversation management
|
|
10
|
-
* - Resource scoping for multi-user scenarios
|
|
11
|
-
* - Cloudflare Workers optimized (edge-ready)
|
|
12
|
-
* - Compatible with existing MemoryAgent architecture
|
|
13
|
-
*
|
|
14
|
-
* @see https://docs.mastra.ai/core/memory
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import { Mastra } from '@mastra/core';
|
|
18
|
-
import { Agent } from '@mastra/core/agent';
|
|
19
|
-
import type { D1Database, KVNamespace } from '@cloudflare/workers-types';
|
|
20
|
-
import type { IMemoryStorage } from './storage/storage-interface';
|
|
21
|
-
import type { MemoryRecord, MemoryType, MemorySearchOptions, MemoryUpdate } from './types';
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Cloudflare Workers environment bindings
|
|
25
|
-
*/
|
|
26
|
-
export interface CloudflareEnv {
|
|
27
|
-
DB?: D1Database;
|
|
28
|
-
KV?: KVNamespace;
|
|
29
|
-
OPENAI_API_KEY?: string;
|
|
30
|
-
ANTHROPIC_API_KEY?: string;
|
|
31
|
-
GROQ_API_KEY?: string;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Storage backend types for Mastra memory
|
|
36
|
-
*/
|
|
37
|
-
export type MastraStorageBackend = 'memory' | 'd1' | 'kv';
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Configuration for Mastra memory integration
|
|
41
|
-
*/
|
|
42
|
-
export interface MastraMemoryConfig {
|
|
43
|
-
/** Storage backend (memory, d1, or kv) */
|
|
44
|
-
storage: MastraStorageBackend;
|
|
45
|
-
/** Cloudflare environment bindings */
|
|
46
|
-
env?: CloudflareEnv;
|
|
47
|
-
/** D1 table name (for d1 storage) */
|
|
48
|
-
tableName?: string;
|
|
49
|
-
/** KV key prefix (for kv storage) */
|
|
50
|
-
kvPrefix?: string;
|
|
51
|
-
/** Enable debug logging */
|
|
52
|
-
debug?: boolean;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* D1-based storage adapter for Mastra Memory
|
|
57
|
-
* Implements IMemoryStorage using Cloudflare D1
|
|
58
|
-
*/
|
|
59
|
-
export class MastraD1MemoryStorage implements IMemoryStorage {
|
|
60
|
-
private db: D1Database;
|
|
61
|
-
private tableName: string;
|
|
62
|
-
|
|
63
|
-
constructor(db: D1Database, tableName: string = 'mastra_memories') {
|
|
64
|
-
this.db = db;
|
|
65
|
-
this.tableName = tableName;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Initialize D1 schema for Mastra memory
|
|
70
|
-
*/
|
|
71
|
-
async initSchema(): Promise<void> {
|
|
72
|
-
await this.db.exec(`
|
|
73
|
-
CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
|
74
|
-
id TEXT PRIMARY KEY,
|
|
75
|
-
user_id TEXT NOT NULL,
|
|
76
|
-
thread_id TEXT NOT NULL,
|
|
77
|
-
resource_id TEXT,
|
|
78
|
-
memory_type TEXT NOT NULL,
|
|
79
|
-
content TEXT NOT NULL,
|
|
80
|
-
importance REAL DEFAULT 1.0,
|
|
81
|
-
access_count INTEGER DEFAULT 0,
|
|
82
|
-
metadata TEXT,
|
|
83
|
-
created_at INTEGER NOT NULL,
|
|
84
|
-
updated_at INTEGER NOT NULL
|
|
85
|
-
);
|
|
86
|
-
|
|
87
|
-
CREATE INDEX IF NOT EXISTS idx_user_thread ON ${this.tableName}(user_id, thread_id);
|
|
88
|
-
CREATE INDEX IF NOT EXISTS idx_resource ON ${this.tableName}(resource_id);
|
|
89
|
-
CREATE INDEX IF NOT EXISTS idx_importance ON ${this.tableName}(importance DESC);
|
|
90
|
-
`);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
async insertMemory(
|
|
94
|
-
userId: string,
|
|
95
|
-
memoryType: MemoryType,
|
|
96
|
-
content: string,
|
|
97
|
-
importance: number,
|
|
98
|
-
metadata?: Record<string, any>
|
|
99
|
-
): Promise<string> {
|
|
100
|
-
const id = crypto.randomUUID();
|
|
101
|
-
const now = Date.now();
|
|
102
|
-
|
|
103
|
-
await this.db
|
|
104
|
-
.prepare(
|
|
105
|
-
`INSERT INTO ${this.tableName}
|
|
106
|
-
(id, user_id, thread_id, resource_id, memory_type, content, importance, metadata, created_at, updated_at)
|
|
107
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
108
|
-
)
|
|
109
|
-
.bind(
|
|
110
|
-
id,
|
|
111
|
-
userId,
|
|
112
|
-
metadata?.thread_id || 'default',
|
|
113
|
-
metadata?.resource_id || null,
|
|
114
|
-
memoryType,
|
|
115
|
-
content,
|
|
116
|
-
importance,
|
|
117
|
-
JSON.stringify(metadata || {}),
|
|
118
|
-
now,
|
|
119
|
-
now
|
|
120
|
-
)
|
|
121
|
-
.run();
|
|
122
|
-
|
|
123
|
-
return id;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
async findMemories(
|
|
127
|
-
userId: string,
|
|
128
|
-
query?: string,
|
|
129
|
-
limit: number = 10,
|
|
130
|
-
options: MemorySearchOptions = {}
|
|
131
|
-
): Promise<MemoryRecord[]> {
|
|
132
|
-
let sql = `SELECT * FROM ${this.tableName} WHERE user_id = ?`;
|
|
133
|
-
const params: any[] = [userId];
|
|
134
|
-
|
|
135
|
-
if (options.memoryType) {
|
|
136
|
-
sql += ' AND memory_type = ?';
|
|
137
|
-
params.push(options.memoryType);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (query) {
|
|
141
|
-
sql += ' AND content LIKE ?';
|
|
142
|
-
params.push(`%${query}%`);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (options.minImportance !== undefined) {
|
|
146
|
-
sql += ' AND importance >= ?';
|
|
147
|
-
params.push(options.minImportance);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
sql += ' ORDER BY importance DESC, updated_at DESC LIMIT ?';
|
|
151
|
-
params.push(limit);
|
|
152
|
-
|
|
153
|
-
const result = await this.db.prepare(sql).bind(...params).all();
|
|
154
|
-
|
|
155
|
-
return (result.results || []).map((row: any) => ({
|
|
156
|
-
id: row.id,
|
|
157
|
-
user_id: row.user_id,
|
|
158
|
-
memory_type: row.memory_type,
|
|
159
|
-
content: row.content,
|
|
160
|
-
importance: row.importance,
|
|
161
|
-
access_count: row.access_count,
|
|
162
|
-
metadata: JSON.parse(row.metadata || '{}'),
|
|
163
|
-
created_at: new Date(row.created_at),
|
|
164
|
-
updated_at: new Date(row.updated_at),
|
|
165
|
-
}));
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
async findSimilarMemories(
|
|
169
|
-
userId: string,
|
|
170
|
-
content: string,
|
|
171
|
-
limit: number = 5
|
|
172
|
-
): Promise<MemoryRecord[]> {
|
|
173
|
-
// Simple keyword-based similarity for D1 (no vector search)
|
|
174
|
-
const words = content.toLowerCase().split(/\s+/).filter(w => w.length > 2);
|
|
175
|
-
const searchTerms = words.slice(0, 3);
|
|
176
|
-
|
|
177
|
-
if (searchTerms.length === 0) return [];
|
|
178
|
-
|
|
179
|
-
const likeConditions = searchTerms.map(() => 'content LIKE ?').join(' OR ');
|
|
180
|
-
const params = [userId, ...searchTerms.map(term => `%${term}%`), limit];
|
|
181
|
-
|
|
182
|
-
const result = await this.db
|
|
183
|
-
.prepare(
|
|
184
|
-
`SELECT * FROM ${this.tableName}
|
|
185
|
-
WHERE user_id = ? AND (${likeConditions})
|
|
186
|
-
ORDER BY importance DESC, updated_at DESC
|
|
187
|
-
LIMIT ?`
|
|
188
|
-
)
|
|
189
|
-
.bind(...params)
|
|
190
|
-
.all();
|
|
191
|
-
|
|
192
|
-
return (result.results || []).map((row: any) => ({
|
|
193
|
-
id: row.id,
|
|
194
|
-
user_id: row.user_id,
|
|
195
|
-
memory_type: row.memory_type,
|
|
196
|
-
content: row.content,
|
|
197
|
-
importance: row.importance,
|
|
198
|
-
access_count: row.access_count,
|
|
199
|
-
metadata: JSON.parse(row.metadata || '{}'),
|
|
200
|
-
created_at: new Date(row.created_at),
|
|
201
|
-
updated_at: new Date(row.updated_at),
|
|
202
|
-
}));
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
async updateMemory(id: string, updates: MemoryUpdate): Promise<void> {
|
|
206
|
-
const fields: string[] = [];
|
|
207
|
-
const params: any[] = [];
|
|
208
|
-
|
|
209
|
-
if (updates.importance !== undefined) {
|
|
210
|
-
fields.push('importance = ?');
|
|
211
|
-
params.push(updates.importance);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
if (updates.access_count !== undefined) {
|
|
215
|
-
if (typeof updates.access_count === 'object' && updates.access_count.increment) {
|
|
216
|
-
fields.push('access_count = access_count + ?');
|
|
217
|
-
params.push(updates.access_count.increment);
|
|
218
|
-
} else {
|
|
219
|
-
fields.push('access_count = ?');
|
|
220
|
-
params.push(updates.access_count);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
if (updates.metadata !== undefined) {
|
|
225
|
-
fields.push('metadata = ?');
|
|
226
|
-
params.push(JSON.stringify(updates.metadata));
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
fields.push('updated_at = ?');
|
|
230
|
-
params.push(Date.now());
|
|
231
|
-
params.push(id);
|
|
232
|
-
|
|
233
|
-
if (fields.length > 0) {
|
|
234
|
-
await this.db
|
|
235
|
-
.prepare(`UPDATE ${this.tableName} SET ${fields.join(', ')} WHERE id = ?`)
|
|
236
|
-
.bind(...params)
|
|
237
|
-
.run();
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
async deleteMemory(id: string): Promise<void> {
|
|
242
|
-
await this.db.prepare(`DELETE FROM ${this.tableName} WHERE id = ?`).bind(id).run();
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
async getMemoryById(id: string): Promise<MemoryRecord | null> {
|
|
246
|
-
const result = await this.db
|
|
247
|
-
.prepare(`SELECT * FROM ${this.tableName} WHERE id = ?`)
|
|
248
|
-
.bind(id)
|
|
249
|
-
.first();
|
|
250
|
-
|
|
251
|
-
if (!result) return null;
|
|
252
|
-
|
|
253
|
-
return {
|
|
254
|
-
id: result.id as string,
|
|
255
|
-
user_id: result.user_id as string,
|
|
256
|
-
memory_type: result.memory_type as MemoryType,
|
|
257
|
-
content: result.content as string,
|
|
258
|
-
importance: result.importance as number,
|
|
259
|
-
access_count: result.access_count as number,
|
|
260
|
-
metadata: JSON.parse((result.metadata as string) || '{}'),
|
|
261
|
-
created_at: new Date(result.created_at as number),
|
|
262
|
-
updated_at: new Date(result.updated_at as number),
|
|
263
|
-
};
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
async batchUpdateMemories(updates: Array<{ id: string; updates: MemoryUpdate }>): Promise<void> {
|
|
267
|
-
for (const { id, updates: updateData } of updates) {
|
|
268
|
-
await this.updateMemory(id, updateData);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* KV-based storage adapter for Mastra Memory
|
|
275
|
-
* Uses Cloudflare KV for simple key-value storage
|
|
276
|
-
*/
|
|
277
|
-
export class MastraKVMemoryStorage implements IMemoryStorage {
|
|
278
|
-
private kv: KVNamespace;
|
|
279
|
-
private prefix: string;
|
|
280
|
-
|
|
281
|
-
constructor(kv: KVNamespace, prefix: string = 'mastra:memory:') {
|
|
282
|
-
this.kv = kv;
|
|
283
|
-
this.prefix = prefix;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
private getUserKey(userId: string): string {
|
|
287
|
-
return `${this.prefix}user:${userId}`;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
private getMemoryKey(id: string): string {
|
|
291
|
-
return `${this.prefix}memory:${id}`;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
async insertMemory(
|
|
295
|
-
userId: string,
|
|
296
|
-
memoryType: MemoryType,
|
|
297
|
-
content: string,
|
|
298
|
-
importance: number,
|
|
299
|
-
metadata?: Record<string, any>
|
|
300
|
-
): Promise<string> {
|
|
301
|
-
const id = crypto.randomUUID();
|
|
302
|
-
const now = Date.now();
|
|
303
|
-
|
|
304
|
-
const memory: MemoryRecord = {
|
|
305
|
-
id,
|
|
306
|
-
user_id: userId,
|
|
307
|
-
memory_type: memoryType,
|
|
308
|
-
content,
|
|
309
|
-
importance,
|
|
310
|
-
access_count: 0,
|
|
311
|
-
metadata: metadata || {},
|
|
312
|
-
created_at: new Date(now),
|
|
313
|
-
updated_at: new Date(now),
|
|
314
|
-
};
|
|
315
|
-
|
|
316
|
-
// Store memory
|
|
317
|
-
await this.kv.put(this.getMemoryKey(id), JSON.stringify(memory));
|
|
318
|
-
|
|
319
|
-
// Update user's memory list
|
|
320
|
-
const userKey = this.getUserKey(userId);
|
|
321
|
-
const userMemories = await this.kv.get(userKey, 'json') as string[] || [];
|
|
322
|
-
userMemories.push(id);
|
|
323
|
-
await this.kv.put(userKey, JSON.stringify(userMemories));
|
|
324
|
-
|
|
325
|
-
return id;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
async findMemories(
|
|
329
|
-
userId: string,
|
|
330
|
-
query?: string,
|
|
331
|
-
limit: number = 10,
|
|
332
|
-
options: MemorySearchOptions = {}
|
|
333
|
-
): Promise<MemoryRecord[]> {
|
|
334
|
-
const userKey = this.getUserKey(userId);
|
|
335
|
-
const memoryIds = await this.kv.get(userKey, 'json') as string[] || [];
|
|
336
|
-
|
|
337
|
-
const memories = await Promise.all(
|
|
338
|
-
memoryIds.map(id => this.getMemoryById(id))
|
|
339
|
-
);
|
|
340
|
-
|
|
341
|
-
let filtered = memories.filter((m): m is MemoryRecord => m !== null);
|
|
342
|
-
|
|
343
|
-
if (query) {
|
|
344
|
-
filtered = filtered.filter(m =>
|
|
345
|
-
m.content.toLowerCase().includes(query.toLowerCase())
|
|
346
|
-
);
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
if (options.memoryType) {
|
|
350
|
-
filtered = filtered.filter(m => m.memory_type === options.memoryType);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
if (options.minImportance !== undefined) {
|
|
354
|
-
filtered = filtered.filter(m => m.importance >= options.minImportance!);
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
return filtered
|
|
358
|
-
.sort((a, b) => b.importance - a.importance || b.updated_at.getTime() - a.updated_at.getTime())
|
|
359
|
-
.slice(0, limit);
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
async findSimilarMemories(
|
|
363
|
-
userId: string,
|
|
364
|
-
content: string,
|
|
365
|
-
limit: number = 5
|
|
366
|
-
): Promise<MemoryRecord[]> {
|
|
367
|
-
const words = content.toLowerCase().split(/\s+/).filter(w => w.length > 2);
|
|
368
|
-
const searchTerms = words.slice(0, 3);
|
|
369
|
-
|
|
370
|
-
if (searchTerms.length === 0) return [];
|
|
371
|
-
|
|
372
|
-
const memories = await this.findMemories(userId, '', 50);
|
|
373
|
-
|
|
374
|
-
return memories
|
|
375
|
-
.filter(m =>
|
|
376
|
-
searchTerms.some(term => m.content.toLowerCase().includes(term))
|
|
377
|
-
)
|
|
378
|
-
.slice(0, limit);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
async updateMemory(id: string, updates: MemoryUpdate): Promise<void> {
|
|
382
|
-
const memory = await this.getMemoryById(id);
|
|
383
|
-
if (!memory) return;
|
|
384
|
-
|
|
385
|
-
if (updates.importance !== undefined) {
|
|
386
|
-
memory.importance = updates.importance;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
if (updates.access_count !== undefined) {
|
|
390
|
-
if (typeof updates.access_count === 'object') {
|
|
391
|
-
memory.access_count += updates.access_count.increment ?? 0;
|
|
392
|
-
} else {
|
|
393
|
-
memory.access_count = updates.access_count;
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
if (updates.metadata !== undefined) {
|
|
398
|
-
memory.metadata = { ...memory.metadata, ...updates.metadata };
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
memory.updated_at = new Date();
|
|
402
|
-
|
|
403
|
-
await this.kv.put(this.getMemoryKey(id), JSON.stringify(memory));
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
async deleteMemory(id: string): Promise<void> {
|
|
407
|
-
const memory = await this.getMemoryById(id);
|
|
408
|
-
if (!memory) return;
|
|
409
|
-
|
|
410
|
-
await this.kv.delete(this.getMemoryKey(id));
|
|
411
|
-
|
|
412
|
-
// Remove from user's list
|
|
413
|
-
const userKey = this.getUserKey(memory.user_id);
|
|
414
|
-
const userMemories = await this.kv.get(userKey, 'json') as string[] || [];
|
|
415
|
-
const filtered = userMemories.filter(mId => mId !== id);
|
|
416
|
-
await this.kv.put(userKey, JSON.stringify(filtered));
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
async getMemoryById(id: string): Promise<MemoryRecord | null> {
|
|
420
|
-
const data = await this.kv.get(this.getMemoryKey(id), 'json') as any;
|
|
421
|
-
if (!data) return null;
|
|
422
|
-
|
|
423
|
-
return {
|
|
424
|
-
...data,
|
|
425
|
-
created_at: new Date(data.created_at),
|
|
426
|
-
updated_at: new Date(data.updated_at),
|
|
427
|
-
};
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
async batchUpdateMemories(updates: Array<{ id: string; updates: MemoryUpdate }>): Promise<void> {
|
|
431
|
-
await Promise.all(
|
|
432
|
-
updates.map(({ id, updates: updateData }) => this.updateMemory(id, updateData))
|
|
433
|
-
);
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
/**
|
|
438
|
-
* Mastra Memory Manager for Cloudflare Workers
|
|
439
|
-
* Wraps Mastra's memory system with Cloudflare-compatible storage
|
|
440
|
-
*/
|
|
441
|
-
export class MastraMemoryManager {
|
|
442
|
-
private mastra: Mastra | null = null;
|
|
443
|
-
private storage: IMemoryStorage;
|
|
444
|
-
private config: MastraMemoryConfig;
|
|
445
|
-
|
|
446
|
-
constructor(config: MastraMemoryConfig) {
|
|
447
|
-
this.config = config;
|
|
448
|
-
|
|
449
|
-
// Initialize storage backend
|
|
450
|
-
if (config.storage === 'd1' && config.env?.DB) {
|
|
451
|
-
this.storage = new MastraD1MemoryStorage(config.env.DB, config.tableName);
|
|
452
|
-
} else if (config.storage === 'kv' && config.env?.KV) {
|
|
453
|
-
this.storage = new MastraKVMemoryStorage(config.env.KV, config.kvPrefix);
|
|
454
|
-
} else {
|
|
455
|
-
throw new Error(`Invalid storage configuration: ${config.storage}`);
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
/**
|
|
460
|
-
* Initialize Mastra instance with memory
|
|
461
|
-
*/
|
|
462
|
-
async initialize(): Promise<Mastra> {
|
|
463
|
-
if (this.mastra) return this.mastra;
|
|
464
|
-
|
|
465
|
-
// Memory persistence is handled by this manager's own D1/KV storage
|
|
466
|
-
// adapters rather than a Mastra memory backend.
|
|
467
|
-
this.mastra = new Mastra({});
|
|
468
|
-
|
|
469
|
-
// Initialize D1 schema if needed
|
|
470
|
-
if (this.config.storage === 'd1' && this.storage instanceof MastraD1MemoryStorage) {
|
|
471
|
-
await this.storage.initSchema();
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
return this.mastra;
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
/**
|
|
478
|
-
* Get storage instance (for direct access)
|
|
479
|
-
*/
|
|
480
|
-
getStorage(): IMemoryStorage {
|
|
481
|
-
return this.storage;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
/**
|
|
485
|
-
* Create an agent with memory
|
|
486
|
-
*/
|
|
487
|
-
async createAgent(config: {
|
|
488
|
-
id: string;
|
|
489
|
-
name: string;
|
|
490
|
-
instructions: string;
|
|
491
|
-
model: any;
|
|
492
|
-
tools?: Record<string, any>;
|
|
493
|
-
userId: string;
|
|
494
|
-
threadId?: string;
|
|
495
|
-
resourceId?: string;
|
|
496
|
-
}): Promise<Agent> {
|
|
497
|
-
const mastra = await this.initialize();
|
|
498
|
-
|
|
499
|
-
// Get or create agent with memory
|
|
500
|
-
const agent = new Agent({
|
|
501
|
-
id: config.id,
|
|
502
|
-
name: config.name,
|
|
503
|
-
instructions: config.instructions,
|
|
504
|
-
model: config.model,
|
|
505
|
-
tools: config.tools || {},
|
|
506
|
-
});
|
|
507
|
-
|
|
508
|
-
// Attach memory context
|
|
509
|
-
(agent as any).memoryContext = {
|
|
510
|
-
userId: config.userId,
|
|
511
|
-
threadId: config.threadId || 'default',
|
|
512
|
-
resourceId: config.resourceId,
|
|
513
|
-
};
|
|
514
|
-
|
|
515
|
-
return agent;
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
/**
|
|
519
|
-
* Store a message in memory
|
|
520
|
-
*/
|
|
521
|
-
async storeMessage(
|
|
522
|
-
userId: string,
|
|
523
|
-
threadId: string,
|
|
524
|
-
role: 'user' | 'assistant',
|
|
525
|
-
content: string,
|
|
526
|
-
metadata?: Record<string, any>
|
|
527
|
-
): Promise<string> {
|
|
528
|
-
return await this.storage.insertMemory(
|
|
529
|
-
userId,
|
|
530
|
-
'conversation',
|
|
531
|
-
content,
|
|
532
|
-
1.0,
|
|
533
|
-
{
|
|
534
|
-
thread_id: threadId,
|
|
535
|
-
role,
|
|
536
|
-
...metadata,
|
|
537
|
-
}
|
|
538
|
-
);
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
/**
|
|
542
|
-
* Recall conversation history
|
|
543
|
-
*/
|
|
544
|
-
async recallConversation(
|
|
545
|
-
userId: string,
|
|
546
|
-
threadId: string,
|
|
547
|
-
limit: number = 20
|
|
548
|
-
): Promise<Array<{ role: 'user' | 'assistant'; content: string; timestamp: Date }>> {
|
|
549
|
-
const memories = await this.storage.findMemories(
|
|
550
|
-
userId,
|
|
551
|
-
undefined,
|
|
552
|
-
limit,
|
|
553
|
-
{ memoryType: 'conversation' }
|
|
554
|
-
);
|
|
555
|
-
|
|
556
|
-
return memories
|
|
557
|
-
.filter(m => m.metadata?.thread_id === threadId)
|
|
558
|
-
.map(m => ({
|
|
559
|
-
role: m.metadata?.role || 'user',
|
|
560
|
-
content: m.content,
|
|
561
|
-
timestamp: m.created_at,
|
|
562
|
-
}))
|
|
563
|
-
.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
/**
|
|
567
|
-
* Get relevant context for a query
|
|
568
|
-
*/
|
|
569
|
-
async getRelevantContext(
|
|
570
|
-
userId: string,
|
|
571
|
-
query: string,
|
|
572
|
-
limit: number = 5
|
|
573
|
-
): Promise<string> {
|
|
574
|
-
const memories = await this.storage.findSimilarMemories(userId, query, limit);
|
|
575
|
-
|
|
576
|
-
if (memories.length === 0) return '';
|
|
577
|
-
|
|
578
|
-
return memories.map(m => m.content).join('\n\n');
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
/**
|
|
583
|
-
* Quick helper to create Mastra memory manager for Cloudflare Workers
|
|
584
|
-
*
|
|
585
|
-
* @example
|
|
586
|
-
* ```ts
|
|
587
|
-
* const memoryManager = createMastraMemory({
|
|
588
|
-
* storage: 'd1',
|
|
589
|
-
* env: env, // Cloudflare env bindings
|
|
590
|
-
* });
|
|
591
|
-
*
|
|
592
|
-
* const agent = await memoryManager.createAgent({
|
|
593
|
-
* id: 'assistant',
|
|
594
|
-
* name: 'AI Assistant',
|
|
595
|
-
* instructions: 'Help users with their questions',
|
|
596
|
-
* model: openai('gpt-4o'),
|
|
597
|
-
* userId: 'user-123',
|
|
598
|
-
* threadId: 'conversation-1',
|
|
599
|
-
* });
|
|
600
|
-
* ```
|
|
601
|
-
*/
|
|
602
|
-
export function createMastraMemory(config: MastraMemoryConfig): MastraMemoryManager {
|
|
603
|
-
return new MastraMemoryManager(config);
|
|
604
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Mastra Memory Integration for Cloudflare Workers
|
|
3
|
+
*
|
|
4
|
+
* Integrates Mastra's memory system with Cloudflare Workers environment.
|
|
5
|
+
* Provides persistent conversation memory using D1, KV, or Durable Objects.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Multi-storage backend support (D1, KV, Durable Objects)
|
|
9
|
+
* - Thread-based conversation management
|
|
10
|
+
* - Resource scoping for multi-user scenarios
|
|
11
|
+
* - Cloudflare Workers optimized (edge-ready)
|
|
12
|
+
* - Compatible with existing MemoryAgent architecture
|
|
13
|
+
*
|
|
14
|
+
* @see https://docs.mastra.ai/core/memory
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { Mastra } from '@mastra/core';
|
|
18
|
+
import { Agent } from '@mastra/core/agent';
|
|
19
|
+
import type { D1Database, KVNamespace } from '@cloudflare/workers-types';
|
|
20
|
+
import type { IMemoryStorage } from './storage/storage-interface';
|
|
21
|
+
import type { MemoryRecord, MemoryType, MemorySearchOptions, MemoryUpdate } from './types';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Cloudflare Workers environment bindings
|
|
25
|
+
*/
|
|
26
|
+
export interface CloudflareEnv {
|
|
27
|
+
DB?: D1Database;
|
|
28
|
+
KV?: KVNamespace;
|
|
29
|
+
OPENAI_API_KEY?: string;
|
|
30
|
+
ANTHROPIC_API_KEY?: string;
|
|
31
|
+
GROQ_API_KEY?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Storage backend types for Mastra memory
|
|
36
|
+
*/
|
|
37
|
+
export type MastraStorageBackend = 'memory' | 'd1' | 'kv';
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Configuration for Mastra memory integration
|
|
41
|
+
*/
|
|
42
|
+
export interface MastraMemoryConfig {
|
|
43
|
+
/** Storage backend (memory, d1, or kv) */
|
|
44
|
+
storage: MastraStorageBackend;
|
|
45
|
+
/** Cloudflare environment bindings */
|
|
46
|
+
env?: CloudflareEnv;
|
|
47
|
+
/** D1 table name (for d1 storage) */
|
|
48
|
+
tableName?: string;
|
|
49
|
+
/** KV key prefix (for kv storage) */
|
|
50
|
+
kvPrefix?: string;
|
|
51
|
+
/** Enable debug logging */
|
|
52
|
+
debug?: boolean;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* D1-based storage adapter for Mastra Memory
|
|
57
|
+
* Implements IMemoryStorage using Cloudflare D1
|
|
58
|
+
*/
|
|
59
|
+
export class MastraD1MemoryStorage implements IMemoryStorage {
|
|
60
|
+
private db: D1Database;
|
|
61
|
+
private tableName: string;
|
|
62
|
+
|
|
63
|
+
constructor(db: D1Database, tableName: string = 'mastra_memories') {
|
|
64
|
+
this.db = db;
|
|
65
|
+
this.tableName = tableName;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Initialize D1 schema for Mastra memory
|
|
70
|
+
*/
|
|
71
|
+
async initSchema(): Promise<void> {
|
|
72
|
+
await this.db.exec(`
|
|
73
|
+
CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
|
74
|
+
id TEXT PRIMARY KEY,
|
|
75
|
+
user_id TEXT NOT NULL,
|
|
76
|
+
thread_id TEXT NOT NULL,
|
|
77
|
+
resource_id TEXT,
|
|
78
|
+
memory_type TEXT NOT NULL,
|
|
79
|
+
content TEXT NOT NULL,
|
|
80
|
+
importance REAL DEFAULT 1.0,
|
|
81
|
+
access_count INTEGER DEFAULT 0,
|
|
82
|
+
metadata TEXT,
|
|
83
|
+
created_at INTEGER NOT NULL,
|
|
84
|
+
updated_at INTEGER NOT NULL
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
CREATE INDEX IF NOT EXISTS idx_user_thread ON ${this.tableName}(user_id, thread_id);
|
|
88
|
+
CREATE INDEX IF NOT EXISTS idx_resource ON ${this.tableName}(resource_id);
|
|
89
|
+
CREATE INDEX IF NOT EXISTS idx_importance ON ${this.tableName}(importance DESC);
|
|
90
|
+
`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async insertMemory(
|
|
94
|
+
userId: string,
|
|
95
|
+
memoryType: MemoryType,
|
|
96
|
+
content: string,
|
|
97
|
+
importance: number,
|
|
98
|
+
metadata?: Record<string, any>
|
|
99
|
+
): Promise<string> {
|
|
100
|
+
const id = crypto.randomUUID();
|
|
101
|
+
const now = Date.now();
|
|
102
|
+
|
|
103
|
+
await this.db
|
|
104
|
+
.prepare(
|
|
105
|
+
`INSERT INTO ${this.tableName}
|
|
106
|
+
(id, user_id, thread_id, resource_id, memory_type, content, importance, metadata, created_at, updated_at)
|
|
107
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
108
|
+
)
|
|
109
|
+
.bind(
|
|
110
|
+
id,
|
|
111
|
+
userId,
|
|
112
|
+
metadata?.thread_id || 'default',
|
|
113
|
+
metadata?.resource_id || null,
|
|
114
|
+
memoryType,
|
|
115
|
+
content,
|
|
116
|
+
importance,
|
|
117
|
+
JSON.stringify(metadata || {}),
|
|
118
|
+
now,
|
|
119
|
+
now
|
|
120
|
+
)
|
|
121
|
+
.run();
|
|
122
|
+
|
|
123
|
+
return id;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async findMemories(
|
|
127
|
+
userId: string,
|
|
128
|
+
query?: string,
|
|
129
|
+
limit: number = 10,
|
|
130
|
+
options: MemorySearchOptions = {}
|
|
131
|
+
): Promise<MemoryRecord[]> {
|
|
132
|
+
let sql = `SELECT * FROM ${this.tableName} WHERE user_id = ?`;
|
|
133
|
+
const params: any[] = [userId];
|
|
134
|
+
|
|
135
|
+
if (options.memoryType) {
|
|
136
|
+
sql += ' AND memory_type = ?';
|
|
137
|
+
params.push(options.memoryType);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (query) {
|
|
141
|
+
sql += ' AND content LIKE ?';
|
|
142
|
+
params.push(`%${query}%`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (options.minImportance !== undefined) {
|
|
146
|
+
sql += ' AND importance >= ?';
|
|
147
|
+
params.push(options.minImportance);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
sql += ' ORDER BY importance DESC, updated_at DESC LIMIT ?';
|
|
151
|
+
params.push(limit);
|
|
152
|
+
|
|
153
|
+
const result = await this.db.prepare(sql).bind(...params).all();
|
|
154
|
+
|
|
155
|
+
return (result.results || []).map((row: any) => ({
|
|
156
|
+
id: row.id,
|
|
157
|
+
user_id: row.user_id,
|
|
158
|
+
memory_type: row.memory_type,
|
|
159
|
+
content: row.content,
|
|
160
|
+
importance: row.importance,
|
|
161
|
+
access_count: row.access_count,
|
|
162
|
+
metadata: JSON.parse(row.metadata || '{}'),
|
|
163
|
+
created_at: new Date(row.created_at),
|
|
164
|
+
updated_at: new Date(row.updated_at),
|
|
165
|
+
}));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async findSimilarMemories(
|
|
169
|
+
userId: string,
|
|
170
|
+
content: string,
|
|
171
|
+
limit: number = 5
|
|
172
|
+
): Promise<MemoryRecord[]> {
|
|
173
|
+
// Simple keyword-based similarity for D1 (no vector search)
|
|
174
|
+
const words = content.toLowerCase().split(/\s+/).filter(w => w.length > 2);
|
|
175
|
+
const searchTerms = words.slice(0, 3);
|
|
176
|
+
|
|
177
|
+
if (searchTerms.length === 0) return [];
|
|
178
|
+
|
|
179
|
+
const likeConditions = searchTerms.map(() => 'content LIKE ?').join(' OR ');
|
|
180
|
+
const params = [userId, ...searchTerms.map(term => `%${term}%`), limit];
|
|
181
|
+
|
|
182
|
+
const result = await this.db
|
|
183
|
+
.prepare(
|
|
184
|
+
`SELECT * FROM ${this.tableName}
|
|
185
|
+
WHERE user_id = ? AND (${likeConditions})
|
|
186
|
+
ORDER BY importance DESC, updated_at DESC
|
|
187
|
+
LIMIT ?`
|
|
188
|
+
)
|
|
189
|
+
.bind(...params)
|
|
190
|
+
.all();
|
|
191
|
+
|
|
192
|
+
return (result.results || []).map((row: any) => ({
|
|
193
|
+
id: row.id,
|
|
194
|
+
user_id: row.user_id,
|
|
195
|
+
memory_type: row.memory_type,
|
|
196
|
+
content: row.content,
|
|
197
|
+
importance: row.importance,
|
|
198
|
+
access_count: row.access_count,
|
|
199
|
+
metadata: JSON.parse(row.metadata || '{}'),
|
|
200
|
+
created_at: new Date(row.created_at),
|
|
201
|
+
updated_at: new Date(row.updated_at),
|
|
202
|
+
}));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async updateMemory(id: string, updates: MemoryUpdate): Promise<void> {
|
|
206
|
+
const fields: string[] = [];
|
|
207
|
+
const params: any[] = [];
|
|
208
|
+
|
|
209
|
+
if (updates.importance !== undefined) {
|
|
210
|
+
fields.push('importance = ?');
|
|
211
|
+
params.push(updates.importance);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (updates.access_count !== undefined) {
|
|
215
|
+
if (typeof updates.access_count === 'object' && updates.access_count.increment) {
|
|
216
|
+
fields.push('access_count = access_count + ?');
|
|
217
|
+
params.push(updates.access_count.increment);
|
|
218
|
+
} else {
|
|
219
|
+
fields.push('access_count = ?');
|
|
220
|
+
params.push(updates.access_count);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (updates.metadata !== undefined) {
|
|
225
|
+
fields.push('metadata = ?');
|
|
226
|
+
params.push(JSON.stringify(updates.metadata));
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
fields.push('updated_at = ?');
|
|
230
|
+
params.push(Date.now());
|
|
231
|
+
params.push(id);
|
|
232
|
+
|
|
233
|
+
if (fields.length > 0) {
|
|
234
|
+
await this.db
|
|
235
|
+
.prepare(`UPDATE ${this.tableName} SET ${fields.join(', ')} WHERE id = ?`)
|
|
236
|
+
.bind(...params)
|
|
237
|
+
.run();
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async deleteMemory(id: string): Promise<void> {
|
|
242
|
+
await this.db.prepare(`DELETE FROM ${this.tableName} WHERE id = ?`).bind(id).run();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async getMemoryById(id: string): Promise<MemoryRecord | null> {
|
|
246
|
+
const result = await this.db
|
|
247
|
+
.prepare(`SELECT * FROM ${this.tableName} WHERE id = ?`)
|
|
248
|
+
.bind(id)
|
|
249
|
+
.first();
|
|
250
|
+
|
|
251
|
+
if (!result) return null;
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
id: result.id as string,
|
|
255
|
+
user_id: result.user_id as string,
|
|
256
|
+
memory_type: result.memory_type as MemoryType,
|
|
257
|
+
content: result.content as string,
|
|
258
|
+
importance: result.importance as number,
|
|
259
|
+
access_count: result.access_count as number,
|
|
260
|
+
metadata: JSON.parse((result.metadata as string) || '{}'),
|
|
261
|
+
created_at: new Date(result.created_at as number),
|
|
262
|
+
updated_at: new Date(result.updated_at as number),
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async batchUpdateMemories(updates: Array<{ id: string; updates: MemoryUpdate }>): Promise<void> {
|
|
267
|
+
for (const { id, updates: updateData } of updates) {
|
|
268
|
+
await this.updateMemory(id, updateData);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* KV-based storage adapter for Mastra Memory
|
|
275
|
+
* Uses Cloudflare KV for simple key-value storage
|
|
276
|
+
*/
|
|
277
|
+
export class MastraKVMemoryStorage implements IMemoryStorage {
|
|
278
|
+
private kv: KVNamespace;
|
|
279
|
+
private prefix: string;
|
|
280
|
+
|
|
281
|
+
constructor(kv: KVNamespace, prefix: string = 'mastra:memory:') {
|
|
282
|
+
this.kv = kv;
|
|
283
|
+
this.prefix = prefix;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
private getUserKey(userId: string): string {
|
|
287
|
+
return `${this.prefix}user:${userId}`;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
private getMemoryKey(id: string): string {
|
|
291
|
+
return `${this.prefix}memory:${id}`;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async insertMemory(
|
|
295
|
+
userId: string,
|
|
296
|
+
memoryType: MemoryType,
|
|
297
|
+
content: string,
|
|
298
|
+
importance: number,
|
|
299
|
+
metadata?: Record<string, any>
|
|
300
|
+
): Promise<string> {
|
|
301
|
+
const id = crypto.randomUUID();
|
|
302
|
+
const now = Date.now();
|
|
303
|
+
|
|
304
|
+
const memory: MemoryRecord = {
|
|
305
|
+
id,
|
|
306
|
+
user_id: userId,
|
|
307
|
+
memory_type: memoryType,
|
|
308
|
+
content,
|
|
309
|
+
importance,
|
|
310
|
+
access_count: 0,
|
|
311
|
+
metadata: metadata || {},
|
|
312
|
+
created_at: new Date(now),
|
|
313
|
+
updated_at: new Date(now),
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
// Store memory
|
|
317
|
+
await this.kv.put(this.getMemoryKey(id), JSON.stringify(memory));
|
|
318
|
+
|
|
319
|
+
// Update user's memory list
|
|
320
|
+
const userKey = this.getUserKey(userId);
|
|
321
|
+
const userMemories = await this.kv.get(userKey, 'json') as string[] || [];
|
|
322
|
+
userMemories.push(id);
|
|
323
|
+
await this.kv.put(userKey, JSON.stringify(userMemories));
|
|
324
|
+
|
|
325
|
+
return id;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
async findMemories(
|
|
329
|
+
userId: string,
|
|
330
|
+
query?: string,
|
|
331
|
+
limit: number = 10,
|
|
332
|
+
options: MemorySearchOptions = {}
|
|
333
|
+
): Promise<MemoryRecord[]> {
|
|
334
|
+
const userKey = this.getUserKey(userId);
|
|
335
|
+
const memoryIds = await this.kv.get(userKey, 'json') as string[] || [];
|
|
336
|
+
|
|
337
|
+
const memories = await Promise.all(
|
|
338
|
+
memoryIds.map(id => this.getMemoryById(id))
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
let filtered = memories.filter((m): m is MemoryRecord => m !== null);
|
|
342
|
+
|
|
343
|
+
if (query) {
|
|
344
|
+
filtered = filtered.filter(m =>
|
|
345
|
+
m.content.toLowerCase().includes(query.toLowerCase())
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (options.memoryType) {
|
|
350
|
+
filtered = filtered.filter(m => m.memory_type === options.memoryType);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (options.minImportance !== undefined) {
|
|
354
|
+
filtered = filtered.filter(m => m.importance >= options.minImportance!);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return filtered
|
|
358
|
+
.sort((a, b) => b.importance - a.importance || b.updated_at.getTime() - a.updated_at.getTime())
|
|
359
|
+
.slice(0, limit);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
async findSimilarMemories(
|
|
363
|
+
userId: string,
|
|
364
|
+
content: string,
|
|
365
|
+
limit: number = 5
|
|
366
|
+
): Promise<MemoryRecord[]> {
|
|
367
|
+
const words = content.toLowerCase().split(/\s+/).filter(w => w.length > 2);
|
|
368
|
+
const searchTerms = words.slice(0, 3);
|
|
369
|
+
|
|
370
|
+
if (searchTerms.length === 0) return [];
|
|
371
|
+
|
|
372
|
+
const memories = await this.findMemories(userId, '', 50);
|
|
373
|
+
|
|
374
|
+
return memories
|
|
375
|
+
.filter(m =>
|
|
376
|
+
searchTerms.some(term => m.content.toLowerCase().includes(term))
|
|
377
|
+
)
|
|
378
|
+
.slice(0, limit);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
async updateMemory(id: string, updates: MemoryUpdate): Promise<void> {
|
|
382
|
+
const memory = await this.getMemoryById(id);
|
|
383
|
+
if (!memory) return;
|
|
384
|
+
|
|
385
|
+
if (updates.importance !== undefined) {
|
|
386
|
+
memory.importance = updates.importance;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (updates.access_count !== undefined) {
|
|
390
|
+
if (typeof updates.access_count === 'object') {
|
|
391
|
+
memory.access_count += updates.access_count.increment ?? 0;
|
|
392
|
+
} else {
|
|
393
|
+
memory.access_count = updates.access_count;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (updates.metadata !== undefined) {
|
|
398
|
+
memory.metadata = { ...memory.metadata, ...updates.metadata };
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
memory.updated_at = new Date();
|
|
402
|
+
|
|
403
|
+
await this.kv.put(this.getMemoryKey(id), JSON.stringify(memory));
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
async deleteMemory(id: string): Promise<void> {
|
|
407
|
+
const memory = await this.getMemoryById(id);
|
|
408
|
+
if (!memory) return;
|
|
409
|
+
|
|
410
|
+
await this.kv.delete(this.getMemoryKey(id));
|
|
411
|
+
|
|
412
|
+
// Remove from user's list
|
|
413
|
+
const userKey = this.getUserKey(memory.user_id);
|
|
414
|
+
const userMemories = await this.kv.get(userKey, 'json') as string[] || [];
|
|
415
|
+
const filtered = userMemories.filter(mId => mId !== id);
|
|
416
|
+
await this.kv.put(userKey, JSON.stringify(filtered));
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
async getMemoryById(id: string): Promise<MemoryRecord | null> {
|
|
420
|
+
const data = await this.kv.get(this.getMemoryKey(id), 'json') as any;
|
|
421
|
+
if (!data) return null;
|
|
422
|
+
|
|
423
|
+
return {
|
|
424
|
+
...data,
|
|
425
|
+
created_at: new Date(data.created_at),
|
|
426
|
+
updated_at: new Date(data.updated_at),
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
async batchUpdateMemories(updates: Array<{ id: string; updates: MemoryUpdate }>): Promise<void> {
|
|
431
|
+
await Promise.all(
|
|
432
|
+
updates.map(({ id, updates: updateData }) => this.updateMemory(id, updateData))
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Mastra Memory Manager for Cloudflare Workers
|
|
439
|
+
* Wraps Mastra's memory system with Cloudflare-compatible storage
|
|
440
|
+
*/
|
|
441
|
+
export class MastraMemoryManager {
|
|
442
|
+
private mastra: Mastra | null = null;
|
|
443
|
+
private storage: IMemoryStorage;
|
|
444
|
+
private config: MastraMemoryConfig;
|
|
445
|
+
|
|
446
|
+
constructor(config: MastraMemoryConfig) {
|
|
447
|
+
this.config = config;
|
|
448
|
+
|
|
449
|
+
// Initialize storage backend
|
|
450
|
+
if (config.storage === 'd1' && config.env?.DB) {
|
|
451
|
+
this.storage = new MastraD1MemoryStorage(config.env.DB, config.tableName);
|
|
452
|
+
} else if (config.storage === 'kv' && config.env?.KV) {
|
|
453
|
+
this.storage = new MastraKVMemoryStorage(config.env.KV, config.kvPrefix);
|
|
454
|
+
} else {
|
|
455
|
+
throw new Error(`Invalid storage configuration: ${config.storage}`);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Initialize Mastra instance with memory
|
|
461
|
+
*/
|
|
462
|
+
async initialize(): Promise<Mastra> {
|
|
463
|
+
if (this.mastra) return this.mastra;
|
|
464
|
+
|
|
465
|
+
// Memory persistence is handled by this manager's own D1/KV storage
|
|
466
|
+
// adapters rather than a Mastra memory backend.
|
|
467
|
+
this.mastra = new Mastra({});
|
|
468
|
+
|
|
469
|
+
// Initialize D1 schema if needed
|
|
470
|
+
if (this.config.storage === 'd1' && this.storage instanceof MastraD1MemoryStorage) {
|
|
471
|
+
await this.storage.initSchema();
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return this.mastra;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Get storage instance (for direct access)
|
|
479
|
+
*/
|
|
480
|
+
getStorage(): IMemoryStorage {
|
|
481
|
+
return this.storage;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Create an agent with memory
|
|
486
|
+
*/
|
|
487
|
+
async createAgent(config: {
|
|
488
|
+
id: string;
|
|
489
|
+
name: string;
|
|
490
|
+
instructions: string;
|
|
491
|
+
model: any;
|
|
492
|
+
tools?: Record<string, any>;
|
|
493
|
+
userId: string;
|
|
494
|
+
threadId?: string;
|
|
495
|
+
resourceId?: string;
|
|
496
|
+
}): Promise<Agent> {
|
|
497
|
+
const mastra = await this.initialize();
|
|
498
|
+
|
|
499
|
+
// Get or create agent with memory
|
|
500
|
+
const agent = new Agent({
|
|
501
|
+
id: config.id,
|
|
502
|
+
name: config.name,
|
|
503
|
+
instructions: config.instructions,
|
|
504
|
+
model: config.model,
|
|
505
|
+
tools: config.tools || {},
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
// Attach memory context
|
|
509
|
+
(agent as any).memoryContext = {
|
|
510
|
+
userId: config.userId,
|
|
511
|
+
threadId: config.threadId || 'default',
|
|
512
|
+
resourceId: config.resourceId,
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
return agent;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Store a message in memory
|
|
520
|
+
*/
|
|
521
|
+
async storeMessage(
|
|
522
|
+
userId: string,
|
|
523
|
+
threadId: string,
|
|
524
|
+
role: 'user' | 'assistant',
|
|
525
|
+
content: string,
|
|
526
|
+
metadata?: Record<string, any>
|
|
527
|
+
): Promise<string> {
|
|
528
|
+
return await this.storage.insertMemory(
|
|
529
|
+
userId,
|
|
530
|
+
'conversation',
|
|
531
|
+
content,
|
|
532
|
+
1.0,
|
|
533
|
+
{
|
|
534
|
+
thread_id: threadId,
|
|
535
|
+
role,
|
|
536
|
+
...metadata,
|
|
537
|
+
}
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Recall conversation history
|
|
543
|
+
*/
|
|
544
|
+
async recallConversation(
|
|
545
|
+
userId: string,
|
|
546
|
+
threadId: string,
|
|
547
|
+
limit: number = 20
|
|
548
|
+
): Promise<Array<{ role: 'user' | 'assistant'; content: string; timestamp: Date }>> {
|
|
549
|
+
const memories = await this.storage.findMemories(
|
|
550
|
+
userId,
|
|
551
|
+
undefined,
|
|
552
|
+
limit,
|
|
553
|
+
{ memoryType: 'conversation' }
|
|
554
|
+
);
|
|
555
|
+
|
|
556
|
+
return memories
|
|
557
|
+
.filter(m => m.metadata?.thread_id === threadId)
|
|
558
|
+
.map(m => ({
|
|
559
|
+
role: m.metadata?.role || 'user',
|
|
560
|
+
content: m.content,
|
|
561
|
+
timestamp: m.created_at,
|
|
562
|
+
}))
|
|
563
|
+
.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Get relevant context for a query
|
|
568
|
+
*/
|
|
569
|
+
async getRelevantContext(
|
|
570
|
+
userId: string,
|
|
571
|
+
query: string,
|
|
572
|
+
limit: number = 5
|
|
573
|
+
): Promise<string> {
|
|
574
|
+
const memories = await this.storage.findSimilarMemories(userId, query, limit);
|
|
575
|
+
|
|
576
|
+
if (memories.length === 0) return '';
|
|
577
|
+
|
|
578
|
+
return memories.map(m => m.content).join('\n\n');
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Quick helper to create Mastra memory manager for Cloudflare Workers
|
|
584
|
+
*
|
|
585
|
+
* @example
|
|
586
|
+
* ```ts
|
|
587
|
+
* const memoryManager = createMastraMemory({
|
|
588
|
+
* storage: 'd1',
|
|
589
|
+
* env: env, // Cloudflare env bindings
|
|
590
|
+
* });
|
|
591
|
+
*
|
|
592
|
+
* const agent = await memoryManager.createAgent({
|
|
593
|
+
* id: 'assistant',
|
|
594
|
+
* name: 'AI Assistant',
|
|
595
|
+
* instructions: 'Help users with their questions',
|
|
596
|
+
* model: openai('gpt-4o'),
|
|
597
|
+
* userId: 'user-123',
|
|
598
|
+
* threadId: 'conversation-1',
|
|
599
|
+
* });
|
|
600
|
+
* ```
|
|
601
|
+
*/
|
|
602
|
+
export function createMastraMemory(config: MastraMemoryConfig): MastraMemoryManager {
|
|
603
|
+
return new MastraMemoryManager(config);
|
|
604
|
+
}
|