@mastra/pg 1.32.0-alpha.1 → 1.32.0-alpha.3

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 (26) hide show
  1. package/dist/docs/SKILL.md +1 -1
  2. package/dist/docs/assets/SOURCE_MAP.json +1 -1
  3. package/dist/docs/references/docs-memory-semantic-recall.md +2 -0
  4. package/dist/index.cjs +286 -8
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.js +286 -8
  7. package/dist/index.js.map +1 -1
  8. package/dist/storage/domains/memory/index.d.ts +1 -1
  9. package/dist/storage/domains/memory/index.d.ts.map +1 -1
  10. package/dist/storage/domains/observability/v-next/ddl.d.ts.map +1 -1
  11. package/dist/storage/domains/observability/v-next/helpers.d.ts.map +1 -1
  12. package/dist/storage/domains/observability/v-next/index.d.ts +4 -1
  13. package/dist/storage/domains/observability/v-next/index.d.ts.map +1 -1
  14. package/dist/storage/domains/observability/v-next/preview-compat.d.ts +1 -0
  15. package/dist/storage/domains/observability/v-next/preview-compat.d.ts.map +1 -1
  16. package/dist/storage/domains/observability/v-next/signal-schema.d.ts +4 -0
  17. package/dist/storage/domains/observability/v-next/signal-schema.d.ts.map +1 -1
  18. package/dist/storage/domains/observability/v-next/span-query.d.ts +1 -0
  19. package/dist/storage/domains/observability/v-next/span-query.d.ts.map +1 -1
  20. package/dist/storage/domains/observability/v-next/thread-query.d.ts +49 -0
  21. package/dist/storage/domains/observability/v-next/thread-query.d.ts.map +1 -0
  22. package/dist/storage/domains/observability/v-next/trace-aggregate.d.ts.map +1 -1
  23. package/dist/storage/domains/observability/v-next/trace-query.d.ts +4 -0
  24. package/dist/storage/domains/observability/v-next/trace-query.d.ts.map +1 -1
  25. package/dist/storage/domains/skills/index.d.ts.map +1 -1
  26. package/package.json +9 -10
@@ -3,7 +3,7 @@ name: mastra-pg
3
3
  description: Documentation for @mastra/pg. Use when working with @mastra/pg APIs, configuration, or implementation.
4
4
  metadata:
5
5
  package: "@mastra/pg"
6
- version: "1.32.0-alpha.1"
6
+ version: "1.32.0-alpha.3"
7
7
  ---
8
8
 
9
9
  ## When to use
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.32.0-alpha.1",
2
+ "version": "1.32.0-alpha.3",
3
3
  "package": "@mastra/pg",
4
4
  "exports": {},
5
5
  "modules": {}
@@ -403,6 +403,8 @@ Memory uses a single embedder for both storing and recalling messages, so pick o
403
403
 
404
404
  Multilingual E5 produces 1024-dimensional vectors. Your vector index must be created with matching dimensions, and E5 vectors can't be mixed with vectors from another embedder in the same index.
405
405
 
406
+ FastEmbed models embed at most 512 tokens per input (roughly 2,000 to 2,500 characters of English text). Longer inputs are truncated, so text past the limit doesn't affect the embedding and can't be found by semantic recall. The AI SDK v3 FastEmbed models return a warning when this happens. The legacy v1 and v2 models don't. If you embed long documents with FastEmbed, chunk them to fit the window, for example with `maxSize: 1500` in `MDocument.chunk()`.
407
+
406
408
  ## PostgreSQL index optimization
407
409
 
408
410
  When using PostgreSQL as your vector store, you can optimize semantic recall performance by configuring the vector index. This is particularly important for large-scale deployments with thousands of messages.
package/dist/index.cjs CHANGED
@@ -16034,6 +16034,11 @@ const SPAN_EVENT_COLUMNS = [
16034
16034
  name: "endedAt",
16035
16035
  type: "timestamptz"
16036
16036
  },
16037
+ {
16038
+ name: "timeToFirstChunkMs",
16039
+ type: "double precision",
16040
+ nullable: true
16041
+ },
16037
16042
  {
16038
16043
  name: "tags",
16039
16044
  type: "text[]",
@@ -16768,6 +16773,11 @@ function additiveColumns(schema) {
16768
16773
  column,
16769
16774
  ddl: `ALTER TABLE ${qualifiedTable(schema, TABLE_SPAN_EVENTS)} ADD COLUMN IF NOT EXISTS "${column}" text`
16770
16775
  })),
16776
+ {
16777
+ table: TABLE_SPAN_EVENTS,
16778
+ column: "timeToFirstChunkMs",
16779
+ ddl: `ALTER TABLE ${qualifiedTable(schema, TABLE_SPAN_EVENTS)} ADD COLUMN IF NOT EXISTS "timeToFirstChunkMs" double precision`
16780
+ },
16771
16781
  {
16772
16782
  table: TABLE_FEEDBACK_EVENTS,
16773
16783
  column: "reviewStatus",
@@ -17489,6 +17499,7 @@ function spanRecordToRow(span) {
17489
17499
  isPending,
17490
17500
  startedAt: toIsoOrDate(span.startedAt),
17491
17501
  endedAt: toIsoOrDate(endedAt),
17502
+ timeToFirstChunkMs: span.timeToFirstChunkMs ?? null,
17492
17503
  tags: normalizeTags(span.tags),
17493
17504
  metadataSearch: buildMetadataSearch(metadata),
17494
17505
  metadataRaw: jsonField(metadata),
@@ -19198,6 +19209,9 @@ async function getScorePercentiles(client, schema, args) {
19198
19209
  //#endregion
19199
19210
  //#region src/storage/domains/observability/v-next/trace-query.ts
19200
19211
  const TRACE_STATUS_SQL = `CASE WHEN r."error" IS NOT NULL THEN 'error' ELSE 'success' END`;
19212
+ /** Core versions without the field reject it in strict query results, so it is only returned when core knows it. */
19213
+ const timeToFirstChunkSupported = "timeToFirstChunkMs" in _mastra_core_storage.TRACE_QUERY_FIELD_REGISTRY.trace;
19214
+ const timeToFirstChunkFeatures = timeToFirstChunkSupported ? ["trace-query-time-to-first-chunk"] : [];
19201
19215
  function durationMsSql(startedAt, endedAt) {
19202
19216
  return `EXTRACT(EPOCH FROM (${endedAt} - ${startedAt}))::numeric * 1000`;
19203
19217
  }
@@ -19212,8 +19226,11 @@ const TRACE_FIELDS = {
19212
19226
  startedAt: "r.\"startedAt\"",
19213
19227
  endedAt: "r.\"endedAt\"",
19214
19228
  durationMs: durationMsSql("r.\"startedAt\"", "r.\"endedAt\""),
19229
+ timeToFirstChunkMs: "r.\"timeToFirstChunkMs\"",
19215
19230
  entityName: "r.\"entityName\"",
19216
19231
  entityType: "r.\"entityType\"",
19232
+ name: "r.\"name\"",
19233
+ spanType: "r.\"spanType\"",
19217
19234
  environment: "r.\"environment\"",
19218
19235
  status: TRACE_STATUS_SQL,
19219
19236
  tags: "r.\"tags\""
@@ -19226,6 +19243,7 @@ const SPAN_FIELDS = {
19226
19243
  startedAt: "s.\"startedAt\"",
19227
19244
  endedAt: "s.\"endedAt\"",
19228
19245
  durationMs: "s.\"durationMs\"",
19246
+ timeToFirstChunkMs: "s.\"timeToFirstChunkMs\"",
19229
19247
  status: "s.\"status\"",
19230
19248
  error: "s.\"error\"",
19231
19249
  entityType: "s.\"entityType\"",
@@ -19295,6 +19313,7 @@ const TRACE_SELECT = `
19295
19313
  r."resourceId" AS "resourceId",
19296
19314
  r."startedAt" AS "startedAt",
19297
19315
  r."endedAt" AS "endedAt",
19316
+ r."timeToFirstChunkMs" AS "timeToFirstChunkMs",
19298
19317
  r."entityName" AS "entityName",
19299
19318
  r."entityType" AS "entityType",
19300
19319
  r."environment" AS "environment",
@@ -19418,11 +19437,11 @@ function compileScalarPredicate(predicate, registry, parameterOffset, allowMetad
19418
19437
  gte: ">="
19419
19438
  };
19420
19439
  if (predicate.operator === "eq") return {
19421
- sql: `${field} IS NOT DISTINCT FROM ${parameter}`,
19440
+ sql: `${field} IS NOT NULL AND ${field} = ${parameter}`,
19422
19441
  values: [...fieldValues, predicate.value]
19423
19442
  };
19424
19443
  if (predicate.operator === "ne") return {
19425
- sql: `${field} IS DISTINCT FROM ${parameter}`,
19444
+ sql: `${field} IS NULL OR ${field} <> ${parameter}`,
19426
19445
  values: [...fieldValues, predicate.value]
19427
19446
  };
19428
19447
  const operator = operators[predicate.operator];
@@ -19511,6 +19530,8 @@ const ROOT_PUSHDOWN_FIELDS = /* @__PURE__ */ new Set([
19511
19530
  "endedAt",
19512
19531
  "entityName",
19513
19532
  "entityType",
19533
+ "name",
19534
+ "spanType",
19514
19535
  "environment"
19515
19536
  ]);
19516
19537
  function isRootPushdownPredicate(predicate) {
@@ -19660,6 +19681,7 @@ function compilePostgresTraceScope(schema, selection, relationCollections, scope
19660
19681
  WHEN s."isPending" THEN NULL
19661
19682
  ELSE ${durationMsSql("s.\"startedAt\"", "s.\"endedAt\"")}
19662
19683
  END AS "durationMs",
19684
+ s."timeToFirstChunkMs",
19663
19685
  CASE WHEN s."error" IS NOT NULL THEN 'error' ELSE 'success' END AS "status",
19664
19686
  s."error",
19665
19687
  s."entityType",
@@ -20034,6 +20056,7 @@ function traceRowToResult(row) {
20034
20056
  resourceId: row.resourceId == null ? null : String(row.resourceId),
20035
20057
  startedAt: asIsoTimestamp$1(row.startedAt),
20036
20058
  endedAt: asIsoTimestamp$1(row.endedAt),
20059
+ ...timeToFirstChunkSupported && { timeToFirstChunkMs: row.timeToFirstChunkMs == null ? null : Number(row.timeToFirstChunkMs) },
20037
20060
  entityName: row.entityName == null ? null : String(row.entityName),
20038
20061
  entityType: row.entityType == null ? null : String(row.entityType),
20039
20062
  environment: row.environment == null ? null : String(row.environment),
@@ -20153,6 +20176,9 @@ async function queryThreads(client, schema, plan, timeoutMs) {
20153
20176
  function compileSpanQueryPredicate(predicate, offset) {
20154
20177
  return compileScalarPredicate(predicate, SPAN_QUERY_FIELDS, offset);
20155
20178
  }
20179
+ function compileThreadQueryPredicate(predicate, offset) {
20180
+ return compileScalarPredicate(predicate, Object.fromEntries(Object.keys(_mastra_core_storage.THREAD_QUERY_FIELD_REGISTRY).map((field) => [field, `s."${field}"`])), offset);
20181
+ }
20156
20182
  //#endregion
20157
20183
  //#region src/storage/domains/observability/v-next/span-query.ts
20158
20184
  const identityFields = [
@@ -20186,7 +20212,8 @@ const fields = [
20186
20212
  "serviceName",
20187
20213
  "experimentId",
20188
20214
  "environment",
20189
- "executionSource"
20215
+ "executionSource",
20216
+ "timeToFirstChunkMs"
20190
20217
  ];
20191
20218
  const identities = identityFields.map((field) => `"${field}"`).join(", ");
20192
20219
  const identityOrder = identityFields.map((field) => `"${field}" COLLATE "C" ASC NULLS LAST`).join(", ");
@@ -20302,8 +20329,9 @@ async function querySpans(client, schema, plan, timeoutMs) {
20302
20329
  return runWithPostgresTraceQueryTimeout(client, timeoutMs, async (tx) => {
20303
20330
  const query = compilePostgresSpanQuery(schema, plan);
20304
20331
  const rows = await tx.any(query.text, query.values);
20305
- const selected = rows.map(({ version: _version, startedAt, endedAt, ...row }) => ({
20332
+ const selected = rows.map(({ version: _version, startedAt, endedAt, timeToFirstChunkMs, ...row }) => ({
20306
20333
  ...row,
20334
+ ...timeToFirstChunkSupported && { timeToFirstChunkMs },
20307
20335
  startedAt: startedAt.toISOString(),
20308
20336
  endedAt: endedAt.toISOString()
20309
20337
  }));
@@ -20350,11 +20378,245 @@ async function querySpans(client, schema, plan, timeoutMs) {
20350
20378
  }, { repeatableRead: true });
20351
20379
  }
20352
20380
  //#endregion
20381
+ //#region src/storage/domains/observability/v-next/thread-query.ts
20382
+ const contextFields = [
20383
+ "resourceId",
20384
+ "userId",
20385
+ "entityType",
20386
+ "entityId",
20387
+ "entityName",
20388
+ "entityVersionId",
20389
+ "runId",
20390
+ "sessionId",
20391
+ "requestId",
20392
+ "serviceName",
20393
+ "experimentId",
20394
+ "environment",
20395
+ "executionSource"
20396
+ ];
20397
+ function predicateFields(predicate, fields = /* @__PURE__ */ new Set()) {
20398
+ if (!predicate) return fields;
20399
+ if (predicate.type === "boolean") for (const arg of predicate.args) predicateFields(arg, fields);
20400
+ else if (predicate.type === "not") predicateFields(predicate.arg, fields);
20401
+ else fields.add(predicate.field);
20402
+ return fields;
20403
+ }
20404
+ function canUseThreadCandidates(probe) {
20405
+ return Number(probe.threadCount) <= 16384 && Number(probe.traceCount) <= 1e5 && Number(probe.threadBytes) + Number(probe.traceBytes) <= 4 * 1024 * 1024;
20406
+ }
20407
+ function compilePostgresThreadRowsQuery(schema, plan, strategy = "full") {
20408
+ const values = [plan.timeRange.from, plan.timeRange.to];
20409
+ const scope = [];
20410
+ if (plan.scope) {
20411
+ values.push(plan.scope.organizationId);
20412
+ scope.push(`"organizationId" = $${values.length}`);
20413
+ if (plan.scope.resourceId !== void 0) {
20414
+ values.push(plan.scope.resourceId);
20415
+ scope.push(`"resourceId" = $${values.length}`);
20416
+ }
20417
+ }
20418
+ const table = qualifiedTable(schema, TABLE_SPAN_EVENTS);
20419
+ const timestamp = plan.timeRange.mode === "threads" ? plan.timeRange.field : "startedAt";
20420
+ const identity = (field) => `"organizationId" IS NULL, COALESCE("organizationId", ''), "${field}"`;
20421
+ const threadFilter = `(${identity("threadId")}) IN (SELECT ${identity("threadId")} FROM candidate_threads)`;
20422
+ const scopedRoots = `scoped_roots AS NOT MATERIALIZED (
20423
+ SELECT * FROM ${table} WHERE "parentSpanId" IS NULL ${scope.length ? `AND ${scope.join(" AND ")}` : ""}
20424
+ )`;
20425
+ const candidates = `candidate_threads AS MATERIALIZED (
20426
+ SELECT DISTINCT "organizationId", "threadId" FROM scoped_roots
20427
+ WHERE "threadId" IS NOT NULL AND "${timestamp}" >= $1 AND "${timestamp}" < $2
20428
+ ${strategy === "probe" ? "LIMIT 16385" : ""}
20429
+ ), candidate_traces AS MATERIALIZED (
20430
+ SELECT DISTINCT "organizationId", "traceId" FROM scoped_roots WHERE ${threadFilter}
20431
+ ${strategy === "probe" ? "LIMIT 100001" : ""}
20432
+ )`;
20433
+ if (strategy === "probe") return {
20434
+ text: `WITH ${scopedRoots}, ${candidates}
20435
+ SELECT (SELECT count(*) FROM candidate_threads) AS "threadCount",
20436
+ (SELECT COALESCE(sum(octet_length(COALESCE("organizationId", '')) + octet_length("threadId")), 0) FROM candidate_threads) AS "threadBytes",
20437
+ count(*) AS "traceCount", COALESCE(sum(octet_length(COALESCE("organizationId", '')) + octet_length("traceId")), 0) AS "traceBytes"
20438
+ FROM candidate_traces`,
20439
+ values
20440
+ };
20441
+ const predicate = plan.where ? compileThreadQueryPredicate(plan.where, values.length + 1) : {
20442
+ sql: "TRUE",
20443
+ values: []
20444
+ };
20445
+ values.push(...predicate.values);
20446
+ let after = "TRUE";
20447
+ if (plan.cursor) {
20448
+ values.push(plan.cursor.sortValue);
20449
+ const stamp = `$${values.length}::timestamptz`;
20450
+ const field = `s."${plan.orderBy.field}"`;
20451
+ const terms = [`${field} ${plan.orderBy.direction === "asc" ? ">" : "<"} ${stamp}`];
20452
+ const equal = [`${field} = ${stamp}`];
20453
+ for (const key of ["organizationId", "threadId"]) {
20454
+ const ref = `s."${key}" COLLATE "C"`;
20455
+ if (plan.cursor[key] === null) equal.push(`${ref} IS NULL`);
20456
+ else {
20457
+ values.push(plan.cursor[key]);
20458
+ const value = `$${values.length}::text COLLATE "C"`;
20459
+ terms.push(`(${equal.join(" AND ")} AND (${ref} > ${value} OR ${ref} IS NULL))`);
20460
+ equal.push(`${ref} = ${value}`);
20461
+ }
20462
+ }
20463
+ after = `(${terms.join(" OR ")})`;
20464
+ }
20465
+ const qualify = plan.timeRange.mode === "traces" ? "s.\"inWindow\"" : `s."${plan.timeRange.field}" >= $1 AND s."${plan.timeRange.field}" < $2`;
20466
+ const filtered = predicateFields(plan.where);
20467
+ const firstContext = [...contextFields, "timeToFirstChunkMs"].filter((field) => field === "resourceId" || filtered.has(field));
20468
+ const lastFields = [
20469
+ "traceId",
20470
+ "spanId",
20471
+ "cursorId",
20472
+ "resourceId",
20473
+ "endedAt",
20474
+ ...filtered.has("status") ? ["status"] : []
20475
+ ];
20476
+ values.push(plan.limit + 1);
20477
+ return {
20478
+ text: `WITH ${scopedRoots}, ${strategy === "candidates" ? `${candidates}, ` : ""}current_roots AS (
20479
+ SELECT DISTINCT ON ("organizationId", "traceId")
20480
+ "organizationId", "threadId", "traceId", "spanId", "cursorId", "startedAt", "endedAt", "isPending",
20481
+ ${firstContext.map((field) => field === "timeToFirstChunkMs" ? `CASE WHEN "spanType" = 'agent_run' THEN "timeToFirstChunkMs" END AS "timeToFirstChunkMs"` : `"${field}"`).join(", ")}
20482
+ ${filtered.has("status") ? ", CASE WHEN error IS NULL THEN 'success' ELSE 'error' END AS status" : ""}
20483
+ FROM scoped_roots ${strategy === "candidates" ? `WHERE (${identity("traceId")}) IN (SELECT ${identity("traceId")} FROM candidate_traces)` : ""}
20484
+ ORDER BY "organizationId", "traceId", "startedAt" ASC, "spanId" COLLATE "C", "cursorId" DESC
20485
+ ), ranked AS (
20486
+ SELECT *, row_number() OVER first_order AS rank,
20487
+ max("endedAt") OVER population AS "lastActivity", count(*) OVER population AS "traceCount",
20488
+ bool_or("startedAt" >= $1 AND "startedAt" < $2) OVER population AS "inWindow",
20489
+ ${lastFields.map((field) => `first_value("${field}") OVER last_order AS "last${field[0].toUpperCase()}${field.slice(1)}"`).join(", ")}
20490
+ FROM current_roots WHERE NOT "isPending" AND "endedAt" IS NOT NULL AND "threadId" IS NOT NULL ${strategy === "candidates" ? `AND ${threadFilter}` : ""}
20491
+ WINDOW population AS (PARTITION BY "organizationId", "threadId"),
20492
+ first_order AS (PARTITION BY "organizationId", "threadId" ORDER BY "startedAt", "traceId" COLLATE "C"),
20493
+ last_order AS (PARTITION BY "organizationId", "threadId" ORDER BY "startedAt" DESC, "traceId" COLLATE "C" DESC)
20494
+ ), rows AS (
20495
+ SELECT "organizationId", "threadId", "traceId" AS "firstTraceId", "spanId" AS "firstSpanId", "cursorId" AS "firstCursorId",
20496
+ "endedAt" AS "firstEndedAt", "startedAt", "lastActivity" AS "endedAt", "traceCount", "inWindow",
20497
+ ${firstContext.map((field) => `"${field}"`).join(", ")},
20498
+ ${lastFields.map((field) => `"last${field[0].toUpperCase()}${field.slice(1)}"${field === "status" ? " AS status" : ""}`).join(", ")}
20499
+ FROM ranked WHERE rank = 1
20500
+ ) SELECT * FROM rows s WHERE ${qualify} AND (${predicate.sql}) AND ${after}
20501
+ ORDER BY s."${plan.orderBy.field}" ${plan.orderBy.direction === "asc" ? "ASC" : "DESC"},
20502
+ s."organizationId" COLLATE "C" ASC NULLS LAST, s."threadId" COLLATE "C" ASC LIMIT $${values.length}`,
20503
+ values
20504
+ };
20505
+ }
20506
+ async function queryThreadRows(client, schema, plan, timeoutMs, strategy = "auto") {
20507
+ const deadline = performance.now() + _mastra_core_storage.resolveTraceQueryTimeoutMs(timeoutMs);
20508
+ const run = async (text, values) => {
20509
+ const remaining = Math.floor(deadline - performance.now());
20510
+ if (remaining <= 0) throw new _mastra_core_storage.TraceQueryExecutionError();
20511
+ return await runWithPostgresTraceQueryTimeout(client, remaining, (tx) => tx.any(text, values));
20512
+ };
20513
+ if (strategy === "auto") {
20514
+ const probeQuery = compilePostgresThreadRowsQuery(schema, plan, "probe");
20515
+ const [probe] = await run(probeQuery.text, probeQuery.values);
20516
+ if (Number(probe.threadCount) === 0) return {
20517
+ threads: [],
20518
+ page: { next: null }
20519
+ };
20520
+ strategy = canUseThreadCandidates(probe) ? "candidates" : "full";
20521
+ }
20522
+ const compiled = compilePostgresThreadRowsQuery(schema, plan, strategy);
20523
+ const selected = await run(compiled.text, compiled.values);
20524
+ const page = selected.slice(0, plan.limit);
20525
+ if (!page.length) return {
20526
+ threads: [],
20527
+ page: { next: null }
20528
+ };
20529
+ const table = qualifiedTable(schema, TABLE_SPAN_EVENTS);
20530
+ const values = [];
20531
+ const keys = page.flatMap((row) => ["first", "last"].map((which) => {
20532
+ const entries = {
20533
+ organizationId: row.organizationId,
20534
+ resourceId: which === "first" ? row.resourceId : row.lastResourceId,
20535
+ traceId: row[`${which}TraceId`],
20536
+ spanId: row[`${which}SpanId`],
20537
+ cursorId: row[`${which}CursorId`],
20538
+ endedAt: row[`${which}EndedAt`]
20539
+ };
20540
+ return `(${Object.entries(entries).map(([field, value]) => {
20541
+ values.push(value);
20542
+ return `"${field}" ${field === "organizationId" || field === "resourceId" ? "IS NOT DISTINCT FROM" : "="} $${values.length}`;
20543
+ }).join(" AND ")})`;
20544
+ }));
20545
+ const payloads = await run(`SELECT "organizationId", "traceId", "cursorId", "spanType", ${contextFields.map((field) => `"${field}"`).join(", ")},
20546
+ CASE WHEN "spanType" = 'agent_run' THEN "timeToFirstChunkMs" END AS "timeToFirstChunkMs",
20547
+ CASE WHEN error IS NULL THEN 'success' ELSE 'error' END AS status,
20548
+ ${[
20549
+ "input",
20550
+ "output",
20551
+ "error"
20552
+ ].map((field) => `"${field}Preview", CASE WHEN ${missingPreviewSql(`"${field}Preview"`)} THEN "${field}" END AS "${field}"`).join(", ")}
20553
+ FROM ${table} WHERE ${keys.join(" OR ")}`, values);
20554
+ const key = (org, trace, version) => JSON.stringify([
20555
+ org,
20556
+ trace,
20557
+ String(version)
20558
+ ]);
20559
+ const byRoot = new Map(payloads.map((row) => [key(row.organizationId, row.traceId, row.cursorId), row]));
20560
+ const agents = page.filter((row) => byRoot.get(key(row.organizationId, row.firstTraceId, row.firstCursorId))?.spanType === "agent_run");
20561
+ const models = /* @__PURE__ */ new Map();
20562
+ if (agents.length) {
20563
+ const modelValues = [];
20564
+ const rows = await run(`SELECT DISTINCT ON ("organizationId", "traceId") "organizationId", "traceId",
20565
+ CASE WHEN jsonb_typeof(attributes -> 'model') = 'string' THEN attributes ->> 'model' END AS model,
20566
+ CASE WHEN jsonb_typeof(attributes -> 'provider') = 'string' THEN attributes ->> 'provider' END AS provider
20567
+ FROM ${table} WHERE "spanType" = 'model_generation' AND NOT "isPending" AND (${agents.map((row) => `(${Object.entries({
20568
+ organizationId: row.organizationId,
20569
+ resourceId: row.resourceId,
20570
+ traceId: row.firstTraceId,
20571
+ parentSpanId: row.firstSpanId
20572
+ }).map(([field, value]) => {
20573
+ modelValues.push(value);
20574
+ return `"${field}" ${field === "organizationId" || field === "resourceId" ? "IS NOT DISTINCT FROM" : "="} $${modelValues.length}`;
20575
+ }).join(" AND ")})`).join(" OR ")})
20576
+ ORDER BY "organizationId", "traceId", "startedAt", "spanId" COLLATE "C", "cursorId" DESC`, modelValues);
20577
+ for (const row of rows) models.set(JSON.stringify([row.organizationId, row.traceId]), row);
20578
+ }
20579
+ const threads = page.flatMap((row) => {
20580
+ const first = byRoot.get(key(row.organizationId, row.firstTraceId, row.firstCursorId));
20581
+ const last = byRoot.get(key(row.organizationId, row.lastTraceId, row.lastCursorId));
20582
+ if (!first || !last) return [];
20583
+ const model = models.get(JSON.stringify([row.organizationId, row.firstTraceId]));
20584
+ return [_mastra_core_storage.threadQueryRowSchema.parse({
20585
+ organizationId: row.organizationId,
20586
+ threadId: row.threadId,
20587
+ firstTraceId: row.firstTraceId,
20588
+ lastTraceId: row.lastTraceId,
20589
+ ...Object.fromEntries(contextFields.map((field) => [field, first[field]])),
20590
+ status: last.status,
20591
+ startedAt: row.startedAt.toISOString(),
20592
+ endedAt: row.endedAt.toISOString(),
20593
+ traceCount: Number(row.traceCount),
20594
+ timeToFirstChunkMs: first.timeToFirstChunkMs,
20595
+ model: model?.model ?? null,
20596
+ provider: model?.provider ?? null,
20597
+ inputPreview: readPreview(first.inputPreview, first.input),
20598
+ outputPreview: readPreview(last.outputPreview, last.output),
20599
+ errorPreview: readPreview(last.errorPreview, last.error)
20600
+ })];
20601
+ });
20602
+ const last = page.at(-1);
20603
+ return {
20604
+ threads,
20605
+ page: { next: selected.length > plan.limit ? _mastra_core_storage.encodeThreadQueryCursor(plan, {
20606
+ organizationId: last.organizationId,
20607
+ threadId: last.threadId,
20608
+ sortValue: last[plan.orderBy.field].toISOString()
20609
+ }) : null }
20610
+ };
20611
+ }
20612
+ //#endregion
20353
20613
  //#region src/storage/domains/observability/v-next/trace-aggregate.ts
20354
20614
  /** Trace-root column expressions (alias `r`) for each groupable dimension; same values `where` sees. */
20355
20615
  const DIMENSION_SQL = {
20356
20616
  entityType: "r.\"entityType\"",
20357
20617
  entityName: "r.\"entityName\"",
20618
+ name: "r.\"name\"",
20619
+ spanType: "r.\"spanType\"",
20358
20620
  environment: "r.\"environment\"",
20359
20621
  status: TRACE_STATUS_SQL,
20360
20622
  serviceName: "r.\"serviceName\"",
@@ -21581,12 +21843,15 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
21581
21843
  "trace-query",
21582
21844
  "trace-aggregate",
21583
21845
  ...spanQueryFeatures,
21846
+ ...typeof _mastra_core_storage.planThreadRowsQuery === "function" ? ["thread-query-rows"] : [],
21584
21847
  "trace-query-root-duration",
21585
21848
  "trace-query-discovery",
21586
21849
  "thread-query",
21587
21850
  "trace-query-tenant-scope",
21588
21851
  "feedback",
21589
- "trace-query-context-ids"
21852
+ "trace-query-context-ids",
21853
+ ...timeToFirstChunkFeatures,
21854
+ "trace-query-root-span-fields"
21590
21855
  ];
21591
21856
  return [
21592
21857
  "metrics",
@@ -21601,12 +21866,15 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
21601
21866
  "trace-query",
21602
21867
  "trace-aggregate",
21603
21868
  ...spanQueryFeatures,
21869
+ ...typeof _mastra_core_storage.planThreadRowsQuery === "function" ? ["thread-query-rows"] : [],
21604
21870
  "trace-query-root-duration",
21605
21871
  "trace-query-discovery",
21606
21872
  "thread-query",
21607
21873
  "trace-query-tenant-scope",
21608
21874
  "feedback",
21609
- "trace-query-context-ids"
21875
+ "trace-query-context-ids",
21876
+ ...timeToFirstChunkFeatures,
21877
+ "trace-query-root-span-fields"
21610
21878
  ];
21611
21879
  }
21612
21880
  async #run(op, fn, details) {
@@ -21668,7 +21936,7 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
21668
21936
  return this.#run("GET_TRACE_QUERY_VALUES", () => getTraceQueryValues(this.#readClient, this.#schema, plan, this.#traceQueryTimeoutMs));
21669
21937
  }
21670
21938
  async queryThreads(plan) {
21671
- return this.#run("QUERY_THREADS", () => queryThreads(this.#readClient, this.#schema, plan, this.#traceQueryTimeoutMs));
21939
+ return this.#run("QUERY_THREADS", () => plan.result === "threadRows" ? queryThreadRows(this.#readClient, this.#schema, plan, this.#traceQueryTimeoutMs) : queryThreads(this.#readClient, this.#schema, plan, this.#traceQueryTimeoutMs));
21672
21940
  }
21673
21941
  async listBranches(args) {
21674
21942
  return this.#run("LIST_BRANCHES", () => listBranches(this.#readClient, this.#schema, args));
@@ -24100,7 +24368,7 @@ var SkillsPG = class SkillsPG extends _mastra_core_storage.SkillsStorage {
24100
24368
  }
24101
24369
  }
24102
24370
  async list(args) {
24103
- const { page = 0, perPage: perPageInput, orderBy, authorId, visibility, status, entityIds, pinFavoritedFor, favoritedOnly } = args || {};
24371
+ const { page = 0, perPage: perPageInput, orderBy, authorId, visibility, status, entityIds, pinFavoritedFor, favoritedOnly, metadata } = args || {};
24104
24372
  const { field, direction } = this.parseOrderBy(orderBy);
24105
24373
  if (page < 0) throw new _mastra_core_error.MastraError({
24106
24374
  id: (0, _mastra_core_storage.createStorageErrorId)("PG", "LIST_SKILLS", "INVALID_PAGE"),
@@ -24145,6 +24413,16 @@ var SkillsPG = class SkillsPG extends _mastra_core_storage.SkillsStorage {
24145
24413
  conditions.push(`s.visibility = $${paramIdx++}`);
24146
24414
  queryParams.push(visibility);
24147
24415
  }
24416
+ if (metadata && Object.keys(metadata).length > 0) {
24417
+ const versionsTable = getTableName$5({
24418
+ indexName: _mastra_core_storage.TABLE_SKILL_VERSIONS,
24419
+ schemaName: getSchemaName$5(this.#schema)
24420
+ });
24421
+ const latestVersionId = `(SELECT lv.id FROM ${versionsTable} lv WHERE lv."skillId" = s.id ORDER BY lv."versionNumber" DESC LIMIT 1)`;
24422
+ const resolvedVersionId = status === "draft" ? latestVersionId : `COALESCE(s."activeVersionId", ${latestVersionId})`;
24423
+ conditions.push(`EXISTS (SELECT 1 FROM ${versionsTable} mv WHERE mv."skillId" = s.id AND mv.id = ${resolvedVersionId} AND mv.metadata @> $${paramIdx++}::jsonb)`);
24424
+ queryParams.push(toPgJson(metadata));
24425
+ }
24148
24426
  if (entityIds && entityIds.length > 0) {
24149
24427
  const placeholders = entityIds.map(() => `$${paramIdx++}`).join(", ");
24150
24428
  conditions.push(`s.id IN (${placeholders})`);