chat-agent-toolkit 1.2.33 → 1.2.35

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.
Files changed (42) hide show
  1. package/dist/research-agent.cjs.js.map +1 -1
  2. package/dist/research-agent.es.js.map +1 -1
  3. package/package.json +1 -1
  4. package/src/config/config-manager.ts +295 -295
  5. package/src/config/config-types.ts +65 -65
  6. package/src/config/environment-variables.ts +16 -16
  7. package/src/config/index.ts +26 -26
  8. package/src/config/language-models-database.ts +1434 -1434
  9. package/src/config/mcp-server-registry.ts +24 -24
  10. package/src/config/model-registry.ts +271 -271
  11. package/src/config/model-tester.ts +211 -211
  12. package/src/config/model-utils.ts +193 -193
  13. package/src/config/provider-ui-config.ts +196 -196
  14. package/src/connectors/open-connector.json +130 -130
  15. package/src/index.ts +26 -26
  16. package/src/memory/ARCHITECTURE.md +302 -302
  17. package/src/memory/README.md +224 -224
  18. package/src/memory/agent-memory-manager.ts +416 -416
  19. package/src/memory/example.ts +343 -343
  20. package/src/memory/index.ts +47 -47
  21. package/src/memory/mastra-integration.ts +604 -604
  22. package/src/memory/storage/drizzle-storage.ts +236 -236
  23. package/src/memory/storage/in-memory-storage.ts +551 -551
  24. package/src/memory/storage/storage-interface.ts +68 -68
  25. package/src/memory/types.ts +125 -125
  26. package/src/models/types.ts +4 -4
  27. package/src/tools/index.ts +13 -13
  28. package/src/tools/open-connector-mastra.ts +270 -270
  29. package/src/tools/open-connector-mcp.ts +170 -170
  30. package/src/tools/qwksearch-api-tools.ts +326 -326
  31. package/src/tools/search/doc-utils.ts +139 -139
  32. package/src/tools/search/document.ts +47 -47
  33. package/src/tools/search/index.ts +14 -14
  34. package/src/tools/search/link-summarizer.ts +112 -112
  35. package/src/tools/search/meta-search-types.ts +62 -62
  36. package/src/tools/search/metaSearchAgent.ts +465 -465
  37. package/src/tools/search/search-handlers.ts +97 -97
  38. package/src/tools/search/suggestionGeneratorAgent.ts +56 -56
  39. package/src/types.d.ts +137 -137
  40. package/src/utils/index.ts +2 -2
  41. package/src/utils/markdown-to-html.ts +53 -53
  42. 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
+ }