@mastra/libsql 0.10.1 → 0.10.2-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 +8 -8
- package/CHANGELOG.md +27 -0
- package/dist/_tsup-dts-rollup.d.cts +53 -3
- package/dist/_tsup-dts-rollup.d.ts +53 -3
- package/dist/index.cjs +347 -129
- package/dist/index.js +347 -129
- package/package.json +10 -10
- package/src/storage/index.test.ts +356 -6
- package/src/storage/index.ts +437 -157
package/src/storage/index.ts
CHANGED
|
@@ -6,20 +6,23 @@ import type { MetricResult, TestInfo } from '@mastra/core/eval';
|
|
|
6
6
|
import type { MastraMessageV1, StorageThreadType } from '@mastra/core/memory';
|
|
7
7
|
import {
|
|
8
8
|
MastraStorage,
|
|
9
|
+
TABLE_EVALS,
|
|
9
10
|
TABLE_MESSAGES,
|
|
10
11
|
TABLE_THREADS,
|
|
11
12
|
TABLE_TRACES,
|
|
12
13
|
TABLE_WORKFLOW_SNAPSHOT,
|
|
13
|
-
TABLE_EVALS,
|
|
14
14
|
} from '@mastra/core/storage';
|
|
15
15
|
import type {
|
|
16
16
|
EvalRow,
|
|
17
|
+
PaginationArgs,
|
|
18
|
+
PaginationInfo,
|
|
17
19
|
StorageColumn,
|
|
18
20
|
StorageGetMessagesArg,
|
|
19
21
|
TABLE_NAMES,
|
|
20
22
|
WorkflowRun,
|
|
21
23
|
WorkflowRuns,
|
|
22
24
|
} from '@mastra/core/storage';
|
|
25
|
+
import type { Trace } from '@mastra/core/telemetry';
|
|
23
26
|
import { parseSqlIdentifier } from '@mastra/core/utils';
|
|
24
27
|
import type { WorkflowRunState } from '@mastra/core/workflows';
|
|
25
28
|
|
|
@@ -122,6 +125,63 @@ export class LibSQLStore extends MastraStorage {
|
|
|
122
125
|
}
|
|
123
126
|
}
|
|
124
127
|
|
|
128
|
+
protected getSqlType(type: StorageColumn['type']): string {
|
|
129
|
+
switch (type) {
|
|
130
|
+
case 'bigint':
|
|
131
|
+
return 'INTEGER'; // SQLite uses INTEGER for all integer sizes
|
|
132
|
+
case 'jsonb':
|
|
133
|
+
return 'TEXT'; // Store JSON as TEXT in SQLite
|
|
134
|
+
default:
|
|
135
|
+
return super.getSqlType(type);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Alters table schema to add columns if they don't exist
|
|
141
|
+
* @param tableName Name of the table
|
|
142
|
+
* @param schema Schema of the table
|
|
143
|
+
* @param ifNotExists Array of column names to add if they don't exist
|
|
144
|
+
*/
|
|
145
|
+
async alterTable({
|
|
146
|
+
tableName,
|
|
147
|
+
schema,
|
|
148
|
+
ifNotExists,
|
|
149
|
+
}: {
|
|
150
|
+
tableName: TABLE_NAMES;
|
|
151
|
+
schema: Record<string, StorageColumn>;
|
|
152
|
+
ifNotExists: string[];
|
|
153
|
+
}): Promise<void> {
|
|
154
|
+
const parsedTableName = parseSqlIdentifier(tableName, 'table name');
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
// 1. Get existing columns using PRAGMA
|
|
158
|
+
const pragmaQuery = `PRAGMA table_info(${parsedTableName})`;
|
|
159
|
+
const result = await this.client.execute(pragmaQuery);
|
|
160
|
+
const existingColumnNames = new Set(result.rows.map((row: any) => row.name.toLowerCase()));
|
|
161
|
+
|
|
162
|
+
// 2. Add missing columns
|
|
163
|
+
for (const columnName of ifNotExists) {
|
|
164
|
+
if (!existingColumnNames.has(columnName.toLowerCase()) && schema[columnName]) {
|
|
165
|
+
const columnDef = schema[columnName];
|
|
166
|
+
const sqlType = this.getSqlType(columnDef.type); // ensure this exists or implement
|
|
167
|
+
const nullable = columnDef.nullable === false ? 'NOT NULL' : '';
|
|
168
|
+
// In SQLite, you must provide a DEFAULT if adding a NOT NULL column to a non-empty table
|
|
169
|
+
const defaultValue = columnDef.nullable === false ? this.getDefaultValue(columnDef.type) : '';
|
|
170
|
+
const alterSql =
|
|
171
|
+
`ALTER TABLE ${parsedTableName} ADD COLUMN "${columnName}" ${sqlType} ${nullable} ${defaultValue}`.trim();
|
|
172
|
+
|
|
173
|
+
await this.client.execute(alterSql);
|
|
174
|
+
this.logger?.debug?.(`Added column ${columnName} to table ${parsedTableName}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
} catch (error) {
|
|
178
|
+
this.logger?.error?.(
|
|
179
|
+
`Error altering table ${tableName}: ${error instanceof Error ? error.message : String(error)}`,
|
|
180
|
+
);
|
|
181
|
+
throw new Error(`Failed to alter table ${tableName}: ${error}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
125
185
|
async clearTable({ tableName }: { tableName: TABLE_NAMES }): Promise<void> {
|
|
126
186
|
const parsedTableName = parseSqlIdentifier(tableName, 'table name');
|
|
127
187
|
try {
|
|
@@ -274,24 +334,97 @@ export class LibSQLStore extends MastraStorage {
|
|
|
274
334
|
};
|
|
275
335
|
}
|
|
276
336
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
}
|
|
337
|
+
/**
|
|
338
|
+
* @deprecated use getThreadsByResourceIdPaginated instead for paginated results.
|
|
339
|
+
*/
|
|
340
|
+
public async getThreadsByResourceId(args: { resourceId: string }): Promise<StorageThreadType[]> {
|
|
341
|
+
const { resourceId } = args;
|
|
342
|
+
|
|
343
|
+
try {
|
|
344
|
+
const baseQuery = `FROM ${TABLE_THREADS} WHERE resourceId = ?`;
|
|
345
|
+
const queryParams: InValue[] = [resourceId];
|
|
346
|
+
|
|
347
|
+
const mapRowToStorageThreadType = (row: any): StorageThreadType => ({
|
|
348
|
+
id: row.id as string,
|
|
349
|
+
resourceId: row.resourceId as string,
|
|
350
|
+
title: row.title as string,
|
|
351
|
+
createdAt: new Date(row.createdAt as string), // Convert string to Date
|
|
352
|
+
updatedAt: new Date(row.updatedAt as string), // Convert string to Date
|
|
353
|
+
metadata: typeof row.metadata === 'string' ? JSON.parse(row.metadata) : row.metadata,
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
// Non-paginated path
|
|
357
|
+
const result = await this.client.execute({
|
|
358
|
+
sql: `SELECT * ${baseQuery} ORDER BY createdAt DESC`,
|
|
359
|
+
args: queryParams,
|
|
360
|
+
});
|
|
282
361
|
|
|
283
|
-
|
|
362
|
+
if (!result.rows) {
|
|
363
|
+
return [];
|
|
364
|
+
}
|
|
365
|
+
return result.rows.map(mapRowToStorageThreadType);
|
|
366
|
+
} catch (error) {
|
|
367
|
+
this.logger.error(`Error getting threads for resource ${resourceId}:`, error);
|
|
284
368
|
return [];
|
|
285
369
|
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
public async getThreadsByResourceIdPaginated(
|
|
373
|
+
args: {
|
|
374
|
+
resourceId: string;
|
|
375
|
+
} & PaginationArgs,
|
|
376
|
+
): Promise<PaginationInfo & { threads: StorageThreadType[] }> {
|
|
377
|
+
const { resourceId, page = 0, perPage = 100 } = args;
|
|
378
|
+
|
|
379
|
+
try {
|
|
380
|
+
const baseQuery = `FROM ${TABLE_THREADS} WHERE resourceId = ?`;
|
|
381
|
+
const queryParams: InValue[] = [resourceId];
|
|
382
|
+
|
|
383
|
+
const mapRowToStorageThreadType = (row: any): StorageThreadType => ({
|
|
384
|
+
id: row.id as string,
|
|
385
|
+
resourceId: row.resourceId as string,
|
|
386
|
+
title: row.title as string,
|
|
387
|
+
createdAt: new Date(row.createdAt as string), // Convert string to Date
|
|
388
|
+
updatedAt: new Date(row.updatedAt as string), // Convert string to Date
|
|
389
|
+
metadata: typeof row.metadata === 'string' ? JSON.parse(row.metadata) : row.metadata,
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
const currentOffset = page * perPage;
|
|
286
393
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
394
|
+
const countResult = await this.client.execute({
|
|
395
|
+
sql: `SELECT COUNT(*) as count ${baseQuery}`,
|
|
396
|
+
args: queryParams,
|
|
397
|
+
});
|
|
398
|
+
const total = Number(countResult.rows?.[0]?.count ?? 0);
|
|
399
|
+
|
|
400
|
+
if (total === 0) {
|
|
401
|
+
return {
|
|
402
|
+
threads: [],
|
|
403
|
+
total: 0,
|
|
404
|
+
page,
|
|
405
|
+
perPage,
|
|
406
|
+
hasMore: false,
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const dataResult = await this.client.execute({
|
|
411
|
+
sql: `SELECT * ${baseQuery} ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
|
|
412
|
+
args: [...queryParams, perPage, currentOffset],
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
const threads = (dataResult.rows || []).map(mapRowToStorageThreadType);
|
|
416
|
+
|
|
417
|
+
return {
|
|
418
|
+
threads,
|
|
419
|
+
total,
|
|
420
|
+
page,
|
|
421
|
+
perPage,
|
|
422
|
+
hasMore: currentOffset + threads.length < total,
|
|
423
|
+
};
|
|
424
|
+
} catch (error) {
|
|
425
|
+
this.logger.error(`Error getting threads for resource ${resourceId}:`, error);
|
|
426
|
+
return { threads: [], total: 0, page, perPage, hasMore: false };
|
|
427
|
+
}
|
|
295
428
|
}
|
|
296
429
|
|
|
297
430
|
async saveThread({ thread }: { thread: StorageThreadType }): Promise<StorageThreadType> {
|
|
@@ -338,11 +471,17 @@ export class LibSQLStore extends MastraStorage {
|
|
|
338
471
|
}
|
|
339
472
|
|
|
340
473
|
async deleteThread({ threadId }: { threadId: string }): Promise<void> {
|
|
474
|
+
// Delete messages for this thread (manual step)
|
|
475
|
+
await this.client.execute({
|
|
476
|
+
sql: `DELETE FROM ${TABLE_MESSAGES} WHERE thread_id = ?`,
|
|
477
|
+
args: [threadId],
|
|
478
|
+
});
|
|
479
|
+
|
|
341
480
|
await this.client.execute({
|
|
342
481
|
sql: `DELETE FROM ${TABLE_THREADS} WHERE id = ?`,
|
|
343
482
|
args: [threadId],
|
|
344
483
|
});
|
|
345
|
-
//
|
|
484
|
+
// TODO: Need to check if CASCADE is enabled so that messages will be automatically deleted due to CASCADE constraint
|
|
346
485
|
}
|
|
347
486
|
|
|
348
487
|
private parseRow(row: any): MastraMessageV2 {
|
|
@@ -358,92 +497,82 @@ export class LibSQLStore extends MastraStorage {
|
|
|
358
497
|
role: row.role,
|
|
359
498
|
createdAt: new Date(row.createdAt as string),
|
|
360
499
|
threadId: row.thread_id,
|
|
500
|
+
resourceId: row.resourceId,
|
|
361
501
|
} as MastraMessageV2;
|
|
362
502
|
if (row.type && row.type !== `v2`) result.type = row.type;
|
|
363
503
|
return result;
|
|
364
504
|
}
|
|
365
505
|
|
|
506
|
+
private async _getIncludedMessages(threadId: string, selectBy: StorageGetMessagesArg['selectBy']) {
|
|
507
|
+
const include = selectBy?.include;
|
|
508
|
+
if (!include) return null;
|
|
509
|
+
|
|
510
|
+
const includeIds = include.map(i => i.id);
|
|
511
|
+
const maxPrev = Math.max(...include.map(i => i.withPreviousMessages || 0));
|
|
512
|
+
const maxNext = Math.max(...include.map(i => i.withNextMessages || 0));
|
|
513
|
+
|
|
514
|
+
const includeResult = await this.client.execute({
|
|
515
|
+
sql: `
|
|
516
|
+
WITH numbered_messages AS (
|
|
517
|
+
SELECT
|
|
518
|
+
id, content, role, type, "createdAt", thread_id,
|
|
519
|
+
ROW_NUMBER() OVER (ORDER BY "createdAt" ASC) as row_num
|
|
520
|
+
FROM "${TABLE_MESSAGES}"
|
|
521
|
+
WHERE thread_id = ?
|
|
522
|
+
),
|
|
523
|
+
target_positions AS (
|
|
524
|
+
SELECT row_num as target_pos
|
|
525
|
+
FROM numbered_messages
|
|
526
|
+
WHERE id IN (${includeIds.map(() => '?').join(', ')})
|
|
527
|
+
)
|
|
528
|
+
SELECT DISTINCT m.*
|
|
529
|
+
FROM numbered_messages m
|
|
530
|
+
CROSS JOIN target_positions t
|
|
531
|
+
WHERE m.row_num BETWEEN (t.target_pos - ?) AND (t.target_pos + ?)
|
|
532
|
+
ORDER BY m."createdAt" ASC
|
|
533
|
+
`,
|
|
534
|
+
args: [threadId, ...includeIds, maxPrev, maxNext],
|
|
535
|
+
});
|
|
536
|
+
return includeResult.rows?.map((row: any) => this.parseRow(row));
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* @deprecated use getMessagesPaginated instead for paginated results.
|
|
541
|
+
*/
|
|
366
542
|
public async getMessages(args: StorageGetMessagesArg & { format?: 'v1' }): Promise<MastraMessageV1[]>;
|
|
367
543
|
public async getMessages(args: StorageGetMessagesArg & { format: 'v2' }): Promise<MastraMessageV2[]>;
|
|
368
544
|
public async getMessages({
|
|
369
545
|
threadId,
|
|
370
546
|
selectBy,
|
|
371
547
|
format,
|
|
372
|
-
}: StorageGetMessagesArg & {
|
|
548
|
+
}: StorageGetMessagesArg & {
|
|
549
|
+
format?: 'v1' | 'v2';
|
|
550
|
+
}): Promise<MastraMessageV1[] | MastraMessageV2[]> {
|
|
373
551
|
try {
|
|
374
552
|
const messages: MastraMessageV2[] = [];
|
|
375
553
|
const limit = typeof selectBy?.last === `number` ? selectBy.last : 40;
|
|
376
554
|
|
|
377
|
-
// If we have specific messages to select
|
|
378
555
|
if (selectBy?.include?.length) {
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
// Get messages around all specified IDs in one query using row numbers
|
|
384
|
-
const includeResult = await this.client.execute({
|
|
385
|
-
sql: `
|
|
386
|
-
WITH numbered_messages AS (
|
|
387
|
-
SELECT
|
|
388
|
-
id,
|
|
389
|
-
content,
|
|
390
|
-
role,
|
|
391
|
-
type,
|
|
392
|
-
"createdAt",
|
|
393
|
-
thread_id,
|
|
394
|
-
ROW_NUMBER() OVER (ORDER BY "createdAt" ASC) as row_num
|
|
395
|
-
FROM "${TABLE_MESSAGES}"
|
|
396
|
-
WHERE thread_id = ?
|
|
397
|
-
),
|
|
398
|
-
target_positions AS (
|
|
399
|
-
SELECT row_num as target_pos
|
|
400
|
-
FROM numbered_messages
|
|
401
|
-
WHERE id IN (${includeIds.map(() => '?').join(', ')})
|
|
402
|
-
)
|
|
403
|
-
SELECT DISTINCT m.*
|
|
404
|
-
FROM numbered_messages m
|
|
405
|
-
CROSS JOIN target_positions t
|
|
406
|
-
WHERE m.row_num BETWEEN (t.target_pos - ?) AND (t.target_pos + ?)
|
|
407
|
-
ORDER BY m."createdAt" ASC
|
|
408
|
-
`,
|
|
409
|
-
args: [threadId, ...includeIds, maxPrev, maxNext],
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
if (includeResult.rows) {
|
|
413
|
-
messages.push(...includeResult.rows.map((row: any) => this.parseRow(row)));
|
|
556
|
+
const includeMessages = await this._getIncludedMessages(threadId, selectBy);
|
|
557
|
+
if (includeMessages) {
|
|
558
|
+
messages.push(...includeMessages);
|
|
414
559
|
}
|
|
415
560
|
}
|
|
416
561
|
|
|
417
|
-
// Get remaining messages, excluding already fetched IDs
|
|
418
562
|
const excludeIds = messages.map(m => m.id);
|
|
419
563
|
const remainingSql = `
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
thread_id
|
|
427
|
-
FROM "${TABLE_MESSAGES}"
|
|
428
|
-
WHERE thread_id = ?
|
|
429
|
-
${excludeIds.length ? `AND id NOT IN (${excludeIds.map(() => '?').join(', ')})` : ''}
|
|
430
|
-
ORDER BY "createdAt" DESC
|
|
431
|
-
LIMIT ?
|
|
432
|
-
`;
|
|
564
|
+
SELECT id, content, role, type, "createdAt", thread_id
|
|
565
|
+
FROM "${TABLE_MESSAGES}"
|
|
566
|
+
WHERE thread_id = ?
|
|
567
|
+
${excludeIds.length ? `AND id NOT IN (${excludeIds.map(() => '?').join(', ')})` : ''}
|
|
568
|
+
ORDER BY "createdAt" DESC LIMIT ?
|
|
569
|
+
`;
|
|
433
570
|
const remainingArgs = [threadId, ...(excludeIds.length ? excludeIds : []), limit];
|
|
434
|
-
|
|
435
|
-
const remainingResult = await this.client.execute({
|
|
436
|
-
sql: remainingSql,
|
|
437
|
-
args: remainingArgs,
|
|
438
|
-
});
|
|
439
|
-
|
|
571
|
+
const remainingResult = await this.client.execute({ sql: remainingSql, args: remainingArgs });
|
|
440
572
|
if (remainingResult.rows) {
|
|
441
573
|
messages.push(...remainingResult.rows.map((row: any) => this.parseRow(row)));
|
|
442
574
|
}
|
|
443
|
-
|
|
444
|
-
// Sort all messages by creation date
|
|
445
575
|
messages.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
|
|
446
|
-
|
|
447
576
|
const list = new MessageList().add(messages, 'memory');
|
|
448
577
|
if (format === `v2`) return list.get.all.v2();
|
|
449
578
|
return list.get.all.v1();
|
|
@@ -453,6 +582,82 @@ export class LibSQLStore extends MastraStorage {
|
|
|
453
582
|
}
|
|
454
583
|
}
|
|
455
584
|
|
|
585
|
+
public async getMessagesPaginated(
|
|
586
|
+
args: StorageGetMessagesArg & {
|
|
587
|
+
format?: 'v1' | 'v2';
|
|
588
|
+
},
|
|
589
|
+
): Promise<PaginationInfo & { messages: MastraMessageV1[] | MastraMessageV2[] }> {
|
|
590
|
+
const { threadId, format, selectBy } = args;
|
|
591
|
+
const { page = 0, perPage = 40, dateRange } = selectBy?.pagination || {};
|
|
592
|
+
const fromDate = dateRange?.start;
|
|
593
|
+
const toDate = dateRange?.end;
|
|
594
|
+
|
|
595
|
+
const messages: MastraMessageV2[] = [];
|
|
596
|
+
|
|
597
|
+
if (selectBy?.include?.length) {
|
|
598
|
+
const includeMessages = await this._getIncludedMessages(threadId, selectBy);
|
|
599
|
+
if (includeMessages) {
|
|
600
|
+
messages.push(...includeMessages);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
try {
|
|
605
|
+
const currentOffset = page * perPage;
|
|
606
|
+
|
|
607
|
+
const conditions: string[] = [`thread_id = ?`];
|
|
608
|
+
const queryParams: InValue[] = [threadId];
|
|
609
|
+
|
|
610
|
+
if (fromDate) {
|
|
611
|
+
conditions.push(`"createdAt" >= ?`);
|
|
612
|
+
queryParams.push(fromDate.toISOString());
|
|
613
|
+
}
|
|
614
|
+
if (toDate) {
|
|
615
|
+
conditions.push(`"createdAt" <= ?`);
|
|
616
|
+
queryParams.push(toDate.toISOString());
|
|
617
|
+
}
|
|
618
|
+
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
619
|
+
|
|
620
|
+
const countResult = await this.client.execute({
|
|
621
|
+
sql: `SELECT COUNT(*) as count FROM ${TABLE_MESSAGES} ${whereClause}`,
|
|
622
|
+
args: queryParams,
|
|
623
|
+
});
|
|
624
|
+
const total = Number(countResult.rows?.[0]?.count ?? 0);
|
|
625
|
+
|
|
626
|
+
if (total === 0) {
|
|
627
|
+
return {
|
|
628
|
+
messages: [],
|
|
629
|
+
total: 0,
|
|
630
|
+
page,
|
|
631
|
+
perPage,
|
|
632
|
+
hasMore: false,
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
const dataResult = await this.client.execute({
|
|
637
|
+
sql: `SELECT id, content, role, type, "createdAt", thread_id FROM ${TABLE_MESSAGES} ${whereClause} ORDER BY "createdAt" DESC LIMIT ? OFFSET ?`,
|
|
638
|
+
args: [...queryParams, perPage, currentOffset],
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
messages.push(...(dataResult.rows || []).map((row: any) => this.parseRow(row)));
|
|
642
|
+
|
|
643
|
+
const messagesToReturn =
|
|
644
|
+
format === 'v1'
|
|
645
|
+
? new MessageList().add(messages, 'memory').get.all.v1()
|
|
646
|
+
: new MessageList().add(messages, 'memory').get.all.v2();
|
|
647
|
+
|
|
648
|
+
return {
|
|
649
|
+
messages: messagesToReturn,
|
|
650
|
+
total,
|
|
651
|
+
page,
|
|
652
|
+
perPage,
|
|
653
|
+
hasMore: currentOffset + messages.length < total,
|
|
654
|
+
};
|
|
655
|
+
} catch (error) {
|
|
656
|
+
this.logger.error('Error getting paginated messages:', error as Error);
|
|
657
|
+
return { messages: [], total: 0, page, perPage, hasMore: false };
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
456
661
|
async saveMessages(args: { messages: MastraMessageV1[]; format?: undefined | 'v1' }): Promise<MastraMessageV1[]>;
|
|
457
662
|
async saveMessages(args: { messages: MastraMessageV2[]; format: 'v2' }): Promise<MastraMessageV2[]>;
|
|
458
663
|
async saveMessages({
|
|
@@ -526,6 +731,7 @@ export class LibSQLStore extends MastraStorage {
|
|
|
526
731
|
};
|
|
527
732
|
}
|
|
528
733
|
|
|
734
|
+
/** @deprecated use getEvals instead */
|
|
529
735
|
async getEvalsByAgentName(agentName: string, type?: 'test' | 'live'): Promise<EvalRow[]> {
|
|
530
736
|
try {
|
|
531
737
|
const baseQuery = `SELECT * FROM ${TABLE_EVALS} WHERE agent_name = ?`;
|
|
@@ -552,120 +758,194 @@ export class LibSQLStore extends MastraStorage {
|
|
|
552
758
|
}
|
|
553
759
|
}
|
|
554
760
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
761
|
+
async getEvals(
|
|
762
|
+
options: {
|
|
763
|
+
agentName?: string;
|
|
764
|
+
type?: 'test' | 'live';
|
|
765
|
+
} & PaginationArgs = {},
|
|
766
|
+
): Promise<PaginationInfo & { evals: EvalRow[] }> {
|
|
767
|
+
const { agentName, type, page = 0, perPage = 100, dateRange } = options;
|
|
768
|
+
const fromDate = dateRange?.start;
|
|
769
|
+
const toDate = dateRange?.end;
|
|
770
|
+
|
|
771
|
+
const conditions: string[] = [];
|
|
772
|
+
const queryParams: InValue[] = [];
|
|
773
|
+
|
|
774
|
+
if (agentName) {
|
|
775
|
+
conditions.push(`agent_name = ?`);
|
|
776
|
+
queryParams.push(agentName);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
if (type === 'test') {
|
|
780
|
+
conditions.push(`(test_info IS NOT NULL AND json_extract(test_info, '$.testPath') IS NOT NULL)`);
|
|
781
|
+
} else if (type === 'live') {
|
|
782
|
+
conditions.push(`(test_info IS NULL OR json_extract(test_info, '$.testPath') IS NULL)`);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
if (fromDate) {
|
|
786
|
+
conditions.push(`created_at >= ?`);
|
|
787
|
+
queryParams.push(fromDate.toISOString());
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
if (toDate) {
|
|
791
|
+
conditions.push(`created_at <= ?`);
|
|
792
|
+
queryParams.push(toDate.toISOString());
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
796
|
+
|
|
797
|
+
const countResult = await this.client.execute({
|
|
798
|
+
sql: `SELECT COUNT(*) as count FROM ${TABLE_EVALS} ${whereClause}`,
|
|
799
|
+
args: queryParams,
|
|
800
|
+
});
|
|
801
|
+
const total = Number(countResult.rows?.[0]?.count ?? 0);
|
|
802
|
+
|
|
803
|
+
const currentOffset = page * perPage;
|
|
804
|
+
const hasMore = currentOffset + perPage < total;
|
|
805
|
+
|
|
806
|
+
if (total === 0) {
|
|
807
|
+
return {
|
|
808
|
+
evals: [],
|
|
809
|
+
total: 0,
|
|
810
|
+
page,
|
|
811
|
+
perPage,
|
|
812
|
+
hasMore: false,
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
const dataResult = await this.client.execute({
|
|
817
|
+
sql: `SELECT * FROM ${TABLE_EVALS} ${whereClause} ORDER BY created_at DESC LIMIT ? OFFSET ?`,
|
|
818
|
+
args: [...queryParams, perPage, currentOffset],
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
return {
|
|
822
|
+
evals: dataResult.rows?.map(row => this.transformEvalRow(row)) ?? [],
|
|
823
|
+
total,
|
|
560
824
|
page,
|
|
561
825
|
perPage,
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
826
|
+
hasMore,
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
/**
|
|
831
|
+
* @deprecated use getTracesPaginated instead.
|
|
832
|
+
*/
|
|
833
|
+
public async getTraces(args: {
|
|
834
|
+
name?: string;
|
|
835
|
+
scope?: string;
|
|
836
|
+
page: number;
|
|
837
|
+
perPage: number;
|
|
838
|
+
attributes?: Record<string, string>;
|
|
839
|
+
filters?: Record<string, any>;
|
|
840
|
+
fromDate?: Date;
|
|
841
|
+
toDate?: Date;
|
|
842
|
+
}): Promise<Trace[]> {
|
|
843
|
+
if (args.fromDate || args.toDate) {
|
|
844
|
+
(args as any).dateRange = {
|
|
845
|
+
start: args.fromDate,
|
|
846
|
+
end: args.toDate,
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
const result = await this.getTracesPaginated(args);
|
|
850
|
+
return result.traces;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
public async getTracesPaginated(
|
|
854
|
+
args: {
|
|
567
855
|
name?: string;
|
|
568
856
|
scope?: string;
|
|
569
|
-
page: number;
|
|
570
|
-
perPage: number;
|
|
571
857
|
attributes?: Record<string, string>;
|
|
572
858
|
filters?: Record<string, any>;
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
} =
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
const
|
|
581
|
-
const offset = page * perPage;
|
|
582
|
-
|
|
583
|
-
const args: (string | number)[] = [];
|
|
584
|
-
|
|
859
|
+
} & PaginationArgs,
|
|
860
|
+
): Promise<PaginationInfo & { traces: Trace[] }> {
|
|
861
|
+
const { name, scope, page = 0, perPage = 100, attributes, filters, dateRange } = args;
|
|
862
|
+
const fromDate = dateRange?.start;
|
|
863
|
+
const toDate = dateRange?.end;
|
|
864
|
+
const currentOffset = page * perPage;
|
|
865
|
+
|
|
866
|
+
const queryArgs: InValue[] = [];
|
|
585
867
|
const conditions: string[] = [];
|
|
868
|
+
|
|
586
869
|
if (name) {
|
|
587
|
-
conditions.push(
|
|
870
|
+
conditions.push('name LIKE ?');
|
|
871
|
+
queryArgs.push(`${name}%`);
|
|
588
872
|
}
|
|
589
873
|
if (scope) {
|
|
590
874
|
conditions.push('scope = ?');
|
|
875
|
+
queryArgs.push(scope);
|
|
591
876
|
}
|
|
592
877
|
if (attributes) {
|
|
593
|
-
Object.
|
|
594
|
-
conditions.push(`attributes
|
|
878
|
+
Object.entries(attributes).forEach(([key, value]) => {
|
|
879
|
+
conditions.push(`json_extract(attributes, '$.${key}') = ?`);
|
|
880
|
+
queryArgs.push(value);
|
|
595
881
|
});
|
|
596
882
|
}
|
|
597
|
-
|
|
598
883
|
if (filters) {
|
|
599
|
-
Object.entries(filters).forEach(([key,
|
|
600
|
-
conditions.push(`${key} = ?`);
|
|
884
|
+
Object.entries(filters).forEach(([key, value]) => {
|
|
885
|
+
conditions.push(`${parseSqlIdentifier(key, 'filter key')} = ?`);
|
|
886
|
+
queryArgs.push(value);
|
|
601
887
|
});
|
|
602
888
|
}
|
|
603
|
-
|
|
604
889
|
if (fromDate) {
|
|
605
890
|
conditions.push('createdAt >= ?');
|
|
891
|
+
queryArgs.push(fromDate.toISOString());
|
|
606
892
|
}
|
|
607
|
-
|
|
608
893
|
if (toDate) {
|
|
609
894
|
conditions.push('createdAt <= ?');
|
|
895
|
+
queryArgs.push(toDate.toISOString());
|
|
610
896
|
}
|
|
611
897
|
|
|
612
898
|
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
613
899
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
if (filters) {
|
|
629
|
-
for (const [, value] of Object.entries(filters)) {
|
|
630
|
-
args.push(value);
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
if (fromDate) {
|
|
635
|
-
args.push(fromDate.toISOString());
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
if (toDate) {
|
|
639
|
-
args.push(toDate.toISOString());
|
|
900
|
+
const countResult = await this.client.execute({
|
|
901
|
+
sql: `SELECT COUNT(*) as count FROM ${TABLE_TRACES} ${whereClause}`,
|
|
902
|
+
args: queryArgs,
|
|
903
|
+
});
|
|
904
|
+
const total = Number(countResult.rows?.[0]?.count ?? 0);
|
|
905
|
+
|
|
906
|
+
if (total === 0) {
|
|
907
|
+
return {
|
|
908
|
+
traces: [],
|
|
909
|
+
total: 0,
|
|
910
|
+
page,
|
|
911
|
+
perPage,
|
|
912
|
+
hasMore: false,
|
|
913
|
+
};
|
|
640
914
|
}
|
|
641
915
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
const result = await this.client.execute({
|
|
916
|
+
const dataResult = await this.client.execute({
|
|
645
917
|
sql: `SELECT * FROM ${TABLE_TRACES} ${whereClause} ORDER BY "startTime" DESC LIMIT ? OFFSET ?`,
|
|
646
|
-
args,
|
|
918
|
+
args: [...queryArgs, perPage, currentOffset],
|
|
647
919
|
});
|
|
648
920
|
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
921
|
+
const traces =
|
|
922
|
+
dataResult.rows?.map(
|
|
923
|
+
row =>
|
|
924
|
+
({
|
|
925
|
+
id: row.id,
|
|
926
|
+
parentSpanId: row.parentSpanId,
|
|
927
|
+
traceId: row.traceId,
|
|
928
|
+
name: row.name,
|
|
929
|
+
scope: row.scope,
|
|
930
|
+
kind: row.kind,
|
|
931
|
+
status: safelyParseJSON(row.status as string),
|
|
932
|
+
events: safelyParseJSON(row.events as string),
|
|
933
|
+
links: safelyParseJSON(row.links as string),
|
|
934
|
+
attributes: safelyParseJSON(row.attributes as string),
|
|
935
|
+
startTime: row.startTime,
|
|
936
|
+
endTime: row.endTime,
|
|
937
|
+
other: safelyParseJSON(row.other as string),
|
|
938
|
+
createdAt: row.createdAt,
|
|
939
|
+
}) as Trace,
|
|
940
|
+
) ?? [];
|
|
652
941
|
|
|
653
|
-
return
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
status: safelyParseJSON(row.status as string),
|
|
661
|
-
events: safelyParseJSON(row.events as string),
|
|
662
|
-
links: safelyParseJSON(row.links as string),
|
|
663
|
-
attributes: safelyParseJSON(row.attributes as string),
|
|
664
|
-
startTime: row.startTime,
|
|
665
|
-
endTime: row.endTime,
|
|
666
|
-
other: safelyParseJSON(row.other as string),
|
|
667
|
-
createdAt: row.createdAt,
|
|
668
|
-
})) as any;
|
|
942
|
+
return {
|
|
943
|
+
traces,
|
|
944
|
+
total,
|
|
945
|
+
page,
|
|
946
|
+
perPage,
|
|
947
|
+
hasMore: currentOffset + traces.length < total,
|
|
948
|
+
};
|
|
669
949
|
}
|
|
670
950
|
|
|
671
951
|
async getWorkflowRuns({
|