@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/dist/index.cjs CHANGED
@@ -851,6 +851,52 @@ var LibSQLStore = class extends storage.MastraStorage {
851
851
  throw error;
852
852
  }
853
853
  }
854
+ getSqlType(type) {
855
+ switch (type) {
856
+ case "bigint":
857
+ return "INTEGER";
858
+ // SQLite uses INTEGER for all integer sizes
859
+ case "jsonb":
860
+ return "TEXT";
861
+ // Store JSON as TEXT in SQLite
862
+ default:
863
+ return super.getSqlType(type);
864
+ }
865
+ }
866
+ /**
867
+ * Alters table schema to add columns if they don't exist
868
+ * @param tableName Name of the table
869
+ * @param schema Schema of the table
870
+ * @param ifNotExists Array of column names to add if they don't exist
871
+ */
872
+ async alterTable({
873
+ tableName,
874
+ schema,
875
+ ifNotExists
876
+ }) {
877
+ const parsedTableName = utils.parseSqlIdentifier(tableName, "table name");
878
+ try {
879
+ const pragmaQuery = `PRAGMA table_info(${parsedTableName})`;
880
+ const result = await this.client.execute(pragmaQuery);
881
+ const existingColumnNames = new Set(result.rows.map((row) => row.name.toLowerCase()));
882
+ for (const columnName of ifNotExists) {
883
+ if (!existingColumnNames.has(columnName.toLowerCase()) && schema[columnName]) {
884
+ const columnDef = schema[columnName];
885
+ const sqlType = this.getSqlType(columnDef.type);
886
+ const nullable = columnDef.nullable === false ? "NOT NULL" : "";
887
+ const defaultValue = columnDef.nullable === false ? this.getDefaultValue(columnDef.type) : "";
888
+ const alterSql = `ALTER TABLE ${parsedTableName} ADD COLUMN "${columnName}" ${sqlType} ${nullable} ${defaultValue}`.trim();
889
+ await this.client.execute(alterSql);
890
+ this.logger?.debug?.(`Added column ${columnName} to table ${parsedTableName}`);
891
+ }
892
+ }
893
+ } catch (error) {
894
+ this.logger?.error?.(
895
+ `Error altering table ${tableName}: ${error instanceof Error ? error.message : String(error)}`
896
+ );
897
+ throw new Error(`Failed to alter table ${tableName}: ${error}`);
898
+ }
899
+ }
854
900
  async clearTable({ tableName }) {
855
901
  const parsedTableName = utils.parseSqlIdentifier(tableName, "table name");
856
902
  try {
@@ -964,22 +1010,83 @@ var LibSQLStore = class extends storage.MastraStorage {
964
1010
  metadata: typeof result.metadata === "string" ? JSON.parse(result.metadata) : result.metadata
965
1011
  };
966
1012
  }
967
- async getThreadsByResourceId({ resourceId }) {
968
- const result = await this.client.execute({
969
- sql: `SELECT * FROM ${storage.TABLE_THREADS} WHERE resourceId = ?`,
970
- args: [resourceId]
971
- });
972
- if (!result.rows) {
1013
+ /**
1014
+ * @deprecated use getThreadsByResourceIdPaginated instead for paginated results.
1015
+ */
1016
+ async getThreadsByResourceId(args) {
1017
+ const { resourceId } = args;
1018
+ try {
1019
+ const baseQuery = `FROM ${storage.TABLE_THREADS} WHERE resourceId = ?`;
1020
+ const queryParams = [resourceId];
1021
+ const mapRowToStorageThreadType = (row) => ({
1022
+ id: row.id,
1023
+ resourceId: row.resourceId,
1024
+ title: row.title,
1025
+ createdAt: new Date(row.createdAt),
1026
+ // Convert string to Date
1027
+ updatedAt: new Date(row.updatedAt),
1028
+ // Convert string to Date
1029
+ metadata: typeof row.metadata === "string" ? JSON.parse(row.metadata) : row.metadata
1030
+ });
1031
+ const result = await this.client.execute({
1032
+ sql: `SELECT * ${baseQuery} ORDER BY createdAt DESC`,
1033
+ args: queryParams
1034
+ });
1035
+ if (!result.rows) {
1036
+ return [];
1037
+ }
1038
+ return result.rows.map(mapRowToStorageThreadType);
1039
+ } catch (error) {
1040
+ this.logger.error(`Error getting threads for resource ${resourceId}:`, error);
973
1041
  return [];
974
1042
  }
975
- return result.rows.map((thread) => ({
976
- id: thread.id,
977
- resourceId: thread.resourceId,
978
- title: thread.title,
979
- createdAt: thread.createdAt,
980
- updatedAt: thread.updatedAt,
981
- metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata
982
- }));
1043
+ }
1044
+ async getThreadsByResourceIdPaginated(args) {
1045
+ const { resourceId, page = 0, perPage = 100 } = args;
1046
+ try {
1047
+ const baseQuery = `FROM ${storage.TABLE_THREADS} WHERE resourceId = ?`;
1048
+ const queryParams = [resourceId];
1049
+ const mapRowToStorageThreadType = (row) => ({
1050
+ id: row.id,
1051
+ resourceId: row.resourceId,
1052
+ title: row.title,
1053
+ createdAt: new Date(row.createdAt),
1054
+ // Convert string to Date
1055
+ updatedAt: new Date(row.updatedAt),
1056
+ // Convert string to Date
1057
+ metadata: typeof row.metadata === "string" ? JSON.parse(row.metadata) : row.metadata
1058
+ });
1059
+ const currentOffset = page * perPage;
1060
+ const countResult = await this.client.execute({
1061
+ sql: `SELECT COUNT(*) as count ${baseQuery}`,
1062
+ args: queryParams
1063
+ });
1064
+ const total = Number(countResult.rows?.[0]?.count ?? 0);
1065
+ if (total === 0) {
1066
+ return {
1067
+ threads: [],
1068
+ total: 0,
1069
+ page,
1070
+ perPage,
1071
+ hasMore: false
1072
+ };
1073
+ }
1074
+ const dataResult = await this.client.execute({
1075
+ sql: `SELECT * ${baseQuery} ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
1076
+ args: [...queryParams, perPage, currentOffset]
1077
+ });
1078
+ const threads = (dataResult.rows || []).map(mapRowToStorageThreadType);
1079
+ return {
1080
+ threads,
1081
+ total,
1082
+ page,
1083
+ perPage,
1084
+ hasMore: currentOffset + threads.length < total
1085
+ };
1086
+ } catch (error) {
1087
+ this.logger.error(`Error getting threads for resource ${resourceId}:`, error);
1088
+ return { threads: [], total: 0, page, perPage, hasMore: false };
1089
+ }
983
1090
  }
984
1091
  async saveThread({ thread }) {
985
1092
  await this.insert({
@@ -1015,6 +1122,10 @@ var LibSQLStore = class extends storage.MastraStorage {
1015
1122
  return updatedThread;
1016
1123
  }
1017
1124
  async deleteThread({ threadId }) {
1125
+ await this.client.execute({
1126
+ sql: `DELETE FROM ${storage.TABLE_MESSAGES} WHERE thread_id = ?`,
1127
+ args: [threadId]
1128
+ });
1018
1129
  await this.client.execute({
1019
1130
  sql: `DELETE FROM ${storage.TABLE_THREADS} WHERE id = ?`,
1020
1131
  args: [threadId]
@@ -1031,11 +1142,42 @@ var LibSQLStore = class extends storage.MastraStorage {
1031
1142
  content,
1032
1143
  role: row.role,
1033
1144
  createdAt: new Date(row.createdAt),
1034
- threadId: row.thread_id
1145
+ threadId: row.thread_id,
1146
+ resourceId: row.resourceId
1035
1147
  };
1036
1148
  if (row.type && row.type !== `v2`) result.type = row.type;
1037
1149
  return result;
1038
1150
  }
1151
+ async _getIncludedMessages(threadId, selectBy) {
1152
+ const include = selectBy?.include;
1153
+ if (!include) return null;
1154
+ const includeIds = include.map((i) => i.id);
1155
+ const maxPrev = Math.max(...include.map((i) => i.withPreviousMessages || 0));
1156
+ const maxNext = Math.max(...include.map((i) => i.withNextMessages || 0));
1157
+ const includeResult = await this.client.execute({
1158
+ sql: `
1159
+ WITH numbered_messages AS (
1160
+ SELECT
1161
+ id, content, role, type, "createdAt", thread_id,
1162
+ ROW_NUMBER() OVER (ORDER BY "createdAt" ASC) as row_num
1163
+ FROM "${storage.TABLE_MESSAGES}"
1164
+ WHERE thread_id = ?
1165
+ ),
1166
+ target_positions AS (
1167
+ SELECT row_num as target_pos
1168
+ FROM numbered_messages
1169
+ WHERE id IN (${includeIds.map(() => "?").join(", ")})
1170
+ )
1171
+ SELECT DISTINCT m.*
1172
+ FROM numbered_messages m
1173
+ CROSS JOIN target_positions t
1174
+ WHERE m.row_num BETWEEN (t.target_pos - ?) AND (t.target_pos + ?)
1175
+ ORDER BY m."createdAt" ASC
1176
+ `,
1177
+ args: [threadId, ...includeIds, maxPrev, maxNext]
1178
+ });
1179
+ return includeResult.rows?.map((row) => this.parseRow(row));
1180
+ }
1039
1181
  async getMessages({
1040
1182
  threadId,
1041
1183
  selectBy,
@@ -1045,60 +1187,21 @@ var LibSQLStore = class extends storage.MastraStorage {
1045
1187
  const messages = [];
1046
1188
  const limit = typeof selectBy?.last === `number` ? selectBy.last : 40;
1047
1189
  if (selectBy?.include?.length) {
1048
- const includeIds = selectBy.include.map((i) => i.id);
1049
- const maxPrev = Math.max(...selectBy.include.map((i) => i.withPreviousMessages || 0));
1050
- const maxNext = Math.max(...selectBy.include.map((i) => i.withNextMessages || 0));
1051
- const includeResult = await this.client.execute({
1052
- sql: `
1053
- WITH numbered_messages AS (
1054
- SELECT
1055
- id,
1056
- content,
1057
- role,
1058
- type,
1059
- "createdAt",
1060
- thread_id,
1061
- ROW_NUMBER() OVER (ORDER BY "createdAt" ASC) as row_num
1062
- FROM "${storage.TABLE_MESSAGES}"
1063
- WHERE thread_id = ?
1064
- ),
1065
- target_positions AS (
1066
- SELECT row_num as target_pos
1067
- FROM numbered_messages
1068
- WHERE id IN (${includeIds.map(() => "?").join(", ")})
1069
- )
1070
- SELECT DISTINCT m.*
1071
- FROM numbered_messages m
1072
- CROSS JOIN target_positions t
1073
- WHERE m.row_num BETWEEN (t.target_pos - ?) AND (t.target_pos + ?)
1074
- ORDER BY m."createdAt" ASC
1075
- `,
1076
- args: [threadId, ...includeIds, maxPrev, maxNext]
1077
- });
1078
- if (includeResult.rows) {
1079
- messages.push(...includeResult.rows.map((row) => this.parseRow(row)));
1190
+ const includeMessages = await this._getIncludedMessages(threadId, selectBy);
1191
+ if (includeMessages) {
1192
+ messages.push(...includeMessages);
1080
1193
  }
1081
1194
  }
1082
1195
  const excludeIds = messages.map((m) => m.id);
1083
1196
  const remainingSql = `
1084
- SELECT
1085
- id,
1086
- content,
1087
- role,
1088
- type,
1089
- "createdAt",
1090
- thread_id
1091
- FROM "${storage.TABLE_MESSAGES}"
1092
- WHERE thread_id = ?
1093
- ${excludeIds.length ? `AND id NOT IN (${excludeIds.map(() => "?").join(", ")})` : ""}
1094
- ORDER BY "createdAt" DESC
1095
- LIMIT ?
1096
- `;
1197
+ SELECT id, content, role, type, "createdAt", thread_id
1198
+ FROM "${storage.TABLE_MESSAGES}"
1199
+ WHERE thread_id = ?
1200
+ ${excludeIds.length ? `AND id NOT IN (${excludeIds.map(() => "?").join(", ")})` : ""}
1201
+ ORDER BY "createdAt" DESC LIMIT ?
1202
+ `;
1097
1203
  const remainingArgs = [threadId, ...excludeIds.length ? excludeIds : [], limit];
1098
- const remainingResult = await this.client.execute({
1099
- sql: remainingSql,
1100
- args: remainingArgs
1101
- });
1204
+ const remainingResult = await this.client.execute({ sql: remainingSql, args: remainingArgs });
1102
1205
  if (remainingResult.rows) {
1103
1206
  messages.push(...remainingResult.rows.map((row) => this.parseRow(row)));
1104
1207
  }
@@ -1111,6 +1214,63 @@ var LibSQLStore = class extends storage.MastraStorage {
1111
1214
  throw error;
1112
1215
  }
1113
1216
  }
1217
+ async getMessagesPaginated(args) {
1218
+ const { threadId, format, selectBy } = args;
1219
+ const { page = 0, perPage = 40, dateRange } = selectBy?.pagination || {};
1220
+ const fromDate = dateRange?.start;
1221
+ const toDate = dateRange?.end;
1222
+ const messages = [];
1223
+ if (selectBy?.include?.length) {
1224
+ const includeMessages = await this._getIncludedMessages(threadId, selectBy);
1225
+ if (includeMessages) {
1226
+ messages.push(...includeMessages);
1227
+ }
1228
+ }
1229
+ try {
1230
+ const currentOffset = page * perPage;
1231
+ const conditions = [`thread_id = ?`];
1232
+ const queryParams = [threadId];
1233
+ if (fromDate) {
1234
+ conditions.push(`"createdAt" >= ?`);
1235
+ queryParams.push(fromDate.toISOString());
1236
+ }
1237
+ if (toDate) {
1238
+ conditions.push(`"createdAt" <= ?`);
1239
+ queryParams.push(toDate.toISOString());
1240
+ }
1241
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
1242
+ const countResult = await this.client.execute({
1243
+ sql: `SELECT COUNT(*) as count FROM ${storage.TABLE_MESSAGES} ${whereClause}`,
1244
+ args: queryParams
1245
+ });
1246
+ const total = Number(countResult.rows?.[0]?.count ?? 0);
1247
+ if (total === 0) {
1248
+ return {
1249
+ messages: [],
1250
+ total: 0,
1251
+ page,
1252
+ perPage,
1253
+ hasMore: false
1254
+ };
1255
+ }
1256
+ const dataResult = await this.client.execute({
1257
+ sql: `SELECT id, content, role, type, "createdAt", thread_id FROM ${storage.TABLE_MESSAGES} ${whereClause} ORDER BY "createdAt" DESC LIMIT ? OFFSET ?`,
1258
+ args: [...queryParams, perPage, currentOffset]
1259
+ });
1260
+ messages.push(...(dataResult.rows || []).map((row) => this.parseRow(row)));
1261
+ const messagesToReturn = format === "v1" ? new agent.MessageList().add(messages, "memory").get.all.v1() : new agent.MessageList().add(messages, "memory").get.all.v2();
1262
+ return {
1263
+ messages: messagesToReturn,
1264
+ total,
1265
+ page,
1266
+ perPage,
1267
+ hasMore: currentOffset + messages.length < total
1268
+ };
1269
+ } catch (error) {
1270
+ this.logger.error("Error getting paginated messages:", error);
1271
+ return { messages: [], total: 0, page, perPage, hasMore: false };
1272
+ }
1273
+ }
1114
1274
  async saveMessages({
1115
1275
  messages,
1116
1276
  format
@@ -1169,6 +1329,7 @@ var LibSQLStore = class extends storage.MastraStorage {
1169
1329
  createdAt: row.created_at
1170
1330
  };
1171
1331
  }
1332
+ /** @deprecated use getEvals instead */
1172
1333
  async getEvalsByAgentName(agentName, type) {
1173
1334
  try {
1174
1335
  const baseQuery = `SELECT * FROM ${storage.TABLE_EVALS} WHERE agent_name = ?`;
@@ -1186,93 +1347,150 @@ var LibSQLStore = class extends storage.MastraStorage {
1186
1347
  throw error;
1187
1348
  }
1188
1349
  }
1189
- // TODO: add types
1190
- async getTraces({
1191
- name,
1192
- scope,
1193
- page,
1194
- perPage,
1195
- attributes,
1196
- filters,
1197
- fromDate,
1198
- toDate
1199
- } = {
1200
- page: 0,
1201
- perPage: 100
1202
- }) {
1203
- const limit = perPage;
1204
- const offset = page * perPage;
1205
- const args = [];
1350
+ async getEvals(options = {}) {
1351
+ const { agentName, type, page = 0, perPage = 100, dateRange } = options;
1352
+ const fromDate = dateRange?.start;
1353
+ const toDate = dateRange?.end;
1206
1354
  const conditions = [];
1207
- if (name) {
1208
- conditions.push("name LIKE CONCAT(?, '%')");
1355
+ const queryParams = [];
1356
+ if (agentName) {
1357
+ conditions.push(`agent_name = ?`);
1358
+ queryParams.push(agentName);
1209
1359
  }
1210
- if (scope) {
1211
- conditions.push("scope = ?");
1212
- }
1213
- if (attributes) {
1214
- Object.keys(attributes).forEach((key) => {
1215
- conditions.push(`attributes->>'$.${key}' = ?`);
1216
- });
1217
- }
1218
- if (filters) {
1219
- Object.entries(filters).forEach(([key, _value]) => {
1220
- conditions.push(`${key} = ?`);
1221
- });
1360
+ if (type === "test") {
1361
+ conditions.push(`(test_info IS NOT NULL AND json_extract(test_info, '$.testPath') IS NOT NULL)`);
1362
+ } else if (type === "live") {
1363
+ conditions.push(`(test_info IS NULL OR json_extract(test_info, '$.testPath') IS NULL)`);
1222
1364
  }
1223
1365
  if (fromDate) {
1224
- conditions.push("createdAt >= ?");
1366
+ conditions.push(`created_at >= ?`);
1367
+ queryParams.push(fromDate.toISOString());
1225
1368
  }
1226
1369
  if (toDate) {
1227
- conditions.push("createdAt <= ?");
1370
+ conditions.push(`created_at <= ?`);
1371
+ queryParams.push(toDate.toISOString());
1228
1372
  }
1229
1373
  const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
1374
+ const countResult = await this.client.execute({
1375
+ sql: `SELECT COUNT(*) as count FROM ${storage.TABLE_EVALS} ${whereClause}`,
1376
+ args: queryParams
1377
+ });
1378
+ const total = Number(countResult.rows?.[0]?.count ?? 0);
1379
+ const currentOffset = page * perPage;
1380
+ const hasMore = currentOffset + perPage < total;
1381
+ if (total === 0) {
1382
+ return {
1383
+ evals: [],
1384
+ total: 0,
1385
+ page,
1386
+ perPage,
1387
+ hasMore: false
1388
+ };
1389
+ }
1390
+ const dataResult = await this.client.execute({
1391
+ sql: `SELECT * FROM ${storage.TABLE_EVALS} ${whereClause} ORDER BY created_at DESC LIMIT ? OFFSET ?`,
1392
+ args: [...queryParams, perPage, currentOffset]
1393
+ });
1394
+ return {
1395
+ evals: dataResult.rows?.map((row) => this.transformEvalRow(row)) ?? [],
1396
+ total,
1397
+ page,
1398
+ perPage,
1399
+ hasMore
1400
+ };
1401
+ }
1402
+ /**
1403
+ * @deprecated use getTracesPaginated instead.
1404
+ */
1405
+ async getTraces(args) {
1406
+ if (args.fromDate || args.toDate) {
1407
+ args.dateRange = {
1408
+ start: args.fromDate,
1409
+ end: args.toDate
1410
+ };
1411
+ }
1412
+ const result = await this.getTracesPaginated(args);
1413
+ return result.traces;
1414
+ }
1415
+ async getTracesPaginated(args) {
1416
+ const { name, scope, page = 0, perPage = 100, attributes, filters, dateRange } = args;
1417
+ const fromDate = dateRange?.start;
1418
+ const toDate = dateRange?.end;
1419
+ const currentOffset = page * perPage;
1420
+ const queryArgs = [];
1421
+ const conditions = [];
1230
1422
  if (name) {
1231
- args.push(name);
1423
+ conditions.push("name LIKE ?");
1424
+ queryArgs.push(`${name}%`);
1232
1425
  }
1233
1426
  if (scope) {
1234
- args.push(scope);
1427
+ conditions.push("scope = ?");
1428
+ queryArgs.push(scope);
1235
1429
  }
1236
1430
  if (attributes) {
1237
- for (const [, value] of Object.entries(attributes)) {
1238
- args.push(value);
1239
- }
1431
+ Object.entries(attributes).forEach(([key, value]) => {
1432
+ conditions.push(`json_extract(attributes, '$.${key}') = ?`);
1433
+ queryArgs.push(value);
1434
+ });
1240
1435
  }
1241
1436
  if (filters) {
1242
- for (const [, value] of Object.entries(filters)) {
1243
- args.push(value);
1244
- }
1437
+ Object.entries(filters).forEach(([key, value]) => {
1438
+ conditions.push(`${utils.parseSqlIdentifier(key, "filter key")} = ?`);
1439
+ queryArgs.push(value);
1440
+ });
1245
1441
  }
1246
1442
  if (fromDate) {
1247
- args.push(fromDate.toISOString());
1443
+ conditions.push("createdAt >= ?");
1444
+ queryArgs.push(fromDate.toISOString());
1248
1445
  }
1249
1446
  if (toDate) {
1250
- args.push(toDate.toISOString());
1447
+ conditions.push("createdAt <= ?");
1448
+ queryArgs.push(toDate.toISOString());
1251
1449
  }
1252
- args.push(limit, offset);
1253
- const result = await this.client.execute({
1254
- sql: `SELECT * FROM ${storage.TABLE_TRACES} ${whereClause} ORDER BY "startTime" DESC LIMIT ? OFFSET ?`,
1255
- args
1450
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
1451
+ const countResult = await this.client.execute({
1452
+ sql: `SELECT COUNT(*) as count FROM ${storage.TABLE_TRACES} ${whereClause}`,
1453
+ args: queryArgs
1256
1454
  });
1257
- if (!result.rows) {
1258
- return [];
1455
+ const total = Number(countResult.rows?.[0]?.count ?? 0);
1456
+ if (total === 0) {
1457
+ return {
1458
+ traces: [],
1459
+ total: 0,
1460
+ page,
1461
+ perPage,
1462
+ hasMore: false
1463
+ };
1259
1464
  }
1260
- return result.rows.map((row) => ({
1261
- id: row.id,
1262
- parentSpanId: row.parentSpanId,
1263
- traceId: row.traceId,
1264
- name: row.name,
1265
- scope: row.scope,
1266
- kind: row.kind,
1267
- status: safelyParseJSON(row.status),
1268
- events: safelyParseJSON(row.events),
1269
- links: safelyParseJSON(row.links),
1270
- attributes: safelyParseJSON(row.attributes),
1271
- startTime: row.startTime,
1272
- endTime: row.endTime,
1273
- other: safelyParseJSON(row.other),
1274
- createdAt: row.createdAt
1275
- }));
1465
+ const dataResult = await this.client.execute({
1466
+ sql: `SELECT * FROM ${storage.TABLE_TRACES} ${whereClause} ORDER BY "startTime" DESC LIMIT ? OFFSET ?`,
1467
+ args: [...queryArgs, perPage, currentOffset]
1468
+ });
1469
+ const traces = dataResult.rows?.map(
1470
+ (row) => ({
1471
+ id: row.id,
1472
+ parentSpanId: row.parentSpanId,
1473
+ traceId: row.traceId,
1474
+ name: row.name,
1475
+ scope: row.scope,
1476
+ kind: row.kind,
1477
+ status: safelyParseJSON(row.status),
1478
+ events: safelyParseJSON(row.events),
1479
+ links: safelyParseJSON(row.links),
1480
+ attributes: safelyParseJSON(row.attributes),
1481
+ startTime: row.startTime,
1482
+ endTime: row.endTime,
1483
+ other: safelyParseJSON(row.other),
1484
+ createdAt: row.createdAt
1485
+ })
1486
+ ) ?? [];
1487
+ return {
1488
+ traces,
1489
+ total,
1490
+ page,
1491
+ perPage,
1492
+ hasMore: currentOffset + traces.length < total
1493
+ };
1276
1494
  }
1277
1495
  async getWorkflowRuns({
1278
1496
  workflowName,