@mastra/upstash 0.11.0 → 0.11.1-alpha.1
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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +24 -0
- package/dist/_tsup-dts-rollup.d.cts +11 -1
- package/dist/_tsup-dts-rollup.d.ts +11 -1
- package/dist/index.cjs +593 -194
- package/dist/index.js +582 -183
- package/package.json +3 -3
- package/src/storage/index.ts +509 -188
- package/src/storage/upstash.test.ts +47 -15
- package/src/vector/index.ts +118 -33
package/src/storage/index.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { MessageList } from '@mastra/core/agent';
|
|
2
|
+
import type { MastraMessageContentV2, MastraMessageV2 } from '@mastra/core/agent';
|
|
3
|
+
import { ErrorCategory, ErrorDomain, MastraError } from '@mastra/core/error';
|
|
2
4
|
import type { MetricResult, TestInfo } from '@mastra/core/eval';
|
|
3
|
-
import type { StorageThreadType, MastraMessageV1
|
|
5
|
+
import type { StorageThreadType, MastraMessageV1 } from '@mastra/core/memory';
|
|
4
6
|
import {
|
|
5
7
|
MastraStorage,
|
|
6
8
|
TABLE_MESSAGES,
|
|
@@ -270,7 +272,17 @@ export class UpstashStore extends MastraStorage {
|
|
|
270
272
|
// Transform to EvalRow format
|
|
271
273
|
return filteredEvals.map(record => this.transformEvalRecord(record));
|
|
272
274
|
} catch (error) {
|
|
273
|
-
|
|
275
|
+
const mastraError = new MastraError(
|
|
276
|
+
{
|
|
277
|
+
id: 'STORAGE_UPSTASH_STORAGE_GET_EVALS_BY_AGENT_NAME_FAILED',
|
|
278
|
+
domain: ErrorDomain.STORAGE,
|
|
279
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
280
|
+
details: { agentName },
|
|
281
|
+
},
|
|
282
|
+
error,
|
|
283
|
+
);
|
|
284
|
+
this.logger?.trackException(mastraError);
|
|
285
|
+
this.logger.error(mastraError.toString());
|
|
274
286
|
return [];
|
|
275
287
|
}
|
|
276
288
|
}
|
|
@@ -285,8 +297,19 @@ export class UpstashStore extends MastraStorage {
|
|
|
285
297
|
end: args.toDate,
|
|
286
298
|
};
|
|
287
299
|
}
|
|
288
|
-
|
|
289
|
-
|
|
300
|
+
try {
|
|
301
|
+
const { traces } = await this.getTracesPaginated(args);
|
|
302
|
+
return traces;
|
|
303
|
+
} catch (error) {
|
|
304
|
+
throw new MastraError(
|
|
305
|
+
{
|
|
306
|
+
id: 'STORAGE_UPSTASH_STORAGE_GET_TRACES_FAILED',
|
|
307
|
+
domain: ErrorDomain.STORAGE,
|
|
308
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
309
|
+
},
|
|
310
|
+
error,
|
|
311
|
+
);
|
|
312
|
+
}
|
|
290
313
|
}
|
|
291
314
|
|
|
292
315
|
public async getTracesPaginated(
|
|
@@ -388,7 +411,20 @@ export class UpstashStore extends MastraStorage {
|
|
|
388
411
|
hasMore,
|
|
389
412
|
};
|
|
390
413
|
} catch (error) {
|
|
391
|
-
|
|
414
|
+
const mastraError = new MastraError(
|
|
415
|
+
{
|
|
416
|
+
id: 'STORAGE_UPSTASH_STORAGE_GET_TRACES_PAGINATED_FAILED',
|
|
417
|
+
domain: ErrorDomain.STORAGE,
|
|
418
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
419
|
+
details: {
|
|
420
|
+
name: args.name || '',
|
|
421
|
+
scope: args.scope || '',
|
|
422
|
+
},
|
|
423
|
+
},
|
|
424
|
+
error,
|
|
425
|
+
);
|
|
426
|
+
this.logger?.trackException(mastraError);
|
|
427
|
+
this.logger.error(mastraError.toString());
|
|
392
428
|
return {
|
|
393
429
|
traces: [],
|
|
394
430
|
total: 0,
|
|
@@ -408,7 +444,21 @@ export class UpstashStore extends MastraStorage {
|
|
|
408
444
|
}): Promise<void> {
|
|
409
445
|
// Redis is schemaless, so we don't need to create tables
|
|
410
446
|
// But we can store the schema for reference
|
|
411
|
-
|
|
447
|
+
try {
|
|
448
|
+
await this.redis.set(`schema:${tableName}`, schema);
|
|
449
|
+
} catch (error) {
|
|
450
|
+
throw new MastraError(
|
|
451
|
+
{
|
|
452
|
+
id: 'STORAGE_UPSTASH_STORAGE_CREATE_TABLE_FAILED',
|
|
453
|
+
domain: ErrorDomain.STORAGE,
|
|
454
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
455
|
+
details: {
|
|
456
|
+
tableName,
|
|
457
|
+
},
|
|
458
|
+
},
|
|
459
|
+
error,
|
|
460
|
+
);
|
|
461
|
+
}
|
|
412
462
|
}
|
|
413
463
|
|
|
414
464
|
/**
|
|
@@ -427,13 +477,41 @@ export class UpstashStore extends MastraStorage {
|
|
|
427
477
|
|
|
428
478
|
async clearTable({ tableName }: { tableName: TABLE_NAMES }): Promise<void> {
|
|
429
479
|
const pattern = `${tableName}:*`;
|
|
430
|
-
|
|
480
|
+
try {
|
|
481
|
+
await this.scanAndDelete(pattern);
|
|
482
|
+
} catch (error) {
|
|
483
|
+
throw new MastraError(
|
|
484
|
+
{
|
|
485
|
+
id: 'STORAGE_UPSTASH_STORAGE_CLEAR_TABLE_FAILED',
|
|
486
|
+
domain: ErrorDomain.STORAGE,
|
|
487
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
488
|
+
details: {
|
|
489
|
+
tableName,
|
|
490
|
+
},
|
|
491
|
+
},
|
|
492
|
+
error,
|
|
493
|
+
);
|
|
494
|
+
}
|
|
431
495
|
}
|
|
432
496
|
|
|
433
497
|
async insert({ tableName, record }: { tableName: TABLE_NAMES; record: Record<string, any> }): Promise<void> {
|
|
434
498
|
const { key, processedRecord } = this.processRecord(tableName, record);
|
|
435
499
|
|
|
436
|
-
|
|
500
|
+
try {
|
|
501
|
+
await this.redis.set(key, processedRecord);
|
|
502
|
+
} catch (error) {
|
|
503
|
+
throw new MastraError(
|
|
504
|
+
{
|
|
505
|
+
id: 'STORAGE_UPSTASH_STORAGE_INSERT_FAILED',
|
|
506
|
+
domain: ErrorDomain.STORAGE,
|
|
507
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
508
|
+
details: {
|
|
509
|
+
tableName,
|
|
510
|
+
},
|
|
511
|
+
},
|
|
512
|
+
error,
|
|
513
|
+
);
|
|
514
|
+
}
|
|
437
515
|
}
|
|
438
516
|
|
|
439
517
|
async batchInsert(input: { tableName: TABLE_NAMES; records: Record<string, any>[] }): Promise<void> {
|
|
@@ -441,37 +519,79 @@ export class UpstashStore extends MastraStorage {
|
|
|
441
519
|
if (!records.length) return;
|
|
442
520
|
|
|
443
521
|
const batchSize = 1000;
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
const
|
|
449
|
-
|
|
522
|
+
try {
|
|
523
|
+
for (let i = 0; i < records.length; i += batchSize) {
|
|
524
|
+
const batch = records.slice(i, i + batchSize);
|
|
525
|
+
const pipeline = this.redis.pipeline();
|
|
526
|
+
for (const record of batch) {
|
|
527
|
+
const { key, processedRecord } = this.processRecord(tableName, record);
|
|
528
|
+
pipeline.set(key, processedRecord);
|
|
529
|
+
}
|
|
530
|
+
await pipeline.exec();
|
|
450
531
|
}
|
|
451
|
-
|
|
532
|
+
} catch (error) {
|
|
533
|
+
throw new MastraError(
|
|
534
|
+
{
|
|
535
|
+
id: 'STORAGE_UPSTASH_STORAGE_BATCH_INSERT_FAILED',
|
|
536
|
+
domain: ErrorDomain.STORAGE,
|
|
537
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
538
|
+
details: {
|
|
539
|
+
tableName,
|
|
540
|
+
},
|
|
541
|
+
},
|
|
542
|
+
error,
|
|
543
|
+
);
|
|
452
544
|
}
|
|
453
545
|
}
|
|
454
546
|
|
|
455
547
|
async load<R>({ tableName, keys }: { tableName: TABLE_NAMES; keys: Record<string, string> }): Promise<R | null> {
|
|
456
548
|
const key = this.getKey(tableName, keys);
|
|
457
|
-
|
|
458
|
-
|
|
549
|
+
try {
|
|
550
|
+
const data = await this.redis.get<R>(key);
|
|
551
|
+
return data || null;
|
|
552
|
+
} catch (error) {
|
|
553
|
+
throw new MastraError(
|
|
554
|
+
{
|
|
555
|
+
id: 'STORAGE_UPSTASH_STORAGE_LOAD_FAILED',
|
|
556
|
+
domain: ErrorDomain.STORAGE,
|
|
557
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
558
|
+
details: {
|
|
559
|
+
tableName,
|
|
560
|
+
},
|
|
561
|
+
},
|
|
562
|
+
error,
|
|
563
|
+
);
|
|
564
|
+
}
|
|
459
565
|
}
|
|
460
566
|
|
|
461
567
|
async getThreadById({ threadId }: { threadId: string }): Promise<StorageThreadType | null> {
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
568
|
+
try {
|
|
569
|
+
const thread = await this.load<StorageThreadType>({
|
|
570
|
+
tableName: TABLE_THREADS,
|
|
571
|
+
keys: { id: threadId },
|
|
572
|
+
});
|
|
466
573
|
|
|
467
|
-
|
|
574
|
+
if (!thread) return null;
|
|
468
575
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
576
|
+
return {
|
|
577
|
+
...thread,
|
|
578
|
+
createdAt: this.ensureDate(thread.createdAt)!,
|
|
579
|
+
updatedAt: this.ensureDate(thread.updatedAt)!,
|
|
580
|
+
metadata: typeof thread.metadata === 'string' ? JSON.parse(thread.metadata) : thread.metadata,
|
|
581
|
+
};
|
|
582
|
+
} catch (error) {
|
|
583
|
+
throw new MastraError(
|
|
584
|
+
{
|
|
585
|
+
id: 'STORAGE_UPSTASH_STORAGE_GET_THREAD_BY_ID_FAILED',
|
|
586
|
+
domain: ErrorDomain.STORAGE,
|
|
587
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
588
|
+
details: {
|
|
589
|
+
threadId,
|
|
590
|
+
},
|
|
591
|
+
},
|
|
592
|
+
error,
|
|
593
|
+
);
|
|
594
|
+
}
|
|
475
595
|
}
|
|
476
596
|
|
|
477
597
|
/**
|
|
@@ -506,7 +626,19 @@ export class UpstashStore extends MastraStorage {
|
|
|
506
626
|
allThreads.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
|
|
507
627
|
return allThreads;
|
|
508
628
|
} catch (error) {
|
|
509
|
-
|
|
629
|
+
const mastraError = new MastraError(
|
|
630
|
+
{
|
|
631
|
+
id: 'STORAGE_UPSTASH_STORAGE_GET_THREADS_BY_RESOURCE_ID_FAILED',
|
|
632
|
+
domain: ErrorDomain.STORAGE,
|
|
633
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
634
|
+
details: {
|
|
635
|
+
resourceId,
|
|
636
|
+
},
|
|
637
|
+
},
|
|
638
|
+
error,
|
|
639
|
+
);
|
|
640
|
+
this.logger?.trackException(mastraError);
|
|
641
|
+
this.logger.error(mastraError.toString());
|
|
510
642
|
return [];
|
|
511
643
|
}
|
|
512
644
|
}
|
|
@@ -535,7 +667,21 @@ export class UpstashStore extends MastraStorage {
|
|
|
535
667
|
hasMore,
|
|
536
668
|
};
|
|
537
669
|
} catch (error) {
|
|
538
|
-
|
|
670
|
+
const mastraError = new MastraError(
|
|
671
|
+
{
|
|
672
|
+
id: 'STORAGE_UPSTASH_STORAGE_GET_THREADS_BY_RESOURCE_ID_PAGINATED_FAILED',
|
|
673
|
+
domain: ErrorDomain.STORAGE,
|
|
674
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
675
|
+
details: {
|
|
676
|
+
resourceId,
|
|
677
|
+
page,
|
|
678
|
+
perPage,
|
|
679
|
+
},
|
|
680
|
+
},
|
|
681
|
+
error,
|
|
682
|
+
);
|
|
683
|
+
this.logger?.trackException(mastraError);
|
|
684
|
+
this.logger.error(mastraError.toString());
|
|
539
685
|
return {
|
|
540
686
|
threads: [],
|
|
541
687
|
total: 0,
|
|
@@ -547,11 +693,28 @@ export class UpstashStore extends MastraStorage {
|
|
|
547
693
|
}
|
|
548
694
|
|
|
549
695
|
async saveThread({ thread }: { thread: StorageThreadType }): Promise<StorageThreadType> {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
696
|
+
try {
|
|
697
|
+
await this.insert({
|
|
698
|
+
tableName: TABLE_THREADS,
|
|
699
|
+
record: thread,
|
|
700
|
+
});
|
|
701
|
+
return thread;
|
|
702
|
+
} catch (error) {
|
|
703
|
+
const mastraError = new MastraError(
|
|
704
|
+
{
|
|
705
|
+
id: 'STORAGE_UPSTASH_STORAGE_SAVE_THREAD_FAILED',
|
|
706
|
+
domain: ErrorDomain.STORAGE,
|
|
707
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
708
|
+
details: {
|
|
709
|
+
threadId: thread.id,
|
|
710
|
+
},
|
|
711
|
+
},
|
|
712
|
+
error,
|
|
713
|
+
);
|
|
714
|
+
this.logger?.trackException(mastraError);
|
|
715
|
+
this.logger.error(mastraError.toString());
|
|
716
|
+
throw mastraError;
|
|
717
|
+
}
|
|
555
718
|
}
|
|
556
719
|
|
|
557
720
|
async updateThread({
|
|
@@ -565,7 +728,15 @@ export class UpstashStore extends MastraStorage {
|
|
|
565
728
|
}): Promise<StorageThreadType> {
|
|
566
729
|
const thread = await this.getThreadById({ threadId: id });
|
|
567
730
|
if (!thread) {
|
|
568
|
-
throw new
|
|
731
|
+
throw new MastraError({
|
|
732
|
+
id: 'STORAGE_UPSTASH_STORAGE_UPDATE_THREAD_FAILED',
|
|
733
|
+
domain: ErrorDomain.STORAGE,
|
|
734
|
+
category: ErrorCategory.USER,
|
|
735
|
+
text: `Thread ${id} not found`,
|
|
736
|
+
details: {
|
|
737
|
+
threadId: id,
|
|
738
|
+
},
|
|
739
|
+
});
|
|
569
740
|
}
|
|
570
741
|
|
|
571
742
|
const updatedThread = {
|
|
@@ -577,30 +748,58 @@ export class UpstashStore extends MastraStorage {
|
|
|
577
748
|
},
|
|
578
749
|
};
|
|
579
750
|
|
|
580
|
-
|
|
581
|
-
|
|
751
|
+
try {
|
|
752
|
+
await this.saveThread({ thread: updatedThread });
|
|
753
|
+
return updatedThread;
|
|
754
|
+
} catch (error) {
|
|
755
|
+
throw new MastraError(
|
|
756
|
+
{
|
|
757
|
+
id: 'STORAGE_UPSTASH_STORAGE_UPDATE_THREAD_FAILED',
|
|
758
|
+
domain: ErrorDomain.STORAGE,
|
|
759
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
760
|
+
details: {
|
|
761
|
+
threadId: id,
|
|
762
|
+
},
|
|
763
|
+
},
|
|
764
|
+
error,
|
|
765
|
+
);
|
|
766
|
+
}
|
|
582
767
|
}
|
|
583
768
|
|
|
584
769
|
async deleteThread({ threadId }: { threadId: string }): Promise<void> {
|
|
585
770
|
// Delete thread metadata and sorted set
|
|
586
771
|
const threadKey = this.getKey(TABLE_THREADS, { id: threadId });
|
|
587
772
|
const threadMessagesKey = this.getThreadMessagesKey(threadId);
|
|
588
|
-
|
|
773
|
+
try {
|
|
774
|
+
const messageIds: string[] = await this.redis.zrange(threadMessagesKey, 0, -1);
|
|
589
775
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
776
|
+
const pipeline = this.redis.pipeline();
|
|
777
|
+
pipeline.del(threadKey);
|
|
778
|
+
pipeline.del(threadMessagesKey);
|
|
593
779
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
780
|
+
for (let i = 0; i < messageIds.length; i++) {
|
|
781
|
+
const messageId = messageIds[i];
|
|
782
|
+
const messageKey = this.getMessageKey(threadId, messageId as string);
|
|
783
|
+
pipeline.del(messageKey);
|
|
784
|
+
}
|
|
599
785
|
|
|
600
|
-
|
|
786
|
+
await pipeline.exec();
|
|
601
787
|
|
|
602
|
-
|
|
603
|
-
|
|
788
|
+
// Bulk delete all message keys for this thread if any remain
|
|
789
|
+
await this.scanAndDelete(this.getMessageKey(threadId, '*'));
|
|
790
|
+
} catch (error) {
|
|
791
|
+
throw new MastraError(
|
|
792
|
+
{
|
|
793
|
+
id: 'STORAGE_UPSTASH_STORAGE_DELETE_THREAD_FAILED',
|
|
794
|
+
domain: ErrorDomain.STORAGE,
|
|
795
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
796
|
+
details: {
|
|
797
|
+
threadId,
|
|
798
|
+
},
|
|
799
|
+
},
|
|
800
|
+
error,
|
|
801
|
+
);
|
|
802
|
+
}
|
|
604
803
|
}
|
|
605
804
|
|
|
606
805
|
async saveMessages(args: { messages: MastraMessageV1[]; format?: undefined | 'v1' }): Promise<MastraMessageV1[]>;
|
|
@@ -612,14 +811,25 @@ export class UpstashStore extends MastraStorage {
|
|
|
612
811
|
if (messages.length === 0) return [];
|
|
613
812
|
|
|
614
813
|
const threadId = messages[0]?.threadId;
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
814
|
+
try {
|
|
815
|
+
if (!threadId) {
|
|
816
|
+
throw new Error('Thread ID is required');
|
|
817
|
+
}
|
|
618
818
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
819
|
+
// Check if thread exists
|
|
820
|
+
const thread = await this.getThreadById({ threadId });
|
|
821
|
+
if (!thread) {
|
|
822
|
+
throw new Error(`Thread ${threadId} not found`);
|
|
823
|
+
}
|
|
824
|
+
} catch (error) {
|
|
825
|
+
throw new MastraError(
|
|
826
|
+
{
|
|
827
|
+
id: 'STORAGE_UPSTASH_STORAGE_SAVE_MESSAGES_INVALID_ARGS',
|
|
828
|
+
domain: ErrorDomain.STORAGE,
|
|
829
|
+
category: ErrorCategory.USER,
|
|
830
|
+
},
|
|
831
|
+
error,
|
|
832
|
+
);
|
|
623
833
|
}
|
|
624
834
|
|
|
625
835
|
// Add an index to each message to maintain order
|
|
@@ -632,41 +842,55 @@ export class UpstashStore extends MastraStorage {
|
|
|
632
842
|
const threadKey = this.getKey(TABLE_THREADS, { id: threadId });
|
|
633
843
|
const existingThread = await this.redis.get<StorageThreadType>(threadKey);
|
|
634
844
|
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
const
|
|
642
|
-
|
|
643
|
-
|
|
845
|
+
try {
|
|
846
|
+
const batchSize = 1000;
|
|
847
|
+
for (let i = 0; i < messagesWithIndex.length; i += batchSize) {
|
|
848
|
+
const batch = messagesWithIndex.slice(i, i + batchSize);
|
|
849
|
+
const pipeline = this.redis.pipeline();
|
|
850
|
+
|
|
851
|
+
for (const message of batch) {
|
|
852
|
+
const key = this.getMessageKey(message.threadId!, message.id);
|
|
853
|
+
const createdAtScore = new Date(message.createdAt).getTime();
|
|
854
|
+
const score = message._index !== undefined ? message._index : createdAtScore;
|
|
855
|
+
|
|
856
|
+
// Store the message data
|
|
857
|
+
pipeline.set(key, message);
|
|
858
|
+
|
|
859
|
+
// Add to sorted set for this thread
|
|
860
|
+
pipeline.zadd(this.getThreadMessagesKey(message.threadId!), {
|
|
861
|
+
score,
|
|
862
|
+
member: message.id,
|
|
863
|
+
});
|
|
864
|
+
}
|
|
644
865
|
|
|
645
|
-
//
|
|
646
|
-
|
|
866
|
+
// Update the thread's updatedAt field (only in the first batch)
|
|
867
|
+
if (i === 0 && existingThread) {
|
|
868
|
+
const updatedThread = {
|
|
869
|
+
...existingThread,
|
|
870
|
+
updatedAt: new Date(),
|
|
871
|
+
};
|
|
872
|
+
pipeline.set(threadKey, this.processRecord(TABLE_THREADS, updatedThread).processedRecord);
|
|
873
|
+
}
|
|
647
874
|
|
|
648
|
-
|
|
649
|
-
pipeline.zadd(this.getThreadMessagesKey(message.threadId!), {
|
|
650
|
-
score,
|
|
651
|
-
member: message.id,
|
|
652
|
-
});
|
|
875
|
+
await pipeline.exec();
|
|
653
876
|
}
|
|
654
877
|
|
|
655
|
-
|
|
656
|
-
if (
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
878
|
+
const list = new MessageList().add(messages, 'memory');
|
|
879
|
+
if (format === `v2`) return list.get.all.v2();
|
|
880
|
+
return list.get.all.v1();
|
|
881
|
+
} catch (error) {
|
|
882
|
+
throw new MastraError(
|
|
883
|
+
{
|
|
884
|
+
id: 'STORAGE_UPSTASH_STORAGE_SAVE_MESSAGES_FAILED',
|
|
885
|
+
domain: ErrorDomain.STORAGE,
|
|
886
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
887
|
+
details: {
|
|
888
|
+
threadId,
|
|
889
|
+
},
|
|
890
|
+
},
|
|
891
|
+
error,
|
|
892
|
+
);
|
|
665
893
|
}
|
|
666
|
-
|
|
667
|
-
const list = new MessageList().add(messages, 'memory');
|
|
668
|
-
if (format === `v2`) return list.get.all.v2();
|
|
669
|
-
return list.get.all.v1();
|
|
670
894
|
}
|
|
671
895
|
|
|
672
896
|
private async _getIncludedMessages(
|
|
@@ -733,89 +957,96 @@ export class UpstashStore extends MastraStorage {
|
|
|
733
957
|
format,
|
|
734
958
|
}: StorageGetMessagesArg & { format?: 'v1' | 'v2' }): Promise<MastraMessageV1[] | MastraMessageV2[]> {
|
|
735
959
|
const threadMessagesKey = this.getThreadMessagesKey(threadId);
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
if (typeof selectBy?.last === 'number') {
|
|
740
|
-
limit = Math.max(0, selectBy.last);
|
|
741
|
-
} else if (selectBy?.last === false) {
|
|
742
|
-
limit = 0;
|
|
743
|
-
} else {
|
|
744
|
-
// No limit specified - get all messages
|
|
745
|
-
limit = Number.MAX_SAFE_INTEGER;
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
const messageIds = new Set<string>();
|
|
749
|
-
const messageIdToThreadIds: Record<string, string> = {};
|
|
960
|
+
try {
|
|
961
|
+
const allMessageIds = await this.redis.zrange(threadMessagesKey, 0, -1);
|
|
962
|
+
const limit = this.resolveMessageLimit({ last: selectBy?.last, defaultLimit: Number.MAX_SAFE_INTEGER });
|
|
750
963
|
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
}
|
|
964
|
+
const messageIds = new Set<string>();
|
|
965
|
+
const messageIdToThreadIds: Record<string, string> = {};
|
|
754
966
|
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
const allIds = await this.redis.zrange(threadMessagesKey, 0, -1);
|
|
759
|
-
allIds.forEach(id => {
|
|
760
|
-
messageIds.add(id as string);
|
|
761
|
-
messageIdToThreadIds[id as string] = threadId;
|
|
762
|
-
});
|
|
763
|
-
} else if (limit > 0) {
|
|
764
|
-
// Get limited number of recent messages
|
|
765
|
-
const latestIds = await this.redis.zrange(threadMessagesKey, -limit, -1);
|
|
766
|
-
latestIds.forEach(id => {
|
|
767
|
-
messageIds.add(id as string);
|
|
768
|
-
messageIdToThreadIds[id as string] = threadId;
|
|
769
|
-
});
|
|
770
|
-
}
|
|
967
|
+
if (limit === 0 && !selectBy?.include) {
|
|
968
|
+
return [];
|
|
969
|
+
}
|
|
771
970
|
|
|
772
|
-
|
|
971
|
+
// Then get the most recent messages (or all if no limit)
|
|
972
|
+
if (limit === Number.MAX_SAFE_INTEGER) {
|
|
973
|
+
// Get all messages
|
|
974
|
+
const allIds = await this.redis.zrange(threadMessagesKey, 0, -1);
|
|
975
|
+
allIds.forEach(id => {
|
|
976
|
+
messageIds.add(id as string);
|
|
977
|
+
messageIdToThreadIds[id as string] = threadId;
|
|
978
|
+
});
|
|
979
|
+
} else if (limit > 0) {
|
|
980
|
+
// Get limited number of recent messages
|
|
981
|
+
const latestIds = await this.redis.zrange(threadMessagesKey, -limit, -1);
|
|
982
|
+
latestIds.forEach(id => {
|
|
983
|
+
messageIds.add(id as string);
|
|
984
|
+
messageIdToThreadIds[id as string] = threadId;
|
|
985
|
+
});
|
|
986
|
+
}
|
|
773
987
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
988
|
+
const includedMessages = await this._getIncludedMessages(threadId, selectBy);
|
|
989
|
+
|
|
990
|
+
// Fetch all needed messages in parallel
|
|
991
|
+
const messages = [
|
|
992
|
+
...includedMessages,
|
|
993
|
+
...((
|
|
994
|
+
await Promise.all(
|
|
995
|
+
Array.from(messageIds).map(async id => {
|
|
996
|
+
const tId = messageIdToThreadIds[id] || threadId;
|
|
997
|
+
const byThreadId = await this.redis.get<MastraMessageV2 & { _index?: number }>(
|
|
998
|
+
this.getMessageKey(tId, id),
|
|
999
|
+
);
|
|
1000
|
+
if (byThreadId) return byThreadId;
|
|
1001
|
+
|
|
1002
|
+
return null;
|
|
1003
|
+
}),
|
|
1004
|
+
)
|
|
1005
|
+
).filter(msg => msg !== null) as (MastraMessageV2 & { _index?: number })[]),
|
|
1006
|
+
];
|
|
783
1007
|
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
)
|
|
787
|
-
).filter(msg => msg !== null) as (MastraMessageV2 & { _index?: number })[]),
|
|
788
|
-
];
|
|
1008
|
+
// Sort messages by their position in the sorted set
|
|
1009
|
+
messages.sort((a, b) => allMessageIds.indexOf(a!.id) - allMessageIds.indexOf(b!.id));
|
|
789
1010
|
|
|
790
|
-
|
|
791
|
-
|
|
1011
|
+
const seen = new Set<string>();
|
|
1012
|
+
const dedupedMessages = messages.filter(row => {
|
|
1013
|
+
if (seen.has(row.id)) return false;
|
|
1014
|
+
seen.add(row.id);
|
|
1015
|
+
return true;
|
|
1016
|
+
});
|
|
792
1017
|
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
1018
|
+
// Remove _index before returning and handle format conversion properly
|
|
1019
|
+
const prepared = dedupedMessages
|
|
1020
|
+
.filter(message => message !== null && message !== undefined)
|
|
1021
|
+
.map(message => {
|
|
1022
|
+
const { _index, ...messageWithoutIndex } = message as MastraMessageV2 & { _index?: number };
|
|
1023
|
+
return messageWithoutIndex as unknown as MastraMessageV1;
|
|
1024
|
+
});
|
|
799
1025
|
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
1026
|
+
// For backward compatibility, return messages directly without using MessageList
|
|
1027
|
+
// since MessageList has deduplication logic that can cause issues
|
|
1028
|
+
if (format === 'v2') {
|
|
1029
|
+
// Convert V1 format back to V2 format
|
|
1030
|
+
return prepared.map(msg => ({
|
|
1031
|
+
...msg,
|
|
1032
|
+
content: msg.content || { format: 2, parts: [{ type: 'text', text: '' }] },
|
|
1033
|
+
})) as MastraMessageV2[];
|
|
1034
|
+
}
|
|
807
1035
|
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
1036
|
+
return prepared;
|
|
1037
|
+
} catch (error) {
|
|
1038
|
+
throw new MastraError(
|
|
1039
|
+
{
|
|
1040
|
+
id: 'STORAGE_UPSTASH_STORAGE_GET_MESSAGES_FAILED',
|
|
1041
|
+
domain: ErrorDomain.STORAGE,
|
|
1042
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1043
|
+
details: {
|
|
1044
|
+
threadId,
|
|
1045
|
+
},
|
|
1046
|
+
},
|
|
1047
|
+
error,
|
|
1048
|
+
);
|
|
816
1049
|
}
|
|
817
|
-
|
|
818
|
-
return prepared;
|
|
819
1050
|
}
|
|
820
1051
|
|
|
821
1052
|
public async getMessagesPaginated(
|
|
@@ -830,10 +1061,10 @@ export class UpstashStore extends MastraStorage {
|
|
|
830
1061
|
const threadMessagesKey = this.getThreadMessagesKey(threadId);
|
|
831
1062
|
const messages: (MastraMessageV2 | MastraMessageV1)[] = [];
|
|
832
1063
|
|
|
833
|
-
const includedMessages = await this._getIncludedMessages(threadId, selectBy);
|
|
834
|
-
messages.push(...includedMessages);
|
|
835
|
-
|
|
836
1064
|
try {
|
|
1065
|
+
const includedMessages = await this._getIncludedMessages(threadId, selectBy);
|
|
1066
|
+
messages.push(...includedMessages);
|
|
1067
|
+
|
|
837
1068
|
const allMessageIds = await this.redis.zrange(threadMessagesKey, 0, -1);
|
|
838
1069
|
if (allMessageIds.length === 0) {
|
|
839
1070
|
return {
|
|
@@ -890,7 +1121,19 @@ export class UpstashStore extends MastraStorage {
|
|
|
890
1121
|
hasMore,
|
|
891
1122
|
};
|
|
892
1123
|
} catch (error) {
|
|
893
|
-
|
|
1124
|
+
const mastraError = new MastraError(
|
|
1125
|
+
{
|
|
1126
|
+
id: 'STORAGE_UPSTASH_STORAGE_GET_MESSAGES_PAGINATED_FAILED',
|
|
1127
|
+
domain: ErrorDomain.STORAGE,
|
|
1128
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1129
|
+
details: {
|
|
1130
|
+
threadId,
|
|
1131
|
+
},
|
|
1132
|
+
},
|
|
1133
|
+
error,
|
|
1134
|
+
);
|
|
1135
|
+
this.logger.error(mastraError.toString());
|
|
1136
|
+
this.logger?.trackException(mastraError);
|
|
894
1137
|
return {
|
|
895
1138
|
messages: [],
|
|
896
1139
|
total: 0,
|
|
@@ -908,17 +1151,33 @@ export class UpstashStore extends MastraStorage {
|
|
|
908
1151
|
snapshot: WorkflowRunState;
|
|
909
1152
|
}): Promise<void> {
|
|
910
1153
|
const { namespace = 'workflows', workflowName, runId, snapshot } = params;
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
1154
|
+
try {
|
|
1155
|
+
await this.insert({
|
|
1156
|
+
tableName: TABLE_WORKFLOW_SNAPSHOT,
|
|
1157
|
+
record: {
|
|
1158
|
+
namespace,
|
|
1159
|
+
workflow_name: workflowName,
|
|
1160
|
+
run_id: runId,
|
|
1161
|
+
snapshot,
|
|
1162
|
+
createdAt: new Date(),
|
|
1163
|
+
updatedAt: new Date(),
|
|
1164
|
+
},
|
|
1165
|
+
});
|
|
1166
|
+
} catch (error) {
|
|
1167
|
+
throw new MastraError(
|
|
1168
|
+
{
|
|
1169
|
+
id: 'STORAGE_UPSTASH_STORAGE_PERSIST_WORKFLOW_SNAPSHOT_FAILED',
|
|
1170
|
+
domain: ErrorDomain.STORAGE,
|
|
1171
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1172
|
+
details: {
|
|
1173
|
+
namespace,
|
|
1174
|
+
workflowName,
|
|
1175
|
+
runId,
|
|
1176
|
+
},
|
|
1177
|
+
},
|
|
1178
|
+
error,
|
|
1179
|
+
);
|
|
1180
|
+
}
|
|
922
1181
|
}
|
|
923
1182
|
|
|
924
1183
|
async loadWorkflowSnapshot(params: {
|
|
@@ -932,14 +1191,30 @@ export class UpstashStore extends MastraStorage {
|
|
|
932
1191
|
workflow_name: workflowName,
|
|
933
1192
|
run_id: runId,
|
|
934
1193
|
});
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
1194
|
+
try {
|
|
1195
|
+
const data = await this.redis.get<{
|
|
1196
|
+
namespace: string;
|
|
1197
|
+
workflow_name: string;
|
|
1198
|
+
run_id: string;
|
|
1199
|
+
snapshot: WorkflowRunState;
|
|
1200
|
+
}>(key);
|
|
1201
|
+
if (!data) return null;
|
|
1202
|
+
return data.snapshot;
|
|
1203
|
+
} catch (error) {
|
|
1204
|
+
throw new MastraError(
|
|
1205
|
+
{
|
|
1206
|
+
id: 'STORAGE_UPSTASH_STORAGE_LOAD_WORKFLOW_SNAPSHOT_FAILED',
|
|
1207
|
+
domain: ErrorDomain.STORAGE,
|
|
1208
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1209
|
+
details: {
|
|
1210
|
+
namespace,
|
|
1211
|
+
workflowName,
|
|
1212
|
+
runId,
|
|
1213
|
+
},
|
|
1214
|
+
},
|
|
1215
|
+
error,
|
|
1216
|
+
);
|
|
1217
|
+
}
|
|
943
1218
|
}
|
|
944
1219
|
|
|
945
1220
|
/**
|
|
@@ -1062,7 +1337,20 @@ export class UpstashStore extends MastraStorage {
|
|
|
1062
1337
|
};
|
|
1063
1338
|
} catch (error) {
|
|
1064
1339
|
const { page = 0, perPage = 100 } = options || {};
|
|
1065
|
-
|
|
1340
|
+
const mastraError = new MastraError(
|
|
1341
|
+
{
|
|
1342
|
+
id: 'STORAGE_UPSTASH_STORAGE_GET_EVALS_FAILED',
|
|
1343
|
+
domain: ErrorDomain.STORAGE,
|
|
1344
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1345
|
+
details: {
|
|
1346
|
+
page,
|
|
1347
|
+
perPage,
|
|
1348
|
+
},
|
|
1349
|
+
},
|
|
1350
|
+
error,
|
|
1351
|
+
);
|
|
1352
|
+
this.logger.error(mastraError.toString());
|
|
1353
|
+
this.logger?.trackException(mastraError);
|
|
1066
1354
|
return {
|
|
1067
1355
|
evals: [],
|
|
1068
1356
|
total: 0,
|
|
@@ -1145,8 +1433,19 @@ export class UpstashStore extends MastraStorage {
|
|
|
1145
1433
|
|
|
1146
1434
|
return { runs, total };
|
|
1147
1435
|
} catch (error) {
|
|
1148
|
-
|
|
1149
|
-
|
|
1436
|
+
throw new MastraError(
|
|
1437
|
+
{
|
|
1438
|
+
id: 'STORAGE_UPSTASH_STORAGE_GET_WORKFLOW_RUNS_FAILED',
|
|
1439
|
+
domain: ErrorDomain.STORAGE,
|
|
1440
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1441
|
+
details: {
|
|
1442
|
+
namespace,
|
|
1443
|
+
workflowName: workflowName || '',
|
|
1444
|
+
resourceId: resourceId || '',
|
|
1445
|
+
},
|
|
1446
|
+
},
|
|
1447
|
+
error,
|
|
1448
|
+
);
|
|
1150
1449
|
}
|
|
1151
1450
|
}
|
|
1152
1451
|
|
|
@@ -1179,12 +1478,34 @@ export class UpstashStore extends MastraStorage {
|
|
|
1179
1478
|
if (!data) return null;
|
|
1180
1479
|
return this.parseWorkflowRun(data);
|
|
1181
1480
|
} catch (error) {
|
|
1182
|
-
|
|
1183
|
-
|
|
1481
|
+
throw new MastraError(
|
|
1482
|
+
{
|
|
1483
|
+
id: 'STORAGE_UPSTASH_STORAGE_GET_WORKFLOW_RUN_BY_ID_FAILED',
|
|
1484
|
+
domain: ErrorDomain.STORAGE,
|
|
1485
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1486
|
+
details: {
|
|
1487
|
+
namespace,
|
|
1488
|
+
runId,
|
|
1489
|
+
workflowName: workflowName || '',
|
|
1490
|
+
},
|
|
1491
|
+
},
|
|
1492
|
+
error,
|
|
1493
|
+
);
|
|
1184
1494
|
}
|
|
1185
1495
|
}
|
|
1186
1496
|
|
|
1187
1497
|
async close(): Promise<void> {
|
|
1188
1498
|
// No explicit cleanup needed for Upstash Redis
|
|
1189
1499
|
}
|
|
1500
|
+
|
|
1501
|
+
async updateMessages(_args: {
|
|
1502
|
+
messages: Partial<Omit<MastraMessageV2, 'createdAt'>> &
|
|
1503
|
+
{
|
|
1504
|
+
id: string;
|
|
1505
|
+
content?: { metadata?: MastraMessageContentV2['metadata']; content?: MastraMessageContentV2['content'] };
|
|
1506
|
+
}[];
|
|
1507
|
+
}): Promise<MastraMessageV2[]> {
|
|
1508
|
+
this.logger.error('updateMessages is not yet implemented in UpstashStore');
|
|
1509
|
+
throw new Error('Method not implemented');
|
|
1510
|
+
}
|
|
1190
1511
|
}
|