@mastra/pg 1.32.0-alpha.2 → 1.32.0-alpha.4

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.js CHANGED
@@ -19185,6 +19185,28 @@ async function getScorePercentiles(client, schema, args) {
19185
19185
  //#endregion
19186
19186
  //#region src/storage/domains/observability/v-next/trace-query.ts
19187
19187
  const TRACE_STATUS_SQL = `CASE WHEN r."error" IS NOT NULL THEN 'error' ELSE 'success' END`;
19188
+ /** Core versions without the field reject it in strict query results, so it is only returned when core knows it. */
19189
+ const timeToFirstChunkSupported = "timeToFirstChunkMs" in coreStorage.TRACE_QUERY_FIELD_REGISTRY.trace;
19190
+ /** Core before the sort rejects `orderBy` on the field, so the sort is only declared when core plans it. */
19191
+ const timeToFirstChunkSortSupported = coreStorage.TRACE_QUERY_ORDER_FIELDS?.includes("timeToFirstChunkMs") === true;
19192
+ const timeToFirstChunkFeatures = [...timeToFirstChunkSupported ? ["trace-query-time-to-first-chunk"] : [], ...timeToFirstChunkSortSupported ? ["trace-query-time-to-first-chunk-sort"] : []];
19193
+ /**
19194
+ * Keyset bounds for a cursor's sort value: terms for rows strictly past it in `direction`, and the
19195
+ * condition for rows level with it. Empty values sort last in both directions, so they are past
19196
+ * every value and level only with each other.
19197
+ */
19198
+ function keysetSortBounds(field, direction, sortValue, values) {
19199
+ if (sortValue === null) return {
19200
+ past: [],
19201
+ level: `${field} IS NULL`
19202
+ };
19203
+ values.push(sortValue);
19204
+ const parameter = `$${values.length}::${typeof sortValue === "number" ? "float8" : "timestamptz"}`;
19205
+ return {
19206
+ past: [`${field} ${direction === "asc" ? ">" : "<"} ${parameter}`, `${field} IS NULL`],
19207
+ level: `${field} = ${parameter}`
19208
+ };
19209
+ }
19188
19210
  function durationMsSql(startedAt, endedAt) {
19189
19211
  return `EXTRACT(EPOCH FROM (${endedAt} - ${startedAt}))::numeric * 1000`;
19190
19212
  }
@@ -19199,8 +19221,11 @@ const TRACE_FIELDS = {
19199
19221
  startedAt: "r.\"startedAt\"",
19200
19222
  endedAt: "r.\"endedAt\"",
19201
19223
  durationMs: durationMsSql("r.\"startedAt\"", "r.\"endedAt\""),
19224
+ timeToFirstChunkMs: "r.\"timeToFirstChunkMs\"",
19202
19225
  entityName: "r.\"entityName\"",
19203
19226
  entityType: "r.\"entityType\"",
19227
+ name: "r.\"name\"",
19228
+ spanType: "r.\"spanType\"",
19204
19229
  environment: "r.\"environment\"",
19205
19230
  status: TRACE_STATUS_SQL,
19206
19231
  tags: "r.\"tags\""
@@ -19213,6 +19238,7 @@ const SPAN_FIELDS = {
19213
19238
  startedAt: "s.\"startedAt\"",
19214
19239
  endedAt: "s.\"endedAt\"",
19215
19240
  durationMs: "s.\"durationMs\"",
19241
+ timeToFirstChunkMs: "s.\"timeToFirstChunkMs\"",
19216
19242
  status: "s.\"status\"",
19217
19243
  error: "s.\"error\"",
19218
19244
  entityType: "s.\"entityType\"",
@@ -19282,6 +19308,7 @@ const TRACE_SELECT = `
19282
19308
  r."resourceId" AS "resourceId",
19283
19309
  r."startedAt" AS "startedAt",
19284
19310
  r."endedAt" AS "endedAt",
19311
+ r."timeToFirstChunkMs" AS "timeToFirstChunkMs",
19285
19312
  r."entityName" AS "entityName",
19286
19313
  r."entityType" AS "entityType",
19287
19314
  r."environment" AS "environment",
@@ -19405,11 +19432,11 @@ function compileScalarPredicate(predicate, registry, parameterOffset, allowMetad
19405
19432
  gte: ">="
19406
19433
  };
19407
19434
  if (predicate.operator === "eq") return {
19408
- sql: `${field} IS NOT DISTINCT FROM ${parameter}`,
19435
+ sql: `${field} IS NOT NULL AND ${field} = ${parameter}`,
19409
19436
  values: [...fieldValues, predicate.value]
19410
19437
  };
19411
19438
  if (predicate.operator === "ne") return {
19412
- sql: `${field} IS DISTINCT FROM ${parameter}`,
19439
+ sql: `${field} IS NULL OR ${field} <> ${parameter}`,
19413
19440
  values: [...fieldValues, predicate.value]
19414
19441
  };
19415
19442
  const operator = operators[predicate.operator];
@@ -19498,6 +19525,8 @@ const ROOT_PUSHDOWN_FIELDS = /* @__PURE__ */ new Set([
19498
19525
  "endedAt",
19499
19526
  "entityName",
19500
19527
  "entityType",
19528
+ "name",
19529
+ "spanType",
19501
19530
  "environment"
19502
19531
  ]);
19503
19532
  function isRootPushdownPredicate(predicate) {
@@ -19647,6 +19676,7 @@ function compilePostgresTraceScope(schema, selection, relationCollections, scope
19647
19676
  WHEN s."isPending" THEN NULL
19648
19677
  ELSE ${durationMsSql("s.\"startedAt\"", "s.\"endedAt\"")}
19649
19678
  END AS "durationMs",
19679
+ s."timeToFirstChunkMs",
19650
19680
  CASE WHEN s."error" IS NOT NULL THEN 'error' ELSE 'success' END AS "status",
19651
19681
  s."error",
19652
19682
  s."entityType",
@@ -19768,8 +19798,8 @@ LIMIT $${values.length}`,
19768
19798
  values
19769
19799
  };
19770
19800
  }
19771
- const orderField = plan.orderBy.field === "startedAt" ? "\"startedAt\"" : "\"endedAt\"";
19772
- const direction = plan.orderBy.direction === "asc" ? "ASC" : "DESC";
19801
+ const orderField = `"${plan.orderBy.field}"`;
19802
+ const direction = `${plan.orderBy.direction === "asc" ? "ASC" : "DESC"} NULLS LAST`;
19773
19803
  if (plan.paginationMode === "page") {
19774
19804
  values.push(plan.perPage, plan.page * plan.perPage);
19775
19805
  return {
@@ -19783,10 +19813,9 @@ LIMIT $${values.length - 1} OFFSET $${values.length}`,
19783
19813
  }
19784
19814
  let pageCondition = "";
19785
19815
  if (plan.cursor) {
19786
- const comparison = plan.orderBy.direction === "asc" ? ">" : "<";
19787
- const sortParameter = `$${values.length + 1}`;
19788
- pageCondition = `WHERE (${orderField} ${comparison} ${sortParameter} OR (${orderField} = ${sortParameter} AND "traceId" > ${`$${values.length + 2}`}))`;
19789
- values.push(plan.cursor.sortValue, plan.cursor.traceId);
19816
+ const { past, level } = keysetSortBounds(orderField, plan.orderBy.direction, plan.cursor.sortValue, values);
19817
+ values.push(plan.cursor.traceId);
19818
+ pageCondition = `WHERE (${[...past, `(${level} AND "traceId" > $${values.length})`].join(" OR ")})`;
19790
19819
  }
19791
19820
  values.push(plan.limit + 1);
19792
19821
  return {
@@ -20021,6 +20050,7 @@ function traceRowToResult(row) {
20021
20050
  resourceId: row.resourceId == null ? null : String(row.resourceId),
20022
20051
  startedAt: asIsoTimestamp$1(row.startedAt),
20023
20052
  endedAt: asIsoTimestamp$1(row.endedAt),
20053
+ ...timeToFirstChunkSupported && { timeToFirstChunkMs: row.timeToFirstChunkMs == null ? null : Number(row.timeToFirstChunkMs) },
20024
20054
  entityName: row.entityName == null ? null : String(row.entityName),
20025
20055
  entityType: row.entityType == null ? null : String(row.entityType),
20026
20056
  environment: row.environment == null ? null : String(row.environment),
@@ -20118,7 +20148,7 @@ async function queryTraces(client, schema, plan, timeoutMs) {
20118
20148
  traces,
20119
20149
  page: { next: rows.length > plan.limit && last ? coreStorage.encodeTraceQueryCursor(plan, {
20120
20150
  result: "traces",
20121
- sortValue: last[plan.orderBy.field],
20151
+ sortValue: last[plan.orderBy.field] ?? null,
20122
20152
  traceId: last.traceId
20123
20153
  }) : null }
20124
20154
  });
@@ -20140,6 +20170,9 @@ async function queryThreads(client, schema, plan, timeoutMs) {
20140
20170
  function compileSpanQueryPredicate(predicate, offset) {
20141
20171
  return compileScalarPredicate(predicate, SPAN_QUERY_FIELDS, offset);
20142
20172
  }
20173
+ function compileThreadQueryPredicate(predicate, offset) {
20174
+ return compileScalarPredicate(predicate, Object.fromEntries(Object.keys(coreStorage.THREAD_QUERY_FIELD_REGISTRY).map((field) => [field, `s."${field}"`])), offset);
20175
+ }
20143
20176
  //#endregion
20144
20177
  //#region src/storage/domains/observability/v-next/span-query.ts
20145
20178
  const identityFields = [
@@ -20173,7 +20206,8 @@ const fields = [
20173
20206
  "serviceName",
20174
20207
  "experimentId",
20175
20208
  "environment",
20176
- "executionSource"
20209
+ "executionSource",
20210
+ "timeToFirstChunkMs"
20177
20211
  ];
20178
20212
  const identities = identityFields.map((field) => `"${field}"`).join(", ");
20179
20213
  const identityOrder = identityFields.map((field) => `"${field}" COLLATE "C" ASC NULLS LAST`).join(", ");
@@ -20186,11 +20220,8 @@ function selectedKeys(rows, values) {
20186
20220
  function afterCursor(plan, values) {
20187
20221
  if (!plan.cursor) return "TRUE";
20188
20222
  const cursor = plan.cursor;
20189
- values.push(cursor.sortValue);
20190
- const timestamp = `$${values.length}::timestamptz`;
20191
- const field = `s."${plan.orderBy.field}"`;
20192
- const terms = [`${field} ${plan.orderBy.direction === "asc" ? ">" : "<"} ${timestamp}`];
20193
- const equal = [`${field} = ${timestamp}`];
20223
+ const { past: terms, level } = keysetSortBounds(`s."${plan.orderBy.field}"`, plan.orderBy.direction, cursor.sortValue, values);
20224
+ const equal = [level];
20194
20225
  for (const key of identityFields) {
20195
20226
  const ref = `s."${key}" COLLATE "C"`;
20196
20227
  const value = cursor[key];
@@ -20256,7 +20287,7 @@ function compilePostgresSpanQuery(schema, plan) {
20256
20287
  "version"
20257
20288
  ].map((field) => `s."${field}"`).join(", ")}
20258
20289
  FROM candidates s WHERE s."startedAt" >= $1 AND s."startedAt" < $2 AND (${predicate.sql}) AND ${after}
20259
- ORDER BY s."${plan.orderBy.field}" ${plan.orderBy.direction === "asc" ? "ASC" : "DESC"}, ${identityOrder}
20290
+ ORDER BY s."${plan.orderBy.field}" ${plan.orderBy.direction === "asc" ? "ASC" : "DESC"} NULLS LAST, ${identityOrder}
20260
20291
  LIMIT $${values.length}`,
20261
20292
  values
20262
20293
  };
@@ -20289,8 +20320,9 @@ async function querySpans(client, schema, plan, timeoutMs) {
20289
20320
  return runWithPostgresTraceQueryTimeout(client, timeoutMs, async (tx) => {
20290
20321
  const query = compilePostgresSpanQuery(schema, plan);
20291
20322
  const rows = await tx.any(query.text, query.values);
20292
- const selected = rows.map(({ version: _version, startedAt, endedAt, ...row }) => ({
20323
+ const selected = rows.map(({ version: _version, startedAt, endedAt, timeToFirstChunkMs, ...row }) => ({
20293
20324
  ...row,
20325
+ ...timeToFirstChunkSupported && { timeToFirstChunkMs },
20294
20326
  startedAt: startedAt.toISOString(),
20295
20327
  endedAt: endedAt.toISOString()
20296
20328
  }));
@@ -20337,11 +20369,245 @@ async function querySpans(client, schema, plan, timeoutMs) {
20337
20369
  }, { repeatableRead: true });
20338
20370
  }
20339
20371
  //#endregion
20372
+ //#region src/storage/domains/observability/v-next/thread-query.ts
20373
+ const contextFields = [
20374
+ "resourceId",
20375
+ "userId",
20376
+ "entityType",
20377
+ "entityId",
20378
+ "entityName",
20379
+ "entityVersionId",
20380
+ "runId",
20381
+ "sessionId",
20382
+ "requestId",
20383
+ "serviceName",
20384
+ "experimentId",
20385
+ "environment",
20386
+ "executionSource"
20387
+ ];
20388
+ function predicateFields(predicate, fields = /* @__PURE__ */ new Set()) {
20389
+ if (!predicate) return fields;
20390
+ if (predicate.type === "boolean") for (const arg of predicate.args) predicateFields(arg, fields);
20391
+ else if (predicate.type === "not") predicateFields(predicate.arg, fields);
20392
+ else fields.add(predicate.field);
20393
+ return fields;
20394
+ }
20395
+ function canUseThreadCandidates(probe) {
20396
+ return Number(probe.threadCount) <= 16384 && Number(probe.traceCount) <= 1e5 && Number(probe.threadBytes) + Number(probe.traceBytes) <= 4 * 1024 * 1024;
20397
+ }
20398
+ function compilePostgresThreadRowsQuery(schema, plan, strategy = "full") {
20399
+ const values = [plan.timeRange.from, plan.timeRange.to];
20400
+ const scope = [];
20401
+ if (plan.scope) {
20402
+ values.push(plan.scope.organizationId);
20403
+ scope.push(`"organizationId" = $${values.length}`);
20404
+ if (plan.scope.resourceId !== void 0) {
20405
+ values.push(plan.scope.resourceId);
20406
+ scope.push(`"resourceId" = $${values.length}`);
20407
+ }
20408
+ }
20409
+ const table = qualifiedTable(schema, TABLE_SPAN_EVENTS);
20410
+ const timestamp = plan.timeRange.mode === "threads" ? plan.timeRange.field : "startedAt";
20411
+ const identity = (field) => `"organizationId" IS NULL, COALESCE("organizationId", ''), "${field}"`;
20412
+ const threadFilter = `(${identity("threadId")}) IN (SELECT ${identity("threadId")} FROM candidate_threads)`;
20413
+ const scopedRoots = `scoped_roots AS NOT MATERIALIZED (
20414
+ SELECT * FROM ${table} WHERE "parentSpanId" IS NULL ${scope.length ? `AND ${scope.join(" AND ")}` : ""}
20415
+ )`;
20416
+ const candidates = `candidate_threads AS MATERIALIZED (
20417
+ SELECT DISTINCT "organizationId", "threadId" FROM scoped_roots
20418
+ WHERE "threadId" IS NOT NULL AND "${timestamp}" >= $1 AND "${timestamp}" < $2
20419
+ ${strategy === "probe" ? "LIMIT 16385" : ""}
20420
+ ), candidate_traces AS MATERIALIZED (
20421
+ SELECT DISTINCT "organizationId", "traceId" FROM scoped_roots WHERE ${threadFilter}
20422
+ ${strategy === "probe" ? "LIMIT 100001" : ""}
20423
+ )`;
20424
+ if (strategy === "probe") return {
20425
+ text: `WITH ${scopedRoots}, ${candidates}
20426
+ SELECT (SELECT count(*) FROM candidate_threads) AS "threadCount",
20427
+ (SELECT COALESCE(sum(octet_length(COALESCE("organizationId", '')) + octet_length("threadId")), 0) FROM candidate_threads) AS "threadBytes",
20428
+ count(*) AS "traceCount", COALESCE(sum(octet_length(COALESCE("organizationId", '')) + octet_length("traceId")), 0) AS "traceBytes"
20429
+ FROM candidate_traces`,
20430
+ values
20431
+ };
20432
+ const predicate = plan.where ? compileThreadQueryPredicate(plan.where, values.length + 1) : {
20433
+ sql: "TRUE",
20434
+ values: []
20435
+ };
20436
+ values.push(...predicate.values);
20437
+ let after = "TRUE";
20438
+ if (plan.cursor) {
20439
+ values.push(plan.cursor.sortValue);
20440
+ const stamp = `$${values.length}::timestamptz`;
20441
+ const field = `s."${plan.orderBy.field}"`;
20442
+ const terms = [`${field} ${plan.orderBy.direction === "asc" ? ">" : "<"} ${stamp}`];
20443
+ const equal = [`${field} = ${stamp}`];
20444
+ for (const key of ["organizationId", "threadId"]) {
20445
+ const ref = `s."${key}" COLLATE "C"`;
20446
+ if (plan.cursor[key] === null) equal.push(`${ref} IS NULL`);
20447
+ else {
20448
+ values.push(plan.cursor[key]);
20449
+ const value = `$${values.length}::text COLLATE "C"`;
20450
+ terms.push(`(${equal.join(" AND ")} AND (${ref} > ${value} OR ${ref} IS NULL))`);
20451
+ equal.push(`${ref} = ${value}`);
20452
+ }
20453
+ }
20454
+ after = `(${terms.join(" OR ")})`;
20455
+ }
20456
+ const qualify = plan.timeRange.mode === "traces" ? "s.\"inWindow\"" : `s."${plan.timeRange.field}" >= $1 AND s."${plan.timeRange.field}" < $2`;
20457
+ const filtered = predicateFields(plan.where);
20458
+ const firstContext = [...contextFields, "timeToFirstChunkMs"].filter((field) => field === "resourceId" || filtered.has(field));
20459
+ const lastFields = [
20460
+ "traceId",
20461
+ "spanId",
20462
+ "cursorId",
20463
+ "resourceId",
20464
+ "endedAt",
20465
+ ...filtered.has("status") ? ["status"] : []
20466
+ ];
20467
+ values.push(plan.limit + 1);
20468
+ return {
20469
+ text: `WITH ${scopedRoots}, ${strategy === "candidates" ? `${candidates}, ` : ""}current_roots AS (
20470
+ SELECT DISTINCT ON ("organizationId", "traceId")
20471
+ "organizationId", "threadId", "traceId", "spanId", "cursorId", "startedAt", "endedAt", "isPending",
20472
+ ${firstContext.map((field) => field === "timeToFirstChunkMs" ? `CASE WHEN "spanType" = 'agent_run' THEN "timeToFirstChunkMs" END AS "timeToFirstChunkMs"` : `"${field}"`).join(", ")}
20473
+ ${filtered.has("status") ? ", CASE WHEN error IS NULL THEN 'success' ELSE 'error' END AS status" : ""}
20474
+ FROM scoped_roots ${strategy === "candidates" ? `WHERE (${identity("traceId")}) IN (SELECT ${identity("traceId")} FROM candidate_traces)` : ""}
20475
+ ORDER BY "organizationId", "traceId", "startedAt" ASC, "spanId" COLLATE "C", "cursorId" DESC
20476
+ ), ranked AS (
20477
+ SELECT *, row_number() OVER first_order AS rank,
20478
+ max("endedAt") OVER population AS "lastActivity", count(*) OVER population AS "traceCount",
20479
+ bool_or("startedAt" >= $1 AND "startedAt" < $2) OVER population AS "inWindow",
20480
+ ${lastFields.map((field) => `first_value("${field}") OVER last_order AS "last${field[0].toUpperCase()}${field.slice(1)}"`).join(", ")}
20481
+ FROM current_roots WHERE NOT "isPending" AND "endedAt" IS NOT NULL AND "threadId" IS NOT NULL ${strategy === "candidates" ? `AND ${threadFilter}` : ""}
20482
+ WINDOW population AS (PARTITION BY "organizationId", "threadId"),
20483
+ first_order AS (PARTITION BY "organizationId", "threadId" ORDER BY "startedAt", "traceId" COLLATE "C"),
20484
+ last_order AS (PARTITION BY "organizationId", "threadId" ORDER BY "startedAt" DESC, "traceId" COLLATE "C" DESC)
20485
+ ), rows AS (
20486
+ SELECT "organizationId", "threadId", "traceId" AS "firstTraceId", "spanId" AS "firstSpanId", "cursorId" AS "firstCursorId",
20487
+ "endedAt" AS "firstEndedAt", "startedAt", "lastActivity" AS "endedAt", "traceCount", "inWindow",
20488
+ ${firstContext.map((field) => `"${field}"`).join(", ")},
20489
+ ${lastFields.map((field) => `"last${field[0].toUpperCase()}${field.slice(1)}"${field === "status" ? " AS status" : ""}`).join(", ")}
20490
+ FROM ranked WHERE rank = 1
20491
+ ) SELECT * FROM rows s WHERE ${qualify} AND (${predicate.sql}) AND ${after}
20492
+ ORDER BY s."${plan.orderBy.field}" ${plan.orderBy.direction === "asc" ? "ASC" : "DESC"},
20493
+ s."organizationId" COLLATE "C" ASC NULLS LAST, s."threadId" COLLATE "C" ASC LIMIT $${values.length}`,
20494
+ values
20495
+ };
20496
+ }
20497
+ async function queryThreadRows(client, schema, plan, timeoutMs, strategy = "auto") {
20498
+ const deadline = performance.now() + coreStorage.resolveTraceQueryTimeoutMs(timeoutMs);
20499
+ const run = async (text, values) => {
20500
+ const remaining = Math.floor(deadline - performance.now());
20501
+ if (remaining <= 0) throw new coreStorage.TraceQueryExecutionError();
20502
+ return await runWithPostgresTraceQueryTimeout(client, remaining, (tx) => tx.any(text, values));
20503
+ };
20504
+ if (strategy === "auto") {
20505
+ const probeQuery = compilePostgresThreadRowsQuery(schema, plan, "probe");
20506
+ const [probe] = await run(probeQuery.text, probeQuery.values);
20507
+ if (Number(probe.threadCount) === 0) return {
20508
+ threads: [],
20509
+ page: { next: null }
20510
+ };
20511
+ strategy = canUseThreadCandidates(probe) ? "candidates" : "full";
20512
+ }
20513
+ const compiled = compilePostgresThreadRowsQuery(schema, plan, strategy);
20514
+ const selected = await run(compiled.text, compiled.values);
20515
+ const page = selected.slice(0, plan.limit);
20516
+ if (!page.length) return {
20517
+ threads: [],
20518
+ page: { next: null }
20519
+ };
20520
+ const table = qualifiedTable(schema, TABLE_SPAN_EVENTS);
20521
+ const values = [];
20522
+ const keys = page.flatMap((row) => ["first", "last"].map((which) => {
20523
+ const entries = {
20524
+ organizationId: row.organizationId,
20525
+ resourceId: which === "first" ? row.resourceId : row.lastResourceId,
20526
+ traceId: row[`${which}TraceId`],
20527
+ spanId: row[`${which}SpanId`],
20528
+ cursorId: row[`${which}CursorId`],
20529
+ endedAt: row[`${which}EndedAt`]
20530
+ };
20531
+ return `(${Object.entries(entries).map(([field, value]) => {
20532
+ values.push(value);
20533
+ return `"${field}" ${field === "organizationId" || field === "resourceId" ? "IS NOT DISTINCT FROM" : "="} $${values.length}`;
20534
+ }).join(" AND ")})`;
20535
+ }));
20536
+ const payloads = await run(`SELECT "organizationId", "traceId", "cursorId", "spanType", ${contextFields.map((field) => `"${field}"`).join(", ")},
20537
+ CASE WHEN "spanType" = 'agent_run' THEN "timeToFirstChunkMs" END AS "timeToFirstChunkMs",
20538
+ CASE WHEN error IS NULL THEN 'success' ELSE 'error' END AS status,
20539
+ ${[
20540
+ "input",
20541
+ "output",
20542
+ "error"
20543
+ ].map((field) => `"${field}Preview", CASE WHEN ${missingPreviewSql(`"${field}Preview"`)} THEN "${field}" END AS "${field}"`).join(", ")}
20544
+ FROM ${table} WHERE ${keys.join(" OR ")}`, values);
20545
+ const key = (org, trace, version) => JSON.stringify([
20546
+ org,
20547
+ trace,
20548
+ String(version)
20549
+ ]);
20550
+ const byRoot = new Map(payloads.map((row) => [key(row.organizationId, row.traceId, row.cursorId), row]));
20551
+ const agents = page.filter((row) => byRoot.get(key(row.organizationId, row.firstTraceId, row.firstCursorId))?.spanType === "agent_run");
20552
+ const models = /* @__PURE__ */ new Map();
20553
+ if (agents.length) {
20554
+ const modelValues = [];
20555
+ const rows = await run(`SELECT DISTINCT ON ("organizationId", "traceId") "organizationId", "traceId",
20556
+ CASE WHEN jsonb_typeof(attributes -> 'model') = 'string' THEN attributes ->> 'model' END AS model,
20557
+ CASE WHEN jsonb_typeof(attributes -> 'provider') = 'string' THEN attributes ->> 'provider' END AS provider
20558
+ FROM ${table} WHERE "spanType" = 'model_generation' AND NOT "isPending" AND (${agents.map((row) => `(${Object.entries({
20559
+ organizationId: row.organizationId,
20560
+ resourceId: row.resourceId,
20561
+ traceId: row.firstTraceId,
20562
+ parentSpanId: row.firstSpanId
20563
+ }).map(([field, value]) => {
20564
+ modelValues.push(value);
20565
+ return `"${field}" ${field === "organizationId" || field === "resourceId" ? "IS NOT DISTINCT FROM" : "="} $${modelValues.length}`;
20566
+ }).join(" AND ")})`).join(" OR ")})
20567
+ ORDER BY "organizationId", "traceId", "startedAt", "spanId" COLLATE "C", "cursorId" DESC`, modelValues);
20568
+ for (const row of rows) models.set(JSON.stringify([row.organizationId, row.traceId]), row);
20569
+ }
20570
+ const threads = page.flatMap((row) => {
20571
+ const first = byRoot.get(key(row.organizationId, row.firstTraceId, row.firstCursorId));
20572
+ const last = byRoot.get(key(row.organizationId, row.lastTraceId, row.lastCursorId));
20573
+ if (!first || !last) return [];
20574
+ const model = models.get(JSON.stringify([row.organizationId, row.firstTraceId]));
20575
+ return [coreStorage.threadQueryRowSchema.parse({
20576
+ organizationId: row.organizationId,
20577
+ threadId: row.threadId,
20578
+ firstTraceId: row.firstTraceId,
20579
+ lastTraceId: row.lastTraceId,
20580
+ ...Object.fromEntries(contextFields.map((field) => [field, first[field]])),
20581
+ status: last.status,
20582
+ startedAt: row.startedAt.toISOString(),
20583
+ endedAt: row.endedAt.toISOString(),
20584
+ traceCount: Number(row.traceCount),
20585
+ timeToFirstChunkMs: first.timeToFirstChunkMs,
20586
+ model: model?.model ?? null,
20587
+ provider: model?.provider ?? null,
20588
+ inputPreview: readPreview(first.inputPreview, first.input),
20589
+ outputPreview: readPreview(last.outputPreview, last.output),
20590
+ errorPreview: readPreview(last.errorPreview, last.error)
20591
+ })];
20592
+ });
20593
+ const last = page.at(-1);
20594
+ return {
20595
+ threads,
20596
+ page: { next: selected.length > plan.limit ? coreStorage.encodeThreadQueryCursor(plan, {
20597
+ organizationId: last.organizationId,
20598
+ threadId: last.threadId,
20599
+ sortValue: last[plan.orderBy.field].toISOString()
20600
+ }) : null }
20601
+ };
20602
+ }
20603
+ //#endregion
20340
20604
  //#region src/storage/domains/observability/v-next/trace-aggregate.ts
20341
20605
  /** Trace-root column expressions (alias `r`) for each groupable dimension; same values `where` sees. */
20342
20606
  const DIMENSION_SQL = {
20343
20607
  entityType: "r.\"entityType\"",
20344
20608
  entityName: "r.\"entityName\"",
20609
+ name: "r.\"name\"",
20610
+ spanType: "r.\"spanType\"",
20345
20611
  environment: "r.\"environment\"",
20346
20612
  status: TRACE_STATUS_SQL,
20347
20613
  serviceName: "r.\"serviceName\"",
@@ -21568,12 +21834,15 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
21568
21834
  "trace-query",
21569
21835
  "trace-aggregate",
21570
21836
  ...spanQueryFeatures,
21837
+ ...typeof coreStorage.planThreadRowsQuery === "function" ? ["thread-query-rows"] : [],
21571
21838
  "trace-query-root-duration",
21572
21839
  "trace-query-discovery",
21573
21840
  "thread-query",
21574
21841
  "trace-query-tenant-scope",
21575
21842
  "feedback",
21576
- "trace-query-context-ids"
21843
+ "trace-query-context-ids",
21844
+ ...timeToFirstChunkFeatures,
21845
+ "trace-query-root-span-fields"
21577
21846
  ];
21578
21847
  return [
21579
21848
  "metrics",
@@ -21588,12 +21857,15 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
21588
21857
  "trace-query",
21589
21858
  "trace-aggregate",
21590
21859
  ...spanQueryFeatures,
21860
+ ...typeof coreStorage.planThreadRowsQuery === "function" ? ["thread-query-rows"] : [],
21591
21861
  "trace-query-root-duration",
21592
21862
  "trace-query-discovery",
21593
21863
  "thread-query",
21594
21864
  "trace-query-tenant-scope",
21595
21865
  "feedback",
21596
- "trace-query-context-ids"
21866
+ "trace-query-context-ids",
21867
+ ...timeToFirstChunkFeatures,
21868
+ "trace-query-root-span-fields"
21597
21869
  ];
21598
21870
  }
21599
21871
  async #run(op, fn, details) {
@@ -21655,7 +21927,7 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
21655
21927
  return this.#run("GET_TRACE_QUERY_VALUES", () => getTraceQueryValues(this.#readClient, this.#schema, plan, this.#traceQueryTimeoutMs));
21656
21928
  }
21657
21929
  async queryThreads(plan) {
21658
- return this.#run("QUERY_THREADS", () => queryThreads(this.#readClient, this.#schema, plan, this.#traceQueryTimeoutMs));
21930
+ return this.#run("QUERY_THREADS", () => plan.result === "threadRows" ? queryThreadRows(this.#readClient, this.#schema, plan, this.#traceQueryTimeoutMs) : queryThreads(this.#readClient, this.#schema, plan, this.#traceQueryTimeoutMs));
21659
21931
  }
21660
21932
  async listBranches(args) {
21661
21933
  return this.#run("LIST_BRANCHES", () => listBranches(this.#readClient, this.#schema, args));
@@ -24087,7 +24359,7 @@ var SkillsPG = class SkillsPG extends SkillsStorage {
24087
24359
  }
24088
24360
  }
24089
24361
  async list(args) {
24090
- const { page = 0, perPage: perPageInput, orderBy, authorId, visibility, status, entityIds, pinFavoritedFor, favoritedOnly } = args || {};
24362
+ const { page = 0, perPage: perPageInput, orderBy, authorId, visibility, status, entityIds, pinFavoritedFor, favoritedOnly, metadata } = args || {};
24091
24363
  const { field, direction } = this.parseOrderBy(orderBy);
24092
24364
  if (page < 0) throw new MastraError({
24093
24365
  id: createStorageErrorId("PG", "LIST_SKILLS", "INVALID_PAGE"),
@@ -24132,6 +24404,16 @@ var SkillsPG = class SkillsPG extends SkillsStorage {
24132
24404
  conditions.push(`s.visibility = $${paramIdx++}`);
24133
24405
  queryParams.push(visibility);
24134
24406
  }
24407
+ if (metadata && Object.keys(metadata).length > 0) {
24408
+ const versionsTable = getTableName$5({
24409
+ indexName: TABLE_SKILL_VERSIONS,
24410
+ schemaName: getSchemaName$5(this.#schema)
24411
+ });
24412
+ const latestVersionId = `(SELECT lv.id FROM ${versionsTable} lv WHERE lv."skillId" = s.id ORDER BY lv."versionNumber" DESC LIMIT 1)`;
24413
+ const resolvedVersionId = status === "draft" ? latestVersionId : `COALESCE(s."activeVersionId", ${latestVersionId})`;
24414
+ conditions.push(`EXISTS (SELECT 1 FROM ${versionsTable} mv WHERE mv."skillId" = s.id AND mv.id = ${resolvedVersionId} AND mv.metadata @> $${paramIdx++}::jsonb)`);
24415
+ queryParams.push(toPgJson(metadata));
24416
+ }
24135
24417
  if (entityIds && entityIds.length > 0) {
24136
24418
  const placeholders = entityIds.map(() => `$${paramIdx++}`).join(", ");
24137
24419
  conditions.push(`s.id IN (${placeholders})`);