@mastra/pg 1.26.0-alpha.2 → 1.26.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/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/reference-memory-memory-class.md +1 -1
- package/dist/docs/references/reference-rag-vector-databases.md +34 -0
- package/dist/index.cjs +232 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +232 -37
- package/dist/index.js.map +1 -1
- package/dist/storage/db/constraint-utils.d.ts +11 -1
- package/dist/storage/db/constraint-utils.d.ts.map +1 -1
- package/dist/storage/domains/memory/index.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/ddl.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/scores.d.ts +1 -0
- package/dist/storage/domains/observability/v-next/scores.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/trace-query.d.ts +4 -2
- package/dist/storage/domains/observability/v-next/trace-query.d.ts.map +1 -1
- package/dist/storage/domains/workflows/index.d.ts +32 -1
- package/dist/storage/domains/workflows/index.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/docs/SKILL.md
CHANGED
|
@@ -51,7 +51,7 @@ export const agent = new Agent({
|
|
|
51
51
|
|
|
52
52
|
**options.observationalMemory** (`boolean | ObservationalMemoryOptions`): Enable Observational Memory for long-context agentic memory. Set to true for defaults, or pass a config object to customize token budgets, models, and scope. See Observational Memory reference for configuration details.
|
|
53
53
|
|
|
54
|
-
**options.generateTitle** (`boolean | { model
|
|
54
|
+
**options.generateTitle** (`boolean | { model?: DynamicArgument<MastraModelConfig>; instructions?: DynamicArgument<string>; minMessages?: number; emitEvent?: boolean }`): Controls automatic thread title generation from the conversation transcript. Accepts a boolean or an object with a custom model (any MastraModelConfig: a model instance, a "provider/model" ID, or an OpenAI-compatible config; defaults to the agent's own model), custom instructions, a minimum message count, and emitEvent. With emitEvent: true the run's stream waits for the title and emits it as a transient data-thread-title chunk before finish (durable and evented agents persist the title but don't emit the chunk yet).
|
|
55
55
|
|
|
56
56
|
## Returns
|
|
57
57
|
|
|
@@ -345,6 +345,29 @@ await store.upsert({
|
|
|
345
345
|
|
|
346
346
|
For detailed setup instructions and best practices, see the [official Elasticsearch documentation](https://www.elastic.co/docs/solutions/search/get-started).
|
|
347
347
|
|
|
348
|
+
**Azure AI Search**:
|
|
349
|
+
|
|
350
|
+
```ts
|
|
351
|
+
import { AzureAISearchVector } from '@mastra/azure-ai-search'
|
|
352
|
+
|
|
353
|
+
const store = new AzureAISearchVector({
|
|
354
|
+
id: 'azure-search-vectors',
|
|
355
|
+
endpoint: process.env.AZURE_AI_SEARCH_ENDPOINT!,
|
|
356
|
+
credential: process.env.AZURE_AI_SEARCH_CREDENTIAL!,
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
await store.createIndex({
|
|
360
|
+
indexName: 'my-collection',
|
|
361
|
+
dimension: 1536,
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
await store.upsert({
|
|
365
|
+
indexName: 'my-collection',
|
|
366
|
+
vectors: embeddings,
|
|
367
|
+
metadata: chunks.map(chunk => ({ text: chunk.text })),
|
|
368
|
+
})
|
|
369
|
+
```
|
|
370
|
+
|
|
348
371
|
**Couchbase**:
|
|
349
372
|
|
|
350
373
|
```ts
|
|
@@ -626,6 +649,17 @@ Index names must:
|
|
|
626
649
|
- Example: `myindex-` isn't valid (ends with hyphen)
|
|
627
650
|
- Example: `MyIndex` isn't valid (contains uppercase letters)
|
|
628
651
|
|
|
652
|
+
**Azure AI Search**:
|
|
653
|
+
|
|
654
|
+
Index names must:
|
|
655
|
+
|
|
656
|
+
- Use only lowercase letters, numbers, dashes (`-`), and underscore (`_`) characters
|
|
657
|
+
- Not start or end with a dash
|
|
658
|
+
- Be between 2 and 128 characters long
|
|
659
|
+
- Example: `my-index-123` and `my_index` are valid
|
|
660
|
+
- Example: `MyIndex` isn't valid (contains uppercase letters)
|
|
661
|
+
- Example: `my-index-` isn't valid (ends with a dash)
|
|
662
|
+
|
|
629
663
|
### Upserting Embeddings
|
|
630
664
|
|
|
631
665
|
After creating an index, you can store embeddings along with their basic metadata:
|
package/dist/index.cjs
CHANGED
|
@@ -2360,6 +2360,8 @@ var RoutingDbClient = class {
|
|
|
2360
2360
|
return this.active.tx(callback);
|
|
2361
2361
|
}
|
|
2362
2362
|
};
|
|
2363
|
+
/** Bytes reserved for the `_xxxxxxxx` collision suffix when hashWhenTruncated applies. */
|
|
2364
|
+
const TRUNCATION_HASH_SUFFIX_LENGTH = 9;
|
|
2363
2365
|
function truncateIdentifier(value, maxLength = 63) {
|
|
2364
2366
|
if (maxLength <= 0) return "";
|
|
2365
2367
|
if (Buffer.byteLength(value, "utf-8") <= maxLength) return value;
|
|
@@ -2380,9 +2382,25 @@ function truncateIdentifier(value, maxLength = 63) {
|
|
|
2380
2382
|
* in system catalogs (pg_constraint.conname, pg_indexes.indexname, etc.).
|
|
2381
2383
|
* Without this normalisation, runtime lookups that compare a mixed-case name
|
|
2382
2384
|
* against the catalog would silently fail.
|
|
2385
|
+
*
|
|
2386
|
+
* With `hashWhenTruncated`, a name that exceeds the limit is truncated further
|
|
2387
|
+
* to make room for `_` + 8 hex chars of the full name's sha256. Plain
|
|
2388
|
+
* truncation cuts the tail, so two names sharing a long `<schema>_<prefix>`
|
|
2389
|
+
* collapse to the same identifier and `CREATE INDEX IF NOT EXISTS` (which
|
|
2390
|
+
* matches by name only) silently skips the second one. The suffix is
|
|
2391
|
+
* deterministic, so creation, warm-init snapshot checks, and DDL export all
|
|
2392
|
+
* agree on the same name. Opt-in because renaming already-released constraint
|
|
2393
|
+
* names would orphan the existing objects in deployed catalogs.
|
|
2383
2394
|
*/
|
|
2384
|
-
function buildConstraintName({ baseName, schemaName, maxLength = 63 }) {
|
|
2385
|
-
|
|
2395
|
+
function buildConstraintName({ baseName, schemaName, maxLength = 63, hashWhenTruncated = false }) {
|
|
2396
|
+
const fullName = `${schemaName ? `${schemaName}_` : ""}${baseName}`.toLowerCase();
|
|
2397
|
+
if (hashWhenTruncated && Buffer.byteLength(fullName, "utf-8") > maxLength) {
|
|
2398
|
+
const suffixLength = Math.min(TRUNCATION_HASH_SUFFIX_LENGTH, maxLength);
|
|
2399
|
+
if (suffixLength < 2) return truncateIdentifier(fullName, maxLength);
|
|
2400
|
+
const hash = (0, crypto$1.createHash)("sha256").update(fullName).digest("hex").slice(0, suffixLength - 1);
|
|
2401
|
+
return `${truncateIdentifier(fullName, maxLength - suffixLength)}_${hash}`;
|
|
2402
|
+
}
|
|
2403
|
+
return truncateIdentifier(fullName, maxLength);
|
|
2386
2404
|
}
|
|
2387
2405
|
//#endregion
|
|
2388
2406
|
//#region src/storage/db/pg-errors.ts
|
|
@@ -10782,7 +10800,7 @@ var MemoryPG = class MemoryPG extends _mastra_core_storage.MemoryStorage {
|
|
|
10782
10800
|
hasMore: false
|
|
10783
10801
|
};
|
|
10784
10802
|
const limitValue = perPageInput === false ? total : perPage;
|
|
10785
|
-
const dataQuery = `SELECT id, "resourceId", title, metadata, "createdAt", "createdAtZ", "updatedAt", "updatedAtZ" ${baseQuery} ORDER BY COALESCE("${field}Z", "${field}") ${direction} LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`;
|
|
10803
|
+
const dataQuery = `SELECT id, "resourceId", title, metadata, "createdAt", "createdAtZ", "updatedAt", "updatedAtZ" ${baseQuery} ORDER BY COALESCE("${field}Z", "${field}") ${direction}, "id" ${direction} LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`;
|
|
10786
10804
|
return {
|
|
10787
10805
|
threads: (await this.#db.readClient.manyOrNone(dataQuery, [
|
|
10788
10806
|
...queryParams,
|
|
@@ -10975,10 +10993,11 @@ var MemoryPG = class MemoryPG extends _mastra_core_storage.MemoryStorage {
|
|
|
10975
10993
|
return messages.sort((a, b) => {
|
|
10976
10994
|
const aValue = field === "createdAt" ? new Date(a.createdAt).getTime() : a[field];
|
|
10977
10995
|
const bValue = field === "createdAt" ? new Date(b.createdAt).getTime() : b[field];
|
|
10978
|
-
|
|
10996
|
+
const idOrder = direction === "ASC" ? a.id.localeCompare(b.id) : b.id.localeCompare(a.id);
|
|
10997
|
+
if (aValue == null && bValue == null) return idOrder;
|
|
10979
10998
|
if (aValue == null) return 1;
|
|
10980
10999
|
if (bValue == null) return -1;
|
|
10981
|
-
if (aValue === bValue) return
|
|
11000
|
+
if (aValue === bValue) return idOrder;
|
|
10982
11001
|
if (typeof aValue === "number" && typeof bValue === "number") return direction === "ASC" ? aValue - bValue : bValue - aValue;
|
|
10983
11002
|
return direction === "ASC" ? String(aValue).localeCompare(String(bValue)) : String(bValue).localeCompare(String(aValue));
|
|
10984
11003
|
});
|
|
@@ -11181,7 +11200,7 @@ var MemoryPG = class MemoryPG extends _mastra_core_storage.MemoryStorage {
|
|
|
11181
11200
|
const metadataFilter = (0, _mastra_core_storage.validateStorageMetadataFilter)(filter?.metadata);
|
|
11182
11201
|
try {
|
|
11183
11202
|
const { field, direction } = this.parseOrderBy(orderBy, "ASC");
|
|
11184
|
-
const orderByStatement = `ORDER BY "${field}" ${direction}`;
|
|
11203
|
+
const orderByStatement = `ORDER BY "${field}" ${direction}, "id" ${direction}`;
|
|
11185
11204
|
const selectStatement = `SELECT id, content, role, type, "createdAt", "createdAtZ", thread_id AS "threadId", "resourceId"`;
|
|
11186
11205
|
const tableName = getTableName$3({
|
|
11187
11206
|
indexName: _mastra_core_storage.TABLE_MESSAGES,
|
|
@@ -11336,7 +11355,7 @@ var MemoryPG = class MemoryPG extends _mastra_core_storage.MemoryStorage {
|
|
|
11336
11355
|
const metadataFilter = (0, _mastra_core_storage.validateStorageMetadataFilter)(filter?.metadata);
|
|
11337
11356
|
try {
|
|
11338
11357
|
const { field, direction } = this.parseOrderBy(orderBy, "ASC");
|
|
11339
|
-
const orderByStatement = `ORDER BY "${field}" ${direction}`;
|
|
11358
|
+
const orderByStatement = `ORDER BY "${field}" ${direction}, "id" ${direction}`;
|
|
11340
11359
|
const selectStatement = `SELECT id, content, role, type, "createdAt", "createdAtZ", thread_id AS "threadId", "resourceId"`;
|
|
11341
11360
|
const tableName = getTableName$3({
|
|
11342
11361
|
indexName: _mastra_core_storage.TABLE_MESSAGES,
|
|
@@ -14831,6 +14850,11 @@ function tableIndexes() {
|
|
|
14831
14850
|
columns: "(\"tags\")",
|
|
14832
14851
|
using: "gin"
|
|
14833
14852
|
},
|
|
14853
|
+
{
|
|
14854
|
+
name: "mastra_score_events_scoreid_cursor_idx",
|
|
14855
|
+
table: TABLE_SCORE_EVENTS,
|
|
14856
|
+
columns: "(\"scoreId\", \"cursorId\" DESC)"
|
|
14857
|
+
},
|
|
14834
14858
|
{
|
|
14835
14859
|
name: "mastra_score_events_cursor_idx",
|
|
14836
14860
|
table: TABLE_SCORE_EVENTS,
|
|
@@ -16963,13 +16987,32 @@ function pushScoreIdentity(acc, scorerId, scoreSource) {
|
|
|
16963
16987
|
acc.params.push(scoreSource);
|
|
16964
16988
|
}
|
|
16965
16989
|
}
|
|
16990
|
+
function scoreRewriteConflict(row) {
|
|
16991
|
+
return `ON CONFLICT ("scoreId", "timestamp") DO UPDATE SET ${[
|
|
16992
|
+
...Object.keys(row).filter((column) => column !== "scoreId" && column !== "timestamp").map((column) => `"${column}" = EXCLUDED."${column}"`),
|
|
16993
|
+
"\"cursorId\" = EXCLUDED.\"cursorId\"",
|
|
16994
|
+
"\"xactId\" = EXCLUDED.\"xactId\""
|
|
16995
|
+
].join(", ")}`;
|
|
16996
|
+
}
|
|
16997
|
+
function collapseExactScoreConflicts(rows) {
|
|
16998
|
+
const records = /* @__PURE__ */ new Map();
|
|
16999
|
+
for (const row of rows) {
|
|
17000
|
+
const timestamp = new Date(row.timestamp).toISOString();
|
|
17001
|
+
const key = `${String(row.scoreId)}\u0000${timestamp}`;
|
|
17002
|
+
records.delete(key);
|
|
17003
|
+
records.set(key, row);
|
|
17004
|
+
}
|
|
17005
|
+
return [...records.values()];
|
|
17006
|
+
}
|
|
16966
17007
|
async function createScore(client, schema, args) {
|
|
16967
|
-
const
|
|
17008
|
+
const row = scoreRecordToRow(args.score);
|
|
17009
|
+
const insert = buildInsert(schema, TABLE_SCORE_EVENTS, [row], scoreRewriteConflict(row));
|
|
16968
17010
|
if (insert) await client.query(insert.text, insert.values);
|
|
16969
17011
|
}
|
|
16970
17012
|
async function batchCreateScores(client, schema, args) {
|
|
16971
17013
|
if (args.scores.length === 0) return;
|
|
16972
|
-
const
|
|
17014
|
+
const rows = collapseExactScoreConflicts(args.scores.map(scoreRecordToRow));
|
|
17015
|
+
const insert = buildInsert(schema, TABLE_SCORE_EVENTS, rows, scoreRewriteConflict(rows[0]));
|
|
16973
17016
|
if (insert) await client.query(insert.text, insert.values);
|
|
16974
17017
|
}
|
|
16975
17018
|
/**
|
|
@@ -16992,6 +17035,16 @@ async function deleteScores(client, schema, args) {
|
|
|
16992
17035
|
}
|
|
16993
17036
|
await client.query(`DELETE FROM ${table} WHERE ${conditions.join(" AND ")}`, values);
|
|
16994
17037
|
}
|
|
17038
|
+
function latestScorePredicate(table, alias = "s") {
|
|
17039
|
+
return `NOT EXISTS (
|
|
17040
|
+
SELECT 1 FROM ${table} newer
|
|
17041
|
+
WHERE newer."scoreId" = ${alias}."scoreId"
|
|
17042
|
+
AND newer."cursorId" > ${alias}."cursorId"
|
|
17043
|
+
)`;
|
|
17044
|
+
}
|
|
17045
|
+
function applyLatestScorePredicate(acc, table) {
|
|
17046
|
+
acc.conditions.push(latestScorePredicate(table));
|
|
17047
|
+
}
|
|
16995
17048
|
async function listScores(client, schema, args) {
|
|
16996
17049
|
const { mode, filters, pagination, orderBy, after, limit } = _mastra_core_storage.listScoresArgsSchema.parse(args);
|
|
16997
17050
|
const table = qualifiedTable(schema, TABLE_SCORE_EVENTS);
|
|
@@ -17002,28 +17055,50 @@ async function listScores(client, schema, args) {
|
|
|
17002
17055
|
return listScoresPage(client, table, filters, pagination.page, pagination.perPage, orderBy.field, orderBy.direction);
|
|
17003
17056
|
}
|
|
17004
17057
|
async function getScoreById(client, schema, scoreId) {
|
|
17058
|
+
const table = qualifiedTable(schema, TABLE_SCORE_EVENTS);
|
|
17005
17059
|
const row = await client.oneOrNone(`SELECT ${SCORE_SELECT_COLUMNS}
|
|
17006
|
-
FROM ${
|
|
17060
|
+
FROM ${table}
|
|
17007
17061
|
WHERE "scoreId" = $1
|
|
17008
|
-
ORDER BY "
|
|
17062
|
+
ORDER BY "cursorId" DESC
|
|
17009
17063
|
LIMIT 1`, [scoreId]);
|
|
17010
17064
|
return row ? rowToScoreRecord(row) : null;
|
|
17011
17065
|
}
|
|
17012
17066
|
async function listScoresPage(client, table, filters, page, perPage, orderField, orderDir) {
|
|
17013
|
-
|
|
17067
|
+
const acc = newFilterAccumulator();
|
|
17068
|
+
applyScoreFilters(acc, filters);
|
|
17069
|
+
applyLatestScorePredicate(acc, table);
|
|
17070
|
+
const whereClause = whereOrEmpty(acc);
|
|
17071
|
+
const countRow = await client.oneOrNone(`SELECT COUNT(*)::text AS count FROM ${table} s ${whereClause}`, acc.params);
|
|
17072
|
+
const total = Number(countRow?.count ?? 0);
|
|
17073
|
+
let scores = [];
|
|
17074
|
+
if (total > 0) {
|
|
17075
|
+
const safeOrderField = (0, _mastra_core_utils.parseSqlIdentifier)(orderField, "order field");
|
|
17076
|
+
scores = (await client.manyOrNone(`SELECT ${SCORE_SELECT_COLUMNS}
|
|
17077
|
+
FROM ${table} s
|
|
17078
|
+
${whereClause}
|
|
17079
|
+
ORDER BY "${safeOrderField}" ${orderDir}, "cursorId" ${orderDir}
|
|
17080
|
+
LIMIT $${acc.next++} OFFSET $${acc.next++}`, [
|
|
17081
|
+
...acc.params,
|
|
17082
|
+
perPage,
|
|
17083
|
+
page * perPage
|
|
17084
|
+
])).map(rowToScoreRecord);
|
|
17085
|
+
}
|
|
17086
|
+
const deltaCursor = deltaPollingFeatureEnabled() ? await readSignalStreamHeadCursor({
|
|
17014
17087
|
client,
|
|
17015
17088
|
table,
|
|
17016
17089
|
filters,
|
|
17017
|
-
|
|
17018
|
-
|
|
17019
|
-
|
|
17020
|
-
|
|
17021
|
-
|
|
17022
|
-
|
|
17023
|
-
|
|
17024
|
-
|
|
17025
|
-
|
|
17026
|
-
|
|
17090
|
+
applyFilters: applyScoreFilters
|
|
17091
|
+
}) : void 0;
|
|
17092
|
+
return {
|
|
17093
|
+
scores,
|
|
17094
|
+
pagination: {
|
|
17095
|
+
total,
|
|
17096
|
+
page,
|
|
17097
|
+
perPage,
|
|
17098
|
+
hasMore: (page + 1) * perPage < total
|
|
17099
|
+
},
|
|
17100
|
+
...deltaCursor !== void 0 ? { deltaCursor } : {}
|
|
17101
|
+
};
|
|
17027
17102
|
}
|
|
17028
17103
|
async function listScoresDelta(client, table, filters, after, limit) {
|
|
17029
17104
|
return listSignalDelta({
|
|
@@ -17039,12 +17114,14 @@ async function listScoresDelta(client, table, filters, after, limit) {
|
|
|
17039
17114
|
});
|
|
17040
17115
|
}
|
|
17041
17116
|
async function runScoreAggregateQuery(client, schema, args, filters) {
|
|
17117
|
+
const table = qualifiedTable(schema, TABLE_SCORE_EVENTS);
|
|
17042
17118
|
const acc = newFilterAccumulator();
|
|
17043
17119
|
pushScoreIdentity(acc, args.scorerId, args.scoreSource);
|
|
17044
17120
|
applyScoreFilters(acc, filters);
|
|
17121
|
+
applyLatestScorePredicate(acc, table);
|
|
17045
17122
|
const sql = `
|
|
17046
17123
|
SELECT ${aggregationSql(args.aggregation, "\"score\"")} AS "value"
|
|
17047
|
-
FROM ${
|
|
17124
|
+
FROM ${table} s
|
|
17048
17125
|
${whereOrEmpty(acc)}
|
|
17049
17126
|
`;
|
|
17050
17127
|
const row = await client.oneOrNone(sql, acc.params);
|
|
@@ -17076,10 +17153,12 @@ async function getScoreBreakdown(client, schema, args) {
|
|
|
17076
17153
|
});
|
|
17077
17154
|
pushScoreIdentity(acc, args.scorerId, args.scoreSource);
|
|
17078
17155
|
applyScoreFilters(acc, args.filters);
|
|
17156
|
+
const table = qualifiedTable(schema, TABLE_SCORE_EVENTS);
|
|
17157
|
+
applyLatestScorePredicate(acc, table);
|
|
17079
17158
|
const sql = `
|
|
17080
17159
|
SELECT ${resolved.map((e) => e.selectSql).join(", ")},
|
|
17081
17160
|
${aggregationSql(args.aggregation, "\"score\"")} AS "value"
|
|
17082
|
-
FROM ${
|
|
17161
|
+
FROM ${table} s
|
|
17083
17162
|
${whereOrEmpty(acc)}
|
|
17084
17163
|
GROUP BY ${resolved.map((e) => e.alias).join(", ")}
|
|
17085
17164
|
ORDER BY "value" DESC NULLS LAST
|
|
@@ -17099,11 +17178,13 @@ async function getScoreTimeSeries(client, schema, args) {
|
|
|
17099
17178
|
});
|
|
17100
17179
|
pushScoreIdentity(acc, args.scorerId, args.scoreSource);
|
|
17101
17180
|
applyScoreFilters(acc, args.filters);
|
|
17181
|
+
const table = qualifiedTable(schema, TABLE_SCORE_EVENTS);
|
|
17182
|
+
applyLatestScorePredicate(acc, table);
|
|
17102
17183
|
const sql = `
|
|
17103
17184
|
SELECT ${bucket} AS bucket,
|
|
17104
17185
|
${resolved.map((e) => e.selectSql).join(", ")},
|
|
17105
17186
|
${aggregationSql(args.aggregation, "\"score\"")} AS "value"
|
|
17106
|
-
FROM ${
|
|
17187
|
+
FROM ${table} s
|
|
17107
17188
|
${whereOrEmpty(acc)}
|
|
17108
17189
|
GROUP BY bucket, ${resolved.map((e) => e.alias).join(", ")}
|
|
17109
17190
|
ORDER BY bucket
|
|
@@ -17121,10 +17202,12 @@ async function getScoreTimeSeries(client, schema, args) {
|
|
|
17121
17202
|
const acc = newFilterAccumulator();
|
|
17122
17203
|
pushScoreIdentity(acc, args.scorerId, args.scoreSource);
|
|
17123
17204
|
applyScoreFilters(acc, args.filters);
|
|
17205
|
+
const table = qualifiedTable(schema, TABLE_SCORE_EVENTS);
|
|
17206
|
+
applyLatestScorePredicate(acc, table);
|
|
17124
17207
|
const sql = `
|
|
17125
17208
|
SELECT ${bucket} AS bucket,
|
|
17126
17209
|
${aggregationSql(args.aggregation, "\"score\"")} AS "value"
|
|
17127
|
-
FROM ${
|
|
17210
|
+
FROM ${table} s
|
|
17128
17211
|
${whereOrEmpty(acc)}
|
|
17129
17212
|
GROUP BY bucket
|
|
17130
17213
|
ORDER BY bucket
|
|
@@ -17144,9 +17227,11 @@ async function getScorePercentiles(client, schema, args) {
|
|
|
17144
17227
|
const acc = newFilterAccumulator();
|
|
17145
17228
|
pushScoreIdentity(acc, args.scorerId, args.scoreSource);
|
|
17146
17229
|
applyScoreFilters(acc, args.filters);
|
|
17230
|
+
const table = qualifiedTable(schema, TABLE_SCORE_EVENTS);
|
|
17231
|
+
applyLatestScorePredicate(acc, table);
|
|
17147
17232
|
const sql = `
|
|
17148
17233
|
SELECT ${bucket} AS bucket, ${percentileSelectSql(args.percentiles, "\"score\"")}
|
|
17149
|
-
FROM ${
|
|
17234
|
+
FROM ${table} s
|
|
17150
17235
|
${whereOrEmpty(acc)}
|
|
17151
17236
|
GROUP BY bucket
|
|
17152
17237
|
ORDER BY bucket
|
|
@@ -17345,13 +17430,6 @@ function latestSpanPredicate$1(spanTable) {
|
|
|
17345
17430
|
AND (newer."isPending" < s."isPending" OR (newer."isPending" = s."isPending" AND newer."cursorId" > s."cursorId"))
|
|
17346
17431
|
)`;
|
|
17347
17432
|
}
|
|
17348
|
-
function latestScorePredicate(scoreTable) {
|
|
17349
|
-
return `NOT EXISTS (
|
|
17350
|
-
SELECT 1 FROM ${scoreTable} newer
|
|
17351
|
-
WHERE newer."scoreId" = s."scoreId"
|
|
17352
|
-
AND newer."cursorId" > s."cursorId"
|
|
17353
|
-
)`;
|
|
17354
|
-
}
|
|
17355
17433
|
function latestFeedbackPredicate(feedbackTable) {
|
|
17356
17434
|
return `NOT EXISTS (
|
|
17357
17435
|
SELECT 1 FROM ${feedbackTable} newer
|
|
@@ -17521,7 +17599,7 @@ function compilePostgresTraceScope(schema, selection, relationCollections) {
|
|
|
17521
17599
|
values
|
|
17522
17600
|
};
|
|
17523
17601
|
}
|
|
17524
|
-
function compilePostgresTraceQuery(schema, plan) {
|
|
17602
|
+
function compilePostgresTraceQuery(schema, plan, mode = "data") {
|
|
17525
17603
|
const { ctes, values } = compilePostgresTraceScope(schema, plan, collectRelationCollections(plan.where));
|
|
17526
17604
|
let predicateSql = "TRUE";
|
|
17527
17605
|
if (plan.where) {
|
|
@@ -17535,6 +17613,12 @@ function compilePostgresTraceQuery(schema, plan) {
|
|
|
17535
17613
|
WHERE ${predicateSql}
|
|
17536
17614
|
)`);
|
|
17537
17615
|
const candidates = `WITH ${ctes.join(",\n")}`;
|
|
17616
|
+
if (plan.paginationMode === "page" && mode === "count") return {
|
|
17617
|
+
text: `${candidates}
|
|
17618
|
+
SELECT COUNT(*)::text AS count
|
|
17619
|
+
FROM candidates`,
|
|
17620
|
+
values
|
|
17621
|
+
};
|
|
17538
17622
|
if (plan.result === "groups") {
|
|
17539
17623
|
const pageCondition = plan.cursor ? `AND "threadId" > $${values.length + 1}` : "";
|
|
17540
17624
|
if (plan.cursor) values.push(plan.cursor.threadId);
|
|
@@ -17552,6 +17636,17 @@ LIMIT $${values.length}`,
|
|
|
17552
17636
|
}
|
|
17553
17637
|
const orderField = plan.orderBy.field === "startedAt" ? "\"startedAt\"" : "\"endedAt\"";
|
|
17554
17638
|
const direction = plan.orderBy.direction === "asc" ? "ASC" : "DESC";
|
|
17639
|
+
if (plan.paginationMode === "page") {
|
|
17640
|
+
values.push(plan.perPage, plan.page * plan.perPage);
|
|
17641
|
+
return {
|
|
17642
|
+
text: `${candidates}
|
|
17643
|
+
SELECT *
|
|
17644
|
+
FROM candidates
|
|
17645
|
+
ORDER BY ${orderField} ${direction}, "traceId" ASC
|
|
17646
|
+
LIMIT $${values.length - 1} OFFSET $${values.length}`,
|
|
17647
|
+
values
|
|
17648
|
+
};
|
|
17649
|
+
}
|
|
17555
17650
|
let pageCondition = "";
|
|
17556
17651
|
if (plan.cursor) {
|
|
17557
17652
|
const comparison = plan.orderBy.direction === "asc" ? ">" : "<";
|
|
@@ -17698,10 +17793,11 @@ function isPostgresResourceLimit(error) {
|
|
|
17698
17793
|
const candidate = error;
|
|
17699
17794
|
return candidate.code === "53200" || candidate.code === "53400";
|
|
17700
17795
|
}
|
|
17701
|
-
async function runWithPostgresTraceQueryTimeout(client, timeoutMs, execute) {
|
|
17796
|
+
async function runWithPostgresTraceQueryTimeout(client, timeoutMs, execute, options = {}) {
|
|
17702
17797
|
const resolvedTimeoutMs = _mastra_core_storage.resolveTraceQueryTimeoutMs(timeoutMs);
|
|
17703
17798
|
try {
|
|
17704
17799
|
return await client.tx(async (transaction) => {
|
|
17800
|
+
if (options.repeatableRead) await transaction.query("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ");
|
|
17705
17801
|
await transaction.query(`SELECT set_config('statement_timeout', $1, true)`, [`${resolvedTimeoutMs}ms`]);
|
|
17706
17802
|
return execute(transaction);
|
|
17707
17803
|
});
|
|
@@ -17735,6 +17831,50 @@ async function getTraceQueryValues(client, schema, plan, timeoutMs) {
|
|
|
17735
17831
|
});
|
|
17736
17832
|
}
|
|
17737
17833
|
async function queryTraces(client, schema, plan, timeoutMs) {
|
|
17834
|
+
if (plan.paginationMode === "page") {
|
|
17835
|
+
const resolvedTimeoutMs = _mastra_core_storage.resolveTraceQueryTimeoutMs(timeoutMs);
|
|
17836
|
+
const deadline = performance.now() + resolvedTimeoutMs;
|
|
17837
|
+
const countQuery = compilePostgresTraceQuery(schema, plan, "count");
|
|
17838
|
+
const dataQuery = compilePostgresTraceQuery(schema, plan);
|
|
17839
|
+
const { total, rows } = await runWithPostgresTraceQueryTimeout(client, resolvedTimeoutMs, async (transaction) => {
|
|
17840
|
+
const countRows = await transaction.any(countQuery.text, countQuery.values);
|
|
17841
|
+
const remainingTimeoutMs = Math.floor(deadline - performance.now());
|
|
17842
|
+
if (remainingTimeoutMs <= 0) throw new _mastra_core_storage.TraceQueryExecutionError();
|
|
17843
|
+
await transaction.query(`SELECT set_config('statement_timeout', $1, true)`, [`${remainingTimeoutMs}ms`]);
|
|
17844
|
+
const rows = await transaction.any(dataQuery.text, dataQuery.values);
|
|
17845
|
+
return {
|
|
17846
|
+
total: Number(countRows[0]?.count ?? 0),
|
|
17847
|
+
rows
|
|
17848
|
+
};
|
|
17849
|
+
}, { repeatableRead: true });
|
|
17850
|
+
const traces = rows.map((row) => ({
|
|
17851
|
+
traceId: String(row.traceId),
|
|
17852
|
+
rootSpanId: String(row.rootSpanId),
|
|
17853
|
+
name: row.name,
|
|
17854
|
+
entityId: row.entityId ?? null,
|
|
17855
|
+
parentSpanId: row.parentSpanId ?? null,
|
|
17856
|
+
createdAt: asIsoTimestamp$1(row.startedAt),
|
|
17857
|
+
metadata: row.metadata ?? null,
|
|
17858
|
+
inputPreview: _mastra_core_storage.buildInputPreview(row.input) ?? null,
|
|
17859
|
+
threadId: row.threadId == null ? null : String(row.threadId),
|
|
17860
|
+
resourceId: row.resourceId == null ? null : String(row.resourceId),
|
|
17861
|
+
startedAt: asIsoTimestamp$1(row.startedAt),
|
|
17862
|
+
endedAt: asIsoTimestamp$1(row.endedAt),
|
|
17863
|
+
entityName: row.entityName == null ? null : String(row.entityName),
|
|
17864
|
+
entityType: row.entityType == null ? null : String(row.entityType),
|
|
17865
|
+
environment: row.environment == null ? null : String(row.environment),
|
|
17866
|
+
status: row.status
|
|
17867
|
+
}));
|
|
17868
|
+
return _mastra_core_storage.traceQueryResponseSchema.parse({
|
|
17869
|
+
traces,
|
|
17870
|
+
pagination: {
|
|
17871
|
+
total,
|
|
17872
|
+
page: plan.page,
|
|
17873
|
+
perPage: plan.perPage,
|
|
17874
|
+
hasMore: (plan.page + 1) * plan.perPage < total
|
|
17875
|
+
}
|
|
17876
|
+
});
|
|
17877
|
+
}
|
|
17738
17878
|
const query = compilePostgresTraceQuery(schema, plan);
|
|
17739
17879
|
const rows = await runWithPostgresTraceQueryTimeout(client, timeoutMs, (transaction) => transaction.any(query.text, query.values));
|
|
17740
17880
|
const visibleRows = rows.slice(0, plan.limit);
|
|
@@ -22195,6 +22335,7 @@ const WORKFLOW_SNAPSHOT_STATUS_INDEX = "mastra_workflow_snapshot_name_status_cre
|
|
|
22195
22335
|
* Schema-prefixed name of the status index, lowercased and truncated the same way Postgres
|
|
22196
22336
|
* stores it, so the init snapshot's index set answers "does it exist?" without a probe or a
|
|
22197
22337
|
* no-op `CREATE INDEX` (schema-prefixed names routinely exceed the 63-byte limit).
|
|
22338
|
+
* Exported for tests.
|
|
22198
22339
|
*/
|
|
22199
22340
|
function workflowSnapshotStatusIndexName(schemaName) {
|
|
22200
22341
|
return buildConstraintName({
|
|
@@ -22212,6 +22353,48 @@ function workflowSnapshotStatusIndexSQL(indexName, schemaName) {
|
|
|
22212
22353
|
schemaName: getSchemaName(schemaName)
|
|
22213
22354
|
})} (workflow_name, (snapshot ->> 'status'), "createdAt" DESC)`;
|
|
22214
22355
|
}
|
|
22356
|
+
/** Base name (before any schema prefix) of the expression index backing the threadId filter. */
|
|
22357
|
+
const WORKFLOW_SNAPSHOT_THREAD_ID_INDEX = "mastra_workflow_snapshot_threadid_idx";
|
|
22358
|
+
/**
|
|
22359
|
+
* Schema-prefixed name of the threadId index (see workflowSnapshotStatusIndexName).
|
|
22360
|
+
*
|
|
22361
|
+
* Unlike the status index, truncation appends a collision hash: both index names share the
|
|
22362
|
+
* long `<schema>_mastra_workflow_snapshot_` prefix, so with a schema name of 37+ bytes plain
|
|
22363
|
+
* truncation collapses them to the same 63-byte identifier and `CREATE INDEX IF NOT EXISTS`
|
|
22364
|
+
* silently skips this index. The status index keeps plain truncation because its truncated
|
|
22365
|
+
* name already exists in deployed catalogs; this index is new and free to adopt the rule.
|
|
22366
|
+
* Exported for tests.
|
|
22367
|
+
*/
|
|
22368
|
+
function workflowSnapshotThreadIdIndexName(schemaName) {
|
|
22369
|
+
return buildConstraintName({
|
|
22370
|
+
baseName: WORKFLOW_SNAPSHOT_THREAD_ID_INDEX,
|
|
22371
|
+
schemaName: schemaName && schemaName !== "public" ? schemaName : void 0,
|
|
22372
|
+
hashWhenTruncated: true
|
|
22373
|
+
});
|
|
22374
|
+
}
|
|
22375
|
+
/**
|
|
22376
|
+
* Expression extracting the thread id embedded in a snapshot (jsonb columns only). Mirrors
|
|
22377
|
+
* the canonical extraction in `@mastra/core` (`getSnapshotMemoryInfo`), which reads one of
|
|
22378
|
+
* two layouts:
|
|
22379
|
+
* 1. agentic-loop: `context.<suspended step>.suspendPayload.__streamState.messageList.memoryInfo.threadId`
|
|
22380
|
+
* 2. durable loop: `context.input.messageListState.memoryInfo.threadId`
|
|
22381
|
+
*
|
|
22382
|
+
* `jsonb_path_query_first(jsonb, jsonpath)` is IMMUTABLE, so the expression is valid in an
|
|
22383
|
+
* expression index. The WHERE clause in listWorkflowRuns() must use this exact expression
|
|
22384
|
+
* text so the planner can match it against the index. If the snapshot layout changes in
|
|
22385
|
+
* core, this expression must be updated in lockstep or it will wrongly exclude rows.
|
|
22386
|
+
*/
|
|
22387
|
+
const WORKFLOW_SNAPSHOT_THREAD_ID_EXPR = `COALESCE(jsonb_path_query_first(snapshot, '$.context.* ? (@.status == "suspended").suspendPayload.__streamState.messageList.memoryInfo.threadId') #>> '{}', snapshot #>> '{context,input,messageListState,memoryInfo,threadId}')`;
|
|
22388
|
+
/**
|
|
22389
|
+
* Expression index on the snapshot-embedded thread id so listWorkflowRuns() threadId filters
|
|
22390
|
+
* (Agent.listSuspendedRuns) can use an index instead of detoasting every snapshot.
|
|
22391
|
+
*/
|
|
22392
|
+
function workflowSnapshotThreadIdIndexSQL(indexName, schemaName) {
|
|
22393
|
+
return `CREATE INDEX IF NOT EXISTS "${indexName}" ON ${getTableName({
|
|
22394
|
+
indexName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
|
|
22395
|
+
schemaName: getSchemaName(schemaName)
|
|
22396
|
+
})} ((${WORKFLOW_SNAPSHOT_THREAD_ID_EXPR}))`;
|
|
22397
|
+
}
|
|
22215
22398
|
var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorage {
|
|
22216
22399
|
#db;
|
|
22217
22400
|
#schema;
|
|
@@ -22284,6 +22467,7 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
|
|
|
22284
22467
|
}));
|
|
22285
22468
|
for (const idx of WorkflowsPG.getDefaultIndexDefs(schemaPrefix)) statements.push(generateIndexSQL(idx, schemaName));
|
|
22286
22469
|
statements.push(`${workflowSnapshotStatusIndexSQL(workflowSnapshotStatusIndexName(parsedSchema), schemaName)};`);
|
|
22470
|
+
statements.push(`${workflowSnapshotThreadIdIndexSQL(workflowSnapshotThreadIdIndexName(parsedSchema), schemaName)};`);
|
|
22287
22471
|
return statements;
|
|
22288
22472
|
}
|
|
22289
22473
|
/**
|
|
@@ -22310,6 +22494,12 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
|
|
|
22310
22494
|
} catch (error) {
|
|
22311
22495
|
this.logger?.warn?.(`Failed to create index ${indexName}:`, error);
|
|
22312
22496
|
}
|
|
22497
|
+
const threadIdIndexName = workflowSnapshotThreadIdIndexName(this.#schema);
|
|
22498
|
+
try {
|
|
22499
|
+
await this.#db.createIndexFromStatement(threadIdIndexName, workflowSnapshotThreadIdIndexSQL(threadIdIndexName, this.#schema));
|
|
22500
|
+
} catch (error) {
|
|
22501
|
+
this.logger?.warn?.(`Failed to create index ${threadIdIndexName}:`, error);
|
|
22502
|
+
}
|
|
22313
22503
|
}
|
|
22314
22504
|
async init() {
|
|
22315
22505
|
await this.#db.createTable({
|
|
@@ -22590,7 +22780,7 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
|
|
|
22590
22780
|
}, error);
|
|
22591
22781
|
}
|
|
22592
22782
|
}
|
|
22593
|
-
async listWorkflowRuns({ workflowName, fromDate, toDate, perPage, page, resourceId, status } = {}) {
|
|
22783
|
+
async listWorkflowRuns({ workflowName, fromDate, toDate, perPage, page, resourceId, threadId, status } = {}) {
|
|
22594
22784
|
try {
|
|
22595
22785
|
const conditions = [];
|
|
22596
22786
|
const values = [];
|
|
@@ -22611,6 +22801,11 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
|
|
|
22611
22801
|
values.push(resourceId);
|
|
22612
22802
|
paramIndex++;
|
|
22613
22803
|
} else this.logger?.warn?.(`[${_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT}] resourceId column not found. Skipping resourceId filter.`);
|
|
22804
|
+
if (threadId) if (await this.#db.getColumnType(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT, "snapshot") === "jsonb") {
|
|
22805
|
+
conditions.push(`${WORKFLOW_SNAPSHOT_THREAD_ID_EXPR} = $${paramIndex}`);
|
|
22806
|
+
values.push(threadId);
|
|
22807
|
+
paramIndex++;
|
|
22808
|
+
} else this.logger?.warn?.(`[${_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT}] snapshot column is not jsonb. Skipping threadId filter.`);
|
|
22614
22809
|
if (fromDate) {
|
|
22615
22810
|
conditions.push(`"createdAt" >= $${paramIndex}`);
|
|
22616
22811
|
values.push(fromDate);
|