@mastra/pg 1.34.0-alpha.0 → 1.34.0

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 (44) hide show
  1. package/README.md +1 -1
  2. package/dist/docs/SKILL.md +48 -0
  3. package/dist/docs/assets/SOURCE_MAP.json +6 -0
  4. package/dist/docs/references/docs-deployment-workers.md +404 -0
  5. package/dist/docs/references/docs-memory-semantic-recall.md +485 -0
  6. package/dist/docs/references/docs-memory-working-memory.md +450 -0
  7. package/dist/docs/references/docs-storage.md +224 -0
  8. package/dist/docs/references/integrations-databases-clickhouse-managed-postgres.md +82 -0
  9. package/dist/docs/references/integrations-databases-dynamodb.md +286 -0
  10. package/dist/docs/references/integrations-databases-neon.md +222 -0
  11. package/dist/docs/references/integrations-databases-postgresql.md +592 -0
  12. package/dist/docs/references/integrations-vectors-pg.md +451 -0
  13. package/dist/docs/references/reference-memory-memory-class.md +237 -0
  14. package/dist/docs/references/reference-processors-message-history-processor.md +89 -0
  15. package/dist/docs/references/reference-processors-semantic-recall-processor.md +119 -0
  16. package/dist/docs/references/reference-processors-working-memory-processor.md +154 -0
  17. package/dist/docs/references/reference-rag-chunking-and-embedding.md +184 -0
  18. package/dist/docs/references/reference-rag-metadata-filters.md +238 -0
  19. package/dist/docs/references/reference-rag-overview.md +76 -0
  20. package/dist/docs/references/reference-rag-retrieval.md +672 -0
  21. package/dist/docs/references/reference-rag-vector-databases.md +808 -0
  22. package/dist/docs/references/reference-storage-composite.md +433 -0
  23. package/dist/docs/references/reference-storage-retention.md +310 -0
  24. package/dist/docs/references/reference-tools-vector-query-tool.md +500 -0
  25. package/dist/index.cjs +176 -71
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.js +176 -71
  28. package/dist/index.js.map +1 -1
  29. package/dist/storage/domains/background-tasks/index.d.ts +1 -0
  30. package/dist/storage/domains/background-tasks/index.d.ts.map +1 -1
  31. package/dist/storage/domains/experiments/index.d.ts +3 -3
  32. package/dist/storage/domains/experiments/index.d.ts.map +1 -1
  33. package/dist/storage/domains/observability/v-next/index.d.ts +1 -1
  34. package/dist/storage/domains/observability/v-next/index.d.ts.map +1 -1
  35. package/dist/storage/domains/observability/v-next/preview-compat.d.ts +3 -0
  36. package/dist/storage/domains/observability/v-next/preview-compat.d.ts.map +1 -1
  37. package/dist/storage/domains/observability/v-next/span-query.d.ts +3 -0
  38. package/dist/storage/domains/observability/v-next/span-query.d.ts.map +1 -1
  39. package/dist/storage/domains/observability/v-next/trace-aggregate.d.ts.map +1 -1
  40. package/dist/storage/domains/observability/v-next/trace-query.d.ts +7 -0
  41. package/dist/storage/domains/observability/v-next/trace-query.d.ts.map +1 -1
  42. package/dist/storage/test-utils.d.ts.map +1 -1
  43. package/dist/vector/index.d.ts.map +1 -1
  44. package/package.json +6 -6
package/dist/index.js CHANGED
@@ -1080,42 +1080,43 @@ var PgVector = class extends MastraVector {
1080
1080
  const scoreExpr = ops.scoreExpr(distanceExpr);
1081
1081
  const filterClause = filterQuery.trim().replace(/^WHERE\s+/i, "");
1082
1082
  const hasFilter = filterClause.length > 0;
1083
- const useIndexedOrder = indexInfo.type === "hnsw" && !hasFilter && minScore <= 0;
1084
1083
  const namespaceParam = filterValues.length + 1;
1085
- const query = useIndexedOrder ? `
1084
+ const queryValues = [...filterValues, namespace];
1085
+ const selectColumns = `vector_id as id, ${scoreExpr} as score, metadata${includeVector ? ", embedding" : ""}`;
1086
+ const whereClause = `WHERE namespace = $${namespaceParam}${filterClause ? ` AND (${filterClause})` : ""}`;
1087
+ const useIndexedOrder = indexInfo.type === "hnsw" && (!hasFilter || this.supportsIterativeScan());
1088
+ let rows;
1089
+ if (useIndexedOrder) {
1090
+ const indexedQuery = `
1086
1091
  WITH vector_scores AS (
1087
- SELECT
1088
- vector_id as id,
1089
- ${scoreExpr} as score,
1090
- metadata
1091
- ${includeVector ? ", embedding" : ""}
1092
+ SELECT ${selectColumns}
1092
1093
  FROM ${tableName}
1093
- WHERE namespace = $${namespaceParam}
1094
+ ${whereClause}
1094
1095
  ORDER BY ${distanceExpr}
1095
1096
  LIMIT $2
1096
1097
  )
1097
- SELECT *
1098
+ SELECT *, score > $1 AS above_min_score
1098
1099
  FROM vector_scores
1099
- WHERE score > $1
1100
- ORDER BY score DESC` : `
1100
+ ORDER BY score DESC`;
1101
+ const candidates = (await client.query(indexedQuery, queryValues)).rows;
1102
+ if (candidates.length >= topK) rows = candidates.filter((row) => row.above_min_score);
1103
+ }
1104
+ if (!rows) {
1105
+ const exactQuery = `
1101
1106
  WITH vector_scores AS (
1102
- SELECT
1103
- vector_id as id,
1104
- ${scoreExpr} as score,
1105
- metadata
1106
- ${includeVector ? ", embedding" : ""}
1107
+ SELECT ${selectColumns}
1107
1108
  FROM ${tableName}
1108
- WHERE namespace = $${namespaceParam}
1109
- ${filterClause ? `AND (${filterClause})` : ""}
1109
+ ${whereClause}
1110
1110
  )
1111
1111
  SELECT *
1112
1112
  FROM vector_scores
1113
1113
  WHERE score > $1
1114
1114
  ORDER BY score DESC
1115
1115
  LIMIT $2`;
1116
- const result = await client.query(query, [...filterValues, namespace]);
1116
+ rows = (await client.query(exactQuery, queryValues)).rows;
1117
+ }
1117
1118
  await client.query("COMMIT");
1118
- return result.rows.map(({ id, score, metadata, embedding }) => ({
1119
+ return rows.map(({ id, score, metadata, embedding }) => ({
1119
1120
  id,
1120
1121
  score,
1121
1122
  metadata,
@@ -1382,7 +1383,7 @@ var PgVector = class extends MastraVector {
1382
1383
  indexConfig,
1383
1384
  vectorType
1384
1385
  }, client);
1385
- if (metadataIndexes?.length) await this.createMetadataIndexes(tableName, indexName, metadataIndexes);
1386
+ if (metadataIndexes?.length) await this.createMetadataIndexes(client, tableName, indexName, metadataIndexes);
1386
1387
  } catch (error) {
1387
1388
  this.createdIndexes.delete(indexName);
1388
1389
  this.namespaceReadyIndexes.delete(indexName);
@@ -1532,13 +1533,13 @@ var PgVector = class extends MastraVector {
1532
1533
  await client.query(indexSQL);
1533
1534
  });
1534
1535
  }
1535
- async createMetadataIndexes(tableName, indexName, metadataFields) {
1536
+ async createMetadataIndexes(client, tableName, indexName, metadataFields) {
1536
1537
  const hasher = await this.hasher;
1537
1538
  for (const field of metadataFields) {
1538
1539
  const fieldHash = hasher.h32(field).toString(16);
1539
1540
  const metadataIdxName = `"${indexName.slice(0, 55 - fieldHash.length)}_md_${fieldHash}_idx"`;
1540
1541
  const escapedField = field.replace(/'/g, "''");
1541
- await this.pool.query(`
1542
+ await client.query(`
1542
1543
  CREATE INDEX CONCURRENTLY IF NOT EXISTS ${metadataIdxName}
1543
1544
  ON ${tableName} ((metadata->>'${escapedField}'))
1544
1545
  `);
@@ -5262,6 +5263,7 @@ function rowToTask(row) {
5262
5263
  const suspendedAt = row.suspendedAtZ || row.suspendedAt;
5263
5264
  const completedAt = row.completedAtZ || row.completedAt;
5264
5265
  const leaseExpiresAt = row.leaseExpiresAtZ || row.leaseExpiresAt;
5266
+ const expiresAt = row.expiresAt != null ? row.expiresAtZ || row.expiresAt : void 0;
5265
5267
  return {
5266
5268
  id: row.id,
5267
5269
  status: row.status,
@@ -5283,10 +5285,13 @@ function rowToTask(row) {
5283
5285
  suspendedAt: suspendedAt ? suspendedAt instanceof Date ? suspendedAt : new Date(suspendedAt) : void 0,
5284
5286
  completedAt: completedAt ? completedAt instanceof Date ? completedAt : new Date(completedAt) : void 0,
5285
5287
  ownerId: row.ownerId ?? void 0,
5286
- leaseExpiresAt: leaseExpiresAt ? leaseExpiresAt instanceof Date ? leaseExpiresAt : new Date(leaseExpiresAt) : void 0
5288
+ leaseExpiresAt: leaseExpiresAt ? leaseExpiresAt instanceof Date ? leaseExpiresAt : new Date(leaseExpiresAt) : void 0,
5289
+ expiresAt: expiresAt ? expiresAt instanceof Date ? expiresAt : new Date(expiresAt) : void 0,
5290
+ metadata: parseJson$2(row.metadata)
5287
5291
  };
5288
5292
  }
5289
5293
  var BackgroundTasksPG = class BackgroundTasksPG extends BackgroundTasksStorage {
5294
+ supportsTaskExpiry = true;
5290
5295
  #db;
5291
5296
  #schema;
5292
5297
  #skipDefaultIndexes;
@@ -5327,7 +5332,9 @@ var BackgroundTasksPG = class BackgroundTasksPG extends BackgroundTasksStorage {
5327
5332
  "suspend_payload",
5328
5333
  "suspendedAt",
5329
5334
  "ownerId",
5330
- "leaseExpiresAt"
5335
+ "leaseExpiresAt",
5336
+ "expiresAt",
5337
+ "metadata"
5331
5338
  ]
5332
5339
  });
5333
5340
  await this.createDefaultIndexes();
@@ -5461,7 +5468,10 @@ var BackgroundTasksPG = class BackgroundTasksPG extends BackgroundTasksStorage {
5461
5468
  completedAtZ: task.completedAt?.toISOString() ?? null,
5462
5469
  ownerId: task.ownerId ?? null,
5463
5470
  leaseExpiresAt: task.leaseExpiresAt?.toISOString() ?? null,
5464
- leaseExpiresAtZ: task.leaseExpiresAt?.toISOString() ?? null
5471
+ leaseExpiresAtZ: task.leaseExpiresAt?.toISOString() ?? null,
5472
+ expiresAt: task.expiresAt?.toISOString() ?? null,
5473
+ expiresAtZ: task.expiresAt?.toISOString() ?? null,
5474
+ metadata: serializeJson(task.metadata)
5465
5475
  }
5466
5476
  });
5467
5477
  }
@@ -5517,6 +5527,16 @@ var BackgroundTasksPG = class BackgroundTasksPG extends BackgroundTasksStorage {
5517
5527
  const val = update.leaseExpiresAt?.toISOString() ?? null;
5518
5528
  params.push(val, val);
5519
5529
  }
5530
+ if ("expiresAt" in update) {
5531
+ setClauses.push(`"expiresAt" = $${paramIdx++}`);
5532
+ setClauses.push(`"expiresAtZ" = $${paramIdx++}`);
5533
+ const val = update.expiresAt?.toISOString() ?? null;
5534
+ params.push(val, val);
5535
+ }
5536
+ if ("metadata" in update) {
5537
+ setClauses.push(`"metadata" = $${paramIdx++}`);
5538
+ params.push(serializeJson(update.metadata));
5539
+ }
5520
5540
  if (setClauses.length === 0) return false;
5521
5541
  const table = getTableName$4(getSchemaName$4(this.#schema));
5522
5542
  params.push(taskId);
@@ -5539,6 +5559,13 @@ var BackgroundTasksPG = class BackgroundTasksPG extends BackgroundTasksStorage {
5539
5559
  params.push(options.expectedLeaseExpiresAt.toISOString());
5540
5560
  }
5541
5561
  }
5562
+ if (options?.expectedSuspendedAt !== void 0) {
5563
+ if (options.expectedSuspendedAt === null) where += ` AND "suspendedAtZ" IS NULL`;
5564
+ else {
5565
+ where += ` AND "suspendedAtZ" = $${paramIdx++}`;
5566
+ params.push(options.expectedSuspendedAt.toISOString());
5567
+ }
5568
+ }
5542
5569
  return ((await this.#db.client.query(`UPDATE ${table} SET ${setClauses.join(", ")} WHERE ${where}`, params)).rowCount ?? 0) > 0;
5543
5570
  }
5544
5571
  async getTask(taskId) {
@@ -5581,7 +5608,9 @@ var BackgroundTasksPG = class BackgroundTasksPG extends BackgroundTasksStorage {
5581
5608
  conditions.push(`"tool_call_id" = $${paramIdx++}`);
5582
5609
  params.push(filter.toolCallId);
5583
5610
  }
5584
- const dateCol = filter.dateFilterBy === "startedAt" ? "\"startedAt\"" : filter.dateFilterBy === "suspendedAt" ? "\"suspendedAt\"" : filter.dateFilterBy === "completedAt" ? "\"completedAt\"" : "\"createdAt\"";
5611
+ const dateCol = filter.dateFilterBy === "startedAt" ? "\"startedAt\"" : filter.dateFilterBy === "suspendedAt" ? "\"suspendedAt\"" : filter.dateFilterBy === "completedAt" ? "\"completedAt\"" : filter.dateFilterBy === "expiresAt" ? "\"expiresAtZ\"" : "\"createdAt\"";
5612
+ if (filter.hasExpiresAt !== void 0) conditions.push(filter.hasExpiresAt ? "\"expiresAt\" IS NOT NULL" : "\"expiresAt\" IS NULL");
5613
+ if (filter.dateFilterBy === "expiresAt" && (filter.fromDate || filter.toDate)) conditions.push("\"expiresAt\" IS NOT NULL");
5585
5614
  if (filter.fromDate) {
5586
5615
  conditions.push(`${dateCol} >= $${paramIdx++}`);
5587
5616
  params.push(filter.fromDate.toISOString());
@@ -5622,7 +5651,9 @@ var BackgroundTasksPG = class BackgroundTasksPG extends BackgroundTasksStorage {
5622
5651
  conditions.push(`"status" IN (${placeholders.join(", ")})`);
5623
5652
  params.push(...statuses);
5624
5653
  }
5625
- const dateCol = filter.dateFilterBy === "startedAt" ? "\"startedAt\"" : filter.dateFilterBy === "suspendedAt" ? "\"suspendedAt\"" : filter.dateFilterBy === "completedAt" ? "\"completedAt\"" : "\"createdAt\"";
5654
+ const dateCol = filter.dateFilterBy === "startedAt" ? "\"startedAt\"" : filter.dateFilterBy === "suspendedAt" ? "\"suspendedAt\"" : filter.dateFilterBy === "completedAt" ? "\"completedAt\"" : filter.dateFilterBy === "expiresAt" ? "\"expiresAtZ\"" : "\"createdAt\"";
5655
+ if (filter.hasExpiresAt !== void 0) conditions.push(filter.hasExpiresAt ? "\"expiresAt\" IS NOT NULL" : "\"expiresAt\" IS NULL");
5656
+ if (filter.dateFilterBy === "expiresAt" && (filter.fromDate || filter.toDate)) conditions.push("\"expiresAt\" IS NOT NULL");
5626
5657
  if (filter.fromDate) {
5627
5658
  conditions.push(`${dateCol} >= $${paramIdx++}`);
5628
5659
  params.push(filter.fromDate.toISOString());
@@ -7238,6 +7269,19 @@ var DatasetsPG = class DatasetsPG extends DatasetsStorage {
7238
7269
  };
7239
7270
  // src/storage/domains/experiments/index.ts
7240
7271
  const DEFAULT_PRUNE_BATCH_SIZE = 1e3;
7272
+ /**
7273
+ * Column list for reading experiment rows. Each `timestamp` column comes back
7274
+ * twice: as itself, and as its `*Z` mirror derived in the query with
7275
+ * `AT TIME ZONE 'UTC'`. A plain `timestamp` otherwise round-trips through the
7276
+ * session timezone, and reading the stored `*Z` column instead would return the
7277
+ * insert time for rows written before the mirrors were filled in. Deriving it
7278
+ * here makes legacy rows read correctly with no backfill to run at startup.
7279
+ */
7280
+ function selectWithMirrors(schema) {
7281
+ return Object.entries(schema).flatMap(([name, definition]) => definition.type === "timestamp" ? [`"${name}"`, `"${name}" AT TIME ZONE 'UTC' AS "${name}Z"`] : [`"${name}"`]).join(", ");
7282
+ }
7283
+ const EXPERIMENT_COLUMNS = selectWithMirrors(EXPERIMENTS_SCHEMA);
7284
+ const EXPERIMENT_RESULT_COLUMNS = selectWithMirrors(EXPERIMENT_RESULTS_SCHEMA);
7241
7285
  var ExperimentsPG = class ExperimentsPG extends ExperimentsStorage {
7242
7286
  #db;
7243
7287
  #schema;
@@ -7246,9 +7290,9 @@ var ExperimentsPG = class ExperimentsPG extends ExperimentsStorage {
7246
7290
  static MANAGED_TABLES = [TABLE_EXPERIMENTS, TABLE_EXPERIMENT_RESULTS];
7247
7291
  /**
7248
7292
  * Experiments prune as whole units: an aged experiment and its result rows go
7249
- * together, mirroring `deleteExperiment`. Anchored on `completedAt` (not the
7250
- * `completedAtZ` mirror, which carries a `DEFAULT NOW()` this domain never
7251
- * overrides — it holds insert time even for running rows). `completedAt` is
7293
+ * together, mirroring `deleteExperiment`. Anchored on `completedAt` rather
7294
+ * than its `completedAtZ` mirror: both are written together, but
7295
+ * `completedAt` is the column the retention index covers. `completedAt` is
7252
7296
  * written as a UTC ISO string and stays NULL while running, so
7253
7297
  * `completedAt < cutoff` is false for in-flight experiments.
7254
7298
  */
@@ -7568,7 +7612,11 @@ var ExperimentsPG = class ExperimentsPG extends ExperimentsStorage {
7568
7612
  startedAt: null,
7569
7613
  completedAt: null,
7570
7614
  createdAt: nowIso,
7571
- updatedAt: nowIso
7615
+ updatedAt: nowIso,
7616
+ startedAtZ: null,
7617
+ completedAtZ: null,
7618
+ createdAtZ: nowIso,
7619
+ updatedAtZ: nowIso
7572
7620
  }
7573
7621
  });
7574
7622
  return {
@@ -7657,12 +7705,14 @@ var ExperimentsPG = class ExperimentsPG extends ExperimentsStorage {
7657
7705
  values.push(input.skippedCount);
7658
7706
  }
7659
7707
  if (input.startedAt !== void 0) {
7660
- setClauses.push(`"startedAt" = $${paramIndex++}`);
7661
- values.push(input.startedAt?.toISOString() ?? null);
7708
+ const iso = input.startedAt?.toISOString() ?? null;
7709
+ setClauses.push(`"startedAt" = $${paramIndex++}`, `"startedAtZ" = $${paramIndex++}`);
7710
+ values.push(iso, iso);
7662
7711
  }
7663
7712
  if (input.completedAt !== void 0) {
7664
- setClauses.push(`"completedAt" = $${paramIndex++}`);
7665
- values.push(input.completedAt?.toISOString() ?? null);
7713
+ const iso = input.completedAt?.toISOString() ?? null;
7714
+ setClauses.push(`"completedAt" = $${paramIndex++}`, `"completedAtZ" = $${paramIndex++}`);
7715
+ values.push(iso, iso);
7666
7716
  }
7667
7717
  values.push(input.id);
7668
7718
  await this.#db.client.none(`UPDATE ${tableName} SET ${setClauses.join(", ")} WHERE "id" = $${paramIndex}`, values);
@@ -7691,7 +7741,7 @@ var ExperimentsPG = class ExperimentsPG extends ExperimentsStorage {
7691
7741
  });
7692
7742
  const { conditions, params } = tenancyWhere(filters, 2);
7693
7743
  const whereSql = ["\"id\" = $1", ...conditions].join(" AND ");
7694
- const result = await client.oneOrNone(`SELECT * FROM ${tableName} WHERE ${whereSql}`, [id, ...params]);
7744
+ const result = await client.oneOrNone(`SELECT ${EXPERIMENT_COLUMNS} FROM ${tableName} WHERE ${whereSql}`, [id, ...params]);
7695
7745
  return result ? this.transformExperimentRow(result) : null;
7696
7746
  } catch (error) {
7697
7747
  throw new MastraError({
@@ -7778,7 +7828,7 @@ var ExperimentsPG = class ExperimentsPG extends ExperimentsStorage {
7778
7828
  const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
7779
7829
  const limitValue = perPageInput === false ? total : perPage;
7780
7830
  return {
7781
- experiments: (await this.#db.readClient.manyOrNone(`SELECT * FROM ${tableName} ${whereClause} ORDER BY "${orderBy.field}" ${orderBy.direction}, "id" ASC LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`, [
7831
+ experiments: (await this.#db.readClient.manyOrNone(`SELECT ${EXPERIMENT_COLUMNS} FROM ${tableName} ${whereClause} ORDER BY "${orderBy.field}" ${orderBy.direction}, "id" ASC LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`, [
7782
7832
  ...queryParams,
7783
7833
  limitValue,
7784
7834
  offset
@@ -7856,9 +7906,11 @@ var ExperimentsPG = class ExperimentsPG extends ExperimentsStorage {
7856
7906
  return t.one(`INSERT INTO ${resultsTable} (
7857
7907
  "id", "experimentId", "itemId", "itemDatasetVersion", "organizationId", "projectId",
7858
7908
  "input", "output", "groundTruth", "metadata", "error", "startedAt", "completedAt",
7859
- "retryCount", "attempt", "traceId", "status", "tags", "toolMockReport", "createdAt"
7860
- ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20)
7861
- RETURNING *`, [
7909
+ "retryCount", "attempt", "traceId", "status", "tags", "toolMockReport", "createdAt",
7910
+ "startedAtZ", "completedAtZ", "createdAtZ"
7911
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20,
7912
+ $21, $22, $23)
7913
+ RETURNING ${EXPERIMENT_RESULT_COLUMNS}`, [
7862
7914
  id,
7863
7915
  input.experimentId,
7864
7916
  input.itemId,
@@ -7878,6 +7930,9 @@ var ExperimentsPG = class ExperimentsPG extends ExperimentsStorage {
7878
7930
  input.status ?? null,
7879
7931
  purgeMetadata || input.tags == null ? null : toPgJson(input.tags),
7880
7932
  purgeMetadata || input.toolMockReport == null ? null : toPgJson(input.toolMockReport),
7933
+ nowIso,
7934
+ input.startedAt.toISOString(),
7935
+ input.completedAt.toISOString(),
7881
7936
  nowIso
7882
7937
  ]);
7883
7938
  });
@@ -7904,6 +7959,7 @@ var ExperimentsPG = class ExperimentsPG extends ExperimentsStorage {
7904
7959
  const row = await this.#db.client.tx(async (t) => {
7905
7960
  const owner = await t.oneOrNone(`SELECT "datasetId" FROM ${experimentsTable} WHERE "id" = $1`, [input.experimentId]);
7906
7961
  const purgeMetadata = await this.#resolvePurgeMetadata(t, owner?.datasetId, input.itemId);
7962
+ const nowIso = (/* @__PURE__ */ new Date()).toISOString();
7907
7963
  const existing = await t.oneOrNone(`SELECT "id" FROM ${tableName} WHERE "experimentId" = $1 AND "itemId" = $2 AND COALESCE("attempt", 0) = $3 FOR UPDATE`, [
7908
7964
  input.experimentId,
7909
7965
  input.itemId,
@@ -7914,9 +7970,11 @@ var ExperimentsPG = class ExperimentsPG extends ExperimentsStorage {
7914
7970
  return t.one(`INSERT INTO ${tableName} (
7915
7971
  "id", "experimentId", "itemId", "itemDatasetVersion", "organizationId", "projectId",
7916
7972
  "input", "output", "groundTruth", "metadata", "error", "startedAt", "completedAt",
7917
- "retryCount", "attempt", "traceId", "status", "tags", "toolMockReport", "createdAt"
7918
- ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20)
7919
- RETURNING *`, [
7973
+ "retryCount", "attempt", "traceId", "status", "tags", "toolMockReport", "createdAt",
7974
+ "startedAtZ", "completedAtZ", "createdAtZ"
7975
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20,
7976
+ $21, $22, $23)
7977
+ RETURNING ${EXPERIMENT_RESULT_COLUMNS}`, [
7920
7978
  id,
7921
7979
  input.experimentId,
7922
7980
  input.itemId,
@@ -7936,15 +7994,19 @@ var ExperimentsPG = class ExperimentsPG extends ExperimentsStorage {
7936
7994
  input.status ?? null,
7937
7995
  purgeMetadata || input.tags == null ? null : toPgJson(input.tags),
7938
7996
  purgeMetadata || input.toolMockReport == null ? null : toPgJson(input.toolMockReport),
7939
- (/* @__PURE__ */ new Date()).toISOString()
7997
+ nowIso,
7998
+ input.startedAt.toISOString(),
7999
+ input.completedAt.toISOString(),
8000
+ nowIso
7940
8001
  ]);
7941
8002
  }
7942
8003
  return t.one(`UPDATE ${tableName} SET
7943
8004
  "itemDatasetVersion" = $2, "organizationId" = $3, "projectId" = $4,
7944
8005
  "input" = $5, "output" = $6, "groundTruth" = $7, "metadata" = $8, "error" = $9,
7945
8006
  "startedAt" = $10, "completedAt" = $11, "retryCount" = $12, "attempt" = $13,
7946
- "traceId" = $14, "status" = $15, "tags" = $16, "toolMockReport" = $17
7947
- WHERE "id" = $1 RETURNING *`, [
8007
+ "traceId" = $14, "status" = $15, "tags" = $16, "toolMockReport" = $17,
8008
+ "startedAtZ" = $18, "completedAtZ" = $19
8009
+ WHERE "id" = $1 RETURNING ${EXPERIMENT_RESULT_COLUMNS}`, [
7948
8010
  existing.id,
7949
8011
  input.itemDatasetVersion ?? null,
7950
8012
  input.organizationId ?? null,
@@ -7961,7 +8023,9 @@ var ExperimentsPG = class ExperimentsPG extends ExperimentsStorage {
7961
8023
  input.traceId ?? null,
7962
8024
  input.status ?? null,
7963
8025
  purgeMetadata || input.tags == null ? null : toPgJson(input.tags),
7964
- purgeMetadata || input.toolMockReport == null ? null : toPgJson(input.toolMockReport)
8026
+ purgeMetadata || input.toolMockReport == null ? null : toPgJson(input.toolMockReport),
8027
+ input.startedAt.toISOString(),
8028
+ input.completedAt.toISOString()
7965
8029
  ]);
7966
8030
  });
7967
8031
  return this.transformExperimentResultRow(row);
@@ -7995,7 +8059,7 @@ var ExperimentsPG = class ExperimentsPG extends ExperimentsStorage {
7995
8059
  "tags" = CASE WHEN $4 THEN NULL WHEN $5 THEN $6::jsonb ELSE "tags" END,
7996
8060
  "comment" = CASE WHEN $4 THEN NULL WHEN $7 THEN $8 ELSE "comment" END
7997
8061
  WHERE "id" = $1${input.experimentId !== void 0 ? " AND \"experimentId\" = $9" : ""}
7998
- RETURNING *`, [
8062
+ RETURNING ${EXPERIMENT_RESULT_COLUMNS}`, [
7999
8063
  input.id,
8000
8064
  input.status !== void 0,
8001
8065
  input.status ?? null,
@@ -8038,7 +8102,7 @@ var ExperimentsPG = class ExperimentsPG extends ExperimentsStorage {
8038
8102
  });
8039
8103
  const { conditions, params } = tenancyWhere(filters, 2);
8040
8104
  const whereSql = ["\"id\" = $1", ...conditions].join(" AND ");
8041
- const result = await client.oneOrNone(`SELECT * FROM ${tableName} WHERE ${whereSql}`, [id, ...params]);
8105
+ const result = await client.oneOrNone(`SELECT ${EXPERIMENT_RESULT_COLUMNS} FROM ${tableName} WHERE ${whereSql}`, [id, ...params]);
8042
8106
  return result ? this.transformExperimentResultRow(result) : null;
8043
8107
  } catch (error) {
8044
8108
  throw new MastraError({
@@ -8101,7 +8165,7 @@ var ExperimentsPG = class ExperimentsPG extends ExperimentsStorage {
8101
8165
  const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
8102
8166
  const limitValue = perPageInput === false ? total : perPage;
8103
8167
  return {
8104
- results: (await this.#db.readClient.manyOrNone(`SELECT * FROM ${tableName} ${whereClause} ORDER BY "${orderBy.field}" ${orderBy.direction}, "id" ASC LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`, [
8168
+ results: (await this.#db.readClient.manyOrNone(`SELECT ${EXPERIMENT_RESULT_COLUMNS} FROM ${tableName} ${whereClause} ORDER BY "${orderBy.field}" ${orderBy.direction}, "id" ASC LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`, [
8105
8169
  ...queryParams,
8106
8170
  limitValue,
8107
8171
  offset
@@ -19520,6 +19584,13 @@ const timeToFirstChunkSortSupported = coreStorage.TRACE_QUERY_ORDER_FIELDS?.incl
19520
19584
  /** Same rule for time to first token: returned and sorted only when core knows the field. */
19521
19585
  const timeToFirstTokenSupported = "timeToFirstTokenMs" in coreStorage.TRACE_QUERY_FIELD_REGISTRY.trace;
19522
19586
  const timeToFirstTokenSortSupported = coreStorage.TRACE_QUERY_ORDER_FIELDS?.includes("timeToFirstTokenMs") === true;
19587
+ /**
19588
+ * Outcome fields follow the same rule: core before them rejects their predicates and strict rows,
19589
+ * so they are only returned and declared when core knows them.
19590
+ */
19591
+ const spanQueryOutcomeFieldsSupported = "outcome" in (coreStorage.SPAN_QUERY_FIELD_REGISTRY ?? {});
19592
+ const traceQueryOutcomeFieldsSupported = "outcome" in (coreStorage.TRACE_QUERY_FIELD_REGISTRY?.trace ?? {});
19593
+ const outcomeFeatures = [...spanQueryOutcomeFieldsSupported ? ["span-query-outcome-fields"] : [], ...traceQueryOutcomeFieldsSupported ? ["trace-query-outcome-fields"] : []];
19523
19594
  const timingFeatures = [
19524
19595
  ...timeToFirstChunkSupported ? ["trace-query-time-to-first-chunk"] : [],
19525
19596
  ...timeToFirstChunkSortSupported ? ["trace-query-time-to-first-chunk-sort"] : [],
@@ -19566,6 +19637,8 @@ const TRACE_FIELDS = {
19566
19637
  spanType: "r.\"spanType\"",
19567
19638
  environment: "r.\"environment\"",
19568
19639
  status: TRACE_STATUS_SQL,
19640
+ outcome: "r.\"outcome\"",
19641
+ stopReason: "r.\"stopReason\"",
19569
19642
  tags: "r.\"tags\""
19570
19643
  };
19571
19644
  const SPAN_FIELDS = {
@@ -19579,6 +19652,8 @@ const SPAN_FIELDS = {
19579
19652
  timeToFirstChunkMs: "s.\"timeToFirstChunkMs\"",
19580
19653
  timeToFirstTokenMs: "s.\"timeToFirstTokenMs\"",
19581
19654
  status: "s.\"status\"",
19655
+ outcome: "s.\"outcome\"",
19656
+ stopReason: "s.\"stopReason\"",
19582
19657
  error: "s.\"error\"",
19583
19658
  entityType: "s.\"entityType\"",
19584
19659
  entityId: "s.\"entityId\"",
@@ -19652,7 +19727,10 @@ const TRACE_SELECT = `
19652
19727
  r."entityName" AS "entityName",
19653
19728
  r."entityType" AS "entityType",
19654
19729
  r."environment" AS "environment",
19655
- ${TRACE_STATUS_SQL} AS "status"`;
19730
+ ${TRACE_STATUS_SQL} AS "status",
19731
+ r."outcome" AS "outcome",
19732
+ r."stopReason" AS "stopReason",
19733
+ r."stopReasonContext" AS "stopReasonContext"`;
19656
19734
  function fieldSql(registry, field) {
19657
19735
  const sql = registry[field];
19658
19736
  if (sql === void 0) throw new Error(`Unsupported trusted trace-query field: ${field}`);
@@ -20009,8 +20087,8 @@ function compilePostgresTraceScope(schema, selection, relationCollections, scope
20009
20087
  s."traceId",
20010
20088
  s."name",
20011
20089
  s."spanType",
20012
- CASE WHEN jsonb_typeof(s."attributes" -> 'model') = 'string' THEN s."attributes" ->> 'model' END AS "model",
20013
- CASE WHEN jsonb_typeof(s."attributes" -> 'provider') = 'string' THEN s."attributes" ->> 'provider' END AS "provider",
20090
+ COALESCE(s."model", CASE WHEN jsonb_typeof(s."attributes" -> 'model') = 'string' THEN s."attributes" ->> 'model' END) AS "model",
20091
+ COALESCE(s."provider", CASE WHEN jsonb_typeof(s."attributes" -> 'provider') = 'string' THEN s."attributes" ->> 'provider' END) AS "provider",
20014
20092
  s."startedAt",
20015
20093
  CASE WHEN s."isPending" THEN NULL ELSE s."endedAt" END AS "endedAt",
20016
20094
  CASE
@@ -20020,6 +20098,8 @@ function compilePostgresTraceScope(schema, selection, relationCollections, scope
20020
20098
  s."timeToFirstChunkMs",
20021
20099
  s."timeToFirstTokenMs",
20022
20100
  CASE WHEN s."error" IS NOT NULL THEN 'error' ELSE 'success' END AS "status",
20101
+ s."outcome",
20102
+ s."stopReason",
20023
20103
  s."error",
20024
20104
  s."entityType",
20025
20105
  s."entityId",
@@ -20397,7 +20477,12 @@ function traceRowToResult(row) {
20397
20477
  entityName: row.entityName == null ? null : String(row.entityName),
20398
20478
  entityType: row.entityType == null ? null : String(row.entityType),
20399
20479
  environment: row.environment == null ? null : String(row.environment),
20400
- status: row.status
20480
+ status: row.status,
20481
+ ...traceQueryOutcomeFieldsSupported && {
20482
+ outcome: row.outcome == null ? null : String(row.outcome),
20483
+ stopReason: row.stopReason == null ? null : String(row.stopReason),
20484
+ stopReasonContext: row.stopReasonContext ?? null
20485
+ }
20401
20486
  };
20402
20487
  }
20403
20488
  async function queryTraces(client, schema, plan, timeoutMs) {
@@ -20550,7 +20635,9 @@ const fields = [
20550
20635
  "environment",
20551
20636
  "executionSource",
20552
20637
  "timeToFirstChunkMs",
20553
- "timeToFirstTokenMs"
20638
+ "timeToFirstTokenMs",
20639
+ "outcome",
20640
+ "stopReason"
20554
20641
  ];
20555
20642
  const identities = identityFields.map((field) => `"${field}"`).join(", ");
20556
20643
  const identityOrder = identityFields.map((field) => `"${field}" COLLATE "C" ASC NULLS LAST`).join(", ");
@@ -20600,8 +20687,6 @@ function compilePostgresSpanSelection(schema, plan) {
20600
20687
  };
20601
20688
  values.push(...predicate.values);
20602
20689
  const table = qualifiedTable(schema, TABLE_SPAN_EVENTS);
20603
- const legacyModelFields = coreStorage.SPAN_QUERY_FIELD_REGISTRY ? "" : `CASE WHEN jsonb_typeof(attributes -> 'model') = 'string' THEN attributes ->> 'model' END AS model,
20604
- CASE WHEN jsonb_typeof(attributes -> 'provider') = 'string' THEN attributes ->> 'provider' END AS provider,`;
20605
20690
  return {
20606
20691
  ctes: [
20607
20692
  `candidate_ids AS MATERIALIZED (
@@ -20612,7 +20697,7 @@ function compilePostgresSpanSelection(schema, plan) {
20612
20697
  `current_spans AS MATERIALIZED (
20613
20698
  SELECT DISTINCT ON (${identities})
20614
20699
  ${fields.map((field) => `"${field}"`).join(", ")}, "startedAt", "endedAt", "cursorId"::text AS version,
20615
- ${legacyModelFields}
20700
+ "model", "provider",
20616
20701
  (EXTRACT(EPOCH FROM ("endedAt" - "startedAt")) * 1000)::float8 AS "durationMs",
20617
20702
  CASE WHEN error IS NULL THEN 'success' ELSE 'error' END AS status,
20618
20703
  error IS NOT NULL AS "hasError"
@@ -20682,10 +20767,14 @@ async function querySpans(client, schema, plan, timeoutMs) {
20682
20767
  return runWithPostgresTraceQueryTimeout(client, timeoutMs, async (tx) => {
20683
20768
  const query = compilePostgresSpanQuery(schema, plan);
20684
20769
  const rows = await tx.any(query.text, query.values);
20685
- const selected = rows.map(({ version: _version, startedAt, endedAt, timeToFirstChunkMs, timeToFirstTokenMs, ...row }) => ({
20770
+ const selected = rows.map(({ version: _version, startedAt, endedAt, timeToFirstChunkMs, timeToFirstTokenMs, outcome, stopReason, ...row }) => ({
20686
20771
  ...row,
20687
20772
  ...timeToFirstChunkSupported && { timeToFirstChunkMs },
20688
20773
  ...timeToFirstTokenSupported && { timeToFirstTokenMs },
20774
+ ...spanQueryOutcomeFieldsSupported && {
20775
+ outcome,
20776
+ stopReason
20777
+ },
20689
20778
  startedAt: startedAt.toISOString(),
20690
20779
  endedAt: endedAt.toISOString()
20691
20780
  }));
@@ -20703,18 +20792,22 @@ async function querySpans(client, schema, plan, timeoutMs) {
20703
20792
  payloadValues.push(row.version);
20704
20793
  return `(${key} AND "cursorId" = $${payloadValues.length}::bigint)`;
20705
20794
  }).join(" OR ");
20706
- const payloadRows = await tx.any(`SELECT ${identities},
20707
- CASE WHEN jsonb_typeof(attributes -> 'model') = 'string' THEN attributes ->> 'model' END AS model,
20708
- CASE WHEN jsonb_typeof(attributes -> 'provider') = 'string' THEN attributes ->> 'provider' END AS provider,
20795
+ const payloadRows = await tx.any(`SELECT ${identities}, "stopReasonContext",
20796
+ COALESCE("model", CASE WHEN jsonb_typeof(attributes -> 'model') = 'string' THEN attributes ->> 'model' END) AS model,
20797
+ COALESCE("provider", CASE WHEN jsonb_typeof(attributes -> 'provider') = 'string' THEN attributes ->> 'provider' END) AS provider,
20709
20798
  ${PREVIEW_PAYLOADS$1.map(([preview, payload]) => `"${preview}", CASE WHEN ${missingPreviewSql(`"${preview}"`)} THEN "${payload}" END AS "${payload}"`).join(",\n ")}
20710
20799
  FROM ${qualifiedTable(schema, TABLE_SPAN_EVENTS)} WHERE ${payloadWhere}`, payloadValues);
20711
20800
  const payloads = payloadRows.map(toSpanQueryPayload);
20712
20801
  const metadata = new Map(payloadRows.map((row) => [coreStorage.spanQueryIdentityKey(row), row]));
20713
- const hydrated = selected.map((row) => ({
20714
- ...row,
20715
- model: metadata.get(coreStorage.spanQueryIdentityKey(row))?.model ?? null,
20716
- provider: metadata.get(coreStorage.spanQueryIdentityKey(row))?.provider ?? null
20717
- }));
20802
+ const hydrated = selected.map((row) => {
20803
+ const stored = metadata.get(coreStorage.spanQueryIdentityKey(row));
20804
+ return {
20805
+ ...row,
20806
+ model: stored?.model ?? null,
20807
+ provider: stored?.provider ?? null,
20808
+ ...spanQueryOutcomeFieldsSupported && { stopReasonContext: stored?.stopReasonContext ?? null }
20809
+ };
20810
+ });
20718
20811
  let metrics = [];
20719
20812
  if (!coreStorage.SPAN_QUERY_FIELD_REGISTRY) {
20720
20813
  await remaining();
@@ -20956,6 +21049,8 @@ const DIMENSION_SQL = {
20956
21049
  entityId: "r.\"entityId\"",
20957
21050
  environment: "r.\"environment\"",
20958
21051
  status: TRACE_STATUS_SQL,
21052
+ outcome: "r.\"outcome\"",
21053
+ stopReason: "r.\"stopReason\"",
20959
21054
  serviceName: "r.\"serviceName\"",
20960
21055
  executionSource: "r.\"executionSource\"",
20961
21056
  threadId: "r.\"threadId\"",
@@ -20978,6 +21073,8 @@ const SPAN_DIMENSION_SQL = Object.fromEntries([
20978
21073
  "rootEntityName",
20979
21074
  "environment",
20980
21075
  "status",
21076
+ "outcome",
21077
+ "stopReason",
20981
21078
  "serviceName",
20982
21079
  "executionSource",
20983
21080
  "threadId",
@@ -20985,7 +21082,9 @@ const SPAN_DIMENSION_SQL = Object.fromEntries([
20985
21082
  "userId",
20986
21083
  "sessionId",
20987
21084
  "organizationId",
20988
- "experimentId"
21085
+ "experimentId",
21086
+ "model",
21087
+ "provider"
20989
21088
  ].map((field) => [field, `r."${field}"`]));
20990
21089
  const PERCENTILES = {
20991
21090
  "duration.p50": .5,
@@ -22038,8 +22137,12 @@ async function dangerouslyClearTracing(client, schema) {
22038
22137
  * application database — observability writes will degrade app performance.
22039
22138
  * Use it through `MastraCompositeStore` with a dedicated Postgres connection.
22040
22139
  */
22041
- const spanQueryFeatures = typeof coreStorage.planSpanQuery === "function" ? ["span-query", "span-query-context-fields"] : [];
22042
- const spanAggregateFeatures = typeof coreStorage.planSpanAggregate === "function" ? ["span-aggregate"] : [];
22140
+ const spanQueryFeatures = typeof coreStorage.planSpanQuery === "function" ? [
22141
+ "span-query",
22142
+ "span-query-context-fields",
22143
+ ..."model" in (coreStorage.SPAN_QUERY_FIELD_REGISTRY ?? {}) ? ["span-query-model-fields"] : []
22144
+ ] : [];
22145
+ const spanAggregateFeatures = typeof coreStorage.planSpanAggregate === "function" ? ["span-aggregate", ..."model" in (coreStorage.SPAN_AGGREGATE_DIMENSION_REGISTRY ?? {}) ? ["span-aggregate-model-dimensions"] : []] : [];
22043
22146
  function wrapError(op, error, details) {
22044
22147
  if (error instanceof MastraError || error instanceof coreStorage.TraceQueryCursorError || error instanceof coreStorage.TraceQueryExecutionError || error instanceof coreStorage.TraceQueryResourceLimitError) throw error;
22045
22148
  throw new MastraError({
@@ -22251,6 +22354,7 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
22251
22354
  "feedback",
22252
22355
  "trace-query-context-ids",
22253
22356
  ...timingFeatures,
22357
+ ...outcomeFeatures,
22254
22358
  "trace-query-root-span-fields",
22255
22359
  "trace-query-root-entity-id"
22256
22360
  ];
@@ -22276,6 +22380,7 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
22276
22380
  "feedback",
22277
22381
  "trace-query-context-ids",
22278
22382
  ...timingFeatures,
22383
+ ...outcomeFeatures,
22279
22384
  "trace-query-root-span-fields",
22280
22385
  "trace-query-root-entity-id"
22281
22386
  ];