@mastra/mysql 0.13.0 → 0.13.1-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_types/@internal_core/dist/memory/index.d.ts +102 -0
- package/dist/_types/@internal_core/package.json +1 -0
- package/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/index.cjs +1045 -449
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1046 -450
- package/dist/index.js.map +1 -1
- package/dist/storage/db/run-fencing.d.ts +34 -0
- package/dist/storage/db/run-fencing.d.ts.map +1 -0
- package/dist/storage/domains/memory/index.d.ts +51 -5
- package/dist/storage/domains/memory/index.d.ts.map +1 -1
- package/dist/storage/domains/workflows/index.d.ts +16 -5
- package/dist/storage/domains/workflows/index.d.ts.map +1 -1
- package/package.json +5 -4
package/dist/index.cjs
CHANGED
|
@@ -3,6 +3,7 @@ let _mastra_core_error = require("@mastra/core/error");
|
|
|
3
3
|
let _mastra_core_storage = require("@mastra/core/storage");
|
|
4
4
|
let mysql2_promise = require("mysql2/promise");
|
|
5
5
|
let _mastra_core_utils = require("@mastra/core/utils");
|
|
6
|
+
let crypto$1 = require("crypto");
|
|
6
7
|
let _mastra_core_agent = require("@mastra/core/agent");
|
|
7
8
|
let _mastra_core_storage_domains_skills = require("@mastra/core/storage/domains/skills");
|
|
8
9
|
//#region src/storage/db/schema-snapshot.ts
|
|
@@ -6022,9 +6023,180 @@ var MCPServersMySQL = class MCPServersMySQL extends _mastra_core_storage.MCPServ
|
|
|
6022
6023
|
}
|
|
6023
6024
|
};
|
|
6024
6025
|
//#endregion
|
|
6026
|
+
//#region ../../packages/_internal-core/dist/memory/index.js
|
|
6027
|
+
/**
|
|
6028
|
+
* Shared rules for observational memory lifecycle writes. Every storage adapter applies
|
|
6029
|
+
* these inside its own atomic section (lock, transaction, or conditional update), so the
|
|
6030
|
+
* decisions are identical across adapters.
|
|
6031
|
+
*/
|
|
6032
|
+
/**
|
|
6033
|
+
* Observational memory columns that lifecycle writes depend on. Storage adapters merge these
|
|
6034
|
+
* into the table schema they get from `@mastra/core`, so the columns exist even when the
|
|
6035
|
+
* installed core predates them.
|
|
6036
|
+
*/
|
|
6037
|
+
const OBSERVATIONAL_MEMORY_LIFECYCLE_COLUMNS = { supersededBy: {
|
|
6038
|
+
type: "text",
|
|
6039
|
+
nullable: true
|
|
6040
|
+
} };
|
|
6041
|
+
/** `schema` plus {@link OBSERVATIONAL_MEMORY_LIFECYCLE_COLUMNS}; `undefined` when `schema` is. */
|
|
6042
|
+
function withObservationalMemoryLifecycleColumns(schema) {
|
|
6043
|
+
return schema ? {
|
|
6044
|
+
...schema,
|
|
6045
|
+
...OBSERVATIONAL_MEMORY_LIFECYCLE_COLUMNS
|
|
6046
|
+
} : void 0;
|
|
6047
|
+
}
|
|
6048
|
+
/**
|
|
6049
|
+
* A buffered chunk stores `lastObservedAt = max message time + 1ms`. It is wholly covered
|
|
6050
|
+
* (every message it observed is already at or before the cursor) iff
|
|
6051
|
+
* `cursor >= chunk.lastObservedAt - 1ms`.
|
|
6052
|
+
*/
|
|
6053
|
+
function isBufferedChunkCoveredByCursor(chunkLastObservedAt, cursor) {
|
|
6054
|
+
if (!cursor) return false;
|
|
6055
|
+
return new Date(cursor).getTime() >= new Date(chunkLastObservedAt).getTime() - 1;
|
|
6056
|
+
}
|
|
6057
|
+
/** The later of two cursors; `undefined` only when both are absent. */
|
|
6058
|
+
function maxObservationCursor(a, b) {
|
|
6059
|
+
if (!a) return b ? new Date(b) : void 0;
|
|
6060
|
+
if (!b) return new Date(a);
|
|
6061
|
+
return new Date(Math.max(new Date(a).getTime(), new Date(b).getTime()));
|
|
6062
|
+
}
|
|
6063
|
+
/**
|
|
6064
|
+
* Decide the new generation's text when a reflection built from `snapshot` commits against
|
|
6065
|
+
* the stored record.
|
|
6066
|
+
*
|
|
6067
|
+
* - Stored text equals the snapshot: the reflection replaces it.
|
|
6068
|
+
* - Stored text extends the snapshot (activation only appends): the appended tail is kept
|
|
6069
|
+
* after the reflection, and its tokens are added.
|
|
6070
|
+
* - Anything else (a non-append rewrite): `null` — the reflection must not be applied.
|
|
6071
|
+
*/
|
|
6072
|
+
function planReflectionGenerationText(input) {
|
|
6073
|
+
const stored = input.storedObservations ?? "";
|
|
6074
|
+
const snapshot = input.snapshotObservations ?? "";
|
|
6075
|
+
if (stored === snapshot) return {
|
|
6076
|
+
mode: "equal",
|
|
6077
|
+
observations: input.reflection,
|
|
6078
|
+
tokenCount: input.tokenCount
|
|
6079
|
+
};
|
|
6080
|
+
if (!stored.startsWith(snapshot)) return null;
|
|
6081
|
+
const tail = stored.slice(snapshot.length).trimStart();
|
|
6082
|
+
if (!tail.trim()) return {
|
|
6083
|
+
mode: "equal",
|
|
6084
|
+
observations: input.reflection,
|
|
6085
|
+
tokenCount: input.tokenCount
|
|
6086
|
+
};
|
|
6087
|
+
return {
|
|
6088
|
+
mode: "append",
|
|
6089
|
+
observations: input.reflection ? `${input.reflection}\n\n${tail}` : tail,
|
|
6090
|
+
tokenCount: input.tokenCount + Math.max(0, (input.storedObservationTokenCount ?? 0) - (input.snapshotObservationTokenCount ?? 0))
|
|
6091
|
+
};
|
|
6092
|
+
}
|
|
6093
|
+
/**
|
|
6094
|
+
* Backs `updateActiveObservations` in adapters that implement `commitActiveObservations`.
|
|
6095
|
+
* Callers of `updateActiveObservations` (which resolves `void`) can't see a commit result, so a
|
|
6096
|
+
* write that was not applied throws instead of resolving as if it had committed.
|
|
6097
|
+
*/
|
|
6098
|
+
function assertActiveObservationsApplied(result, id) {
|
|
6099
|
+
if (result.applied) return;
|
|
6100
|
+
throw new Error(result.reason === "retired" ? `Observational memory record ${id} was superseded by a newer generation; observations were not written` : `Observational memory record ${id} changed since the observations were composed; observations were not written`);
|
|
6101
|
+
}
|
|
6102
|
+
/**
|
|
6103
|
+
* Whether the stored text can still accept a reflection built from `snapshot`
|
|
6104
|
+
* (equal, or only appended to since).
|
|
6105
|
+
*/
|
|
6106
|
+
function isAppendOnlySince(storedObservations, snapshotObservations) {
|
|
6107
|
+
const stored = storedObservations ?? "";
|
|
6108
|
+
const snapshot = snapshotObservations ?? "";
|
|
6109
|
+
return stored === snapshot || stored.startsWith(snapshot);
|
|
6110
|
+
}
|
|
6111
|
+
//#endregion
|
|
6112
|
+
//#region src/storage/db/run-fencing.ts
|
|
6113
|
+
/**
|
|
6114
|
+
* Epoch milliseconds on the database clock. `UTC_TIMESTAMP` is the time the
|
|
6115
|
+
* statement started and doesn't depend on the session time zone. Every lease
|
|
6116
|
+
* comparison runs in a statement that already holds the run's owner row, so
|
|
6117
|
+
* no lock wait sits between that start time and the comparison.
|
|
6118
|
+
*/
|
|
6119
|
+
const DB_NOW_MS = `(TIMESTAMPDIFF(MICROSECOND, '1970-01-01 00:00:00', UTC_TIMESTAMP(3)) DIV 1000)`;
|
|
6120
|
+
const ER_DUP_ENTRY = 1062;
|
|
6121
|
+
const ER_LOCK_DEADLOCK = 1213;
|
|
6122
|
+
const MAX_CLAIM_ATTEMPTS = 5;
|
|
6123
|
+
/**
|
|
6124
|
+
* The check half of a fenced write; call it first inside the write's
|
|
6125
|
+
* transaction. The shared lock holds the claim row until commit, so a claim
|
|
6126
|
+
* that updates the row waits for the write to finish, and every later write
|
|
6127
|
+
* sees the new claim.
|
|
6128
|
+
*/
|
|
6129
|
+
async function assertRunFence(q, claimsTable, fence, operation) {
|
|
6130
|
+
const [rows] = await q.execute(`SELECT generation, ${quoteIdentifier("ownerId", "column name")} AS ownerId FROM ${claimsTable}
|
|
6131
|
+
WHERE ${quoteIdentifier("runId", "column name")} = ? LOCK IN SHARE MODE`, [fence.runId]);
|
|
6132
|
+
const row = rows[0];
|
|
6133
|
+
if (!(0, _mastra_core_storage.matchesRunFence)(row ? {
|
|
6134
|
+
generation: Number(row.generation),
|
|
6135
|
+
ownerId: String(row.ownerId)
|
|
6136
|
+
} : null, fence)) throw new _mastra_core_storage.RunFenceConflictError(fence, operation);
|
|
6137
|
+
}
|
|
6138
|
+
/** Runs `fn` in a transaction on its own pooled connection. */
|
|
6139
|
+
async function inTransaction(pool, fn) {
|
|
6140
|
+
const connection = await pool.getConnection();
|
|
6141
|
+
try {
|
|
6142
|
+
await connection.beginTransaction();
|
|
6143
|
+
const result = await fn(connection);
|
|
6144
|
+
await connection.commit();
|
|
6145
|
+
return result;
|
|
6146
|
+
} catch (error) {
|
|
6147
|
+
await connection.rollback().catch(() => {});
|
|
6148
|
+
throw error;
|
|
6149
|
+
} finally {
|
|
6150
|
+
connection.release();
|
|
6151
|
+
}
|
|
6152
|
+
}
|
|
6153
|
+
/**
|
|
6154
|
+
* Runs `write` directly when it carries no fence, or in a transaction behind
|
|
6155
|
+
* the fence check when it does.
|
|
6156
|
+
*/
|
|
6157
|
+
function withRunFence(pool, claimsTable, fence, operation, write) {
|
|
6158
|
+
if (!fence) return write(pool);
|
|
6159
|
+
return inTransaction(pool, async (connection) => {
|
|
6160
|
+
await assertRunFence(connection, claimsTable, fence, operation);
|
|
6161
|
+
return write(connection);
|
|
6162
|
+
});
|
|
6163
|
+
}
|
|
6164
|
+
/**
|
|
6165
|
+
* Runs a transaction that creates or updates a run's claim row, retrying when
|
|
6166
|
+
* two first claims race to insert it. Under REPEATABLE READ both lock the gap
|
|
6167
|
+
* where the row would go and one is chosen as a deadlock victim; under READ
|
|
6168
|
+
* COMMITTED the later insert hits the duplicate key. On retry the row exists
|
|
6169
|
+
* and the claim serializes behind its row lock.
|
|
6170
|
+
*/
|
|
6171
|
+
async function claimTransaction(pool, fn) {
|
|
6172
|
+
for (let attempt = 1;; attempt++) try {
|
|
6173
|
+
return await inTransaction(pool, fn);
|
|
6174
|
+
} catch (error) {
|
|
6175
|
+
const errno = error.errno;
|
|
6176
|
+
if (attempt >= MAX_CLAIM_ATTEMPTS || errno !== ER_DUP_ENTRY && errno !== ER_LOCK_DEADLOCK) throw error;
|
|
6177
|
+
}
|
|
6178
|
+
}
|
|
6179
|
+
//#endregion
|
|
6025
6180
|
//#region src/storage/domains/memory/index.ts
|
|
6026
6181
|
const OM_TABLE = "mastra_observational_memory";
|
|
6027
6182
|
const OM_TABLE_QUOTED = quoteIdentifier(OM_TABLE, "table name");
|
|
6183
|
+
const RUN_FENCES_TABLE = formatTableName(_mastra_core_storage.TABLE_MEMORY_RUN_FENCES);
|
|
6184
|
+
const RUN_ID$1 = quoteIdentifier("runId", "column name");
|
|
6185
|
+
const OWNER_ID$1 = quoteIdentifier("ownerId", "column name");
|
|
6186
|
+
const RETIRED_AT = quoteIdentifier("retiredAt", "column name");
|
|
6187
|
+
/** Canonical head order: newest generation, then earliest createdAt, then lowest id. */
|
|
6188
|
+
const OM_HEAD_ORDER = `${quoteIdentifier("generationCount", "column name")} DESC, ${quoteIdentifier("createdAt", "column name")} ASC, ${quoteIdentifier("id", "column name")} ASC`;
|
|
6189
|
+
/** Max `supersededBy` hops when redirecting a write aimed at a retired record. */
|
|
6190
|
+
const OM_MAX_HEAD_HOPS = 3;
|
|
6191
|
+
/** Attempts for an OM transaction that hits a deadlock or lock-wait timeout. */
|
|
6192
|
+
const OM_MAX_TRANSACTION_ATTEMPTS = 3;
|
|
6193
|
+
/** How long initialization waits for the per-key `GET_LOCK` before failing. */
|
|
6194
|
+
const OM_INIT_LOCK_TIMEOUT_SECONDS = 30;
|
|
6195
|
+
/** ER_LOCK_DEADLOCK (1213) or ER_LOCK_WAIT_TIMEOUT (1205): the transaction was rolled back and can be retried. */
|
|
6196
|
+
function isRetryableLockError(error) {
|
|
6197
|
+
const errno = error?.errno;
|
|
6198
|
+
return errno === 1213 || errno === 1205;
|
|
6199
|
+
}
|
|
6028
6200
|
function emitValidationError(message) {
|
|
6029
6201
|
return new _mastra_core_error.MastraError({
|
|
6030
6202
|
id: "MYSQL_MEMORY_VALIDATION_ERROR",
|
|
@@ -6118,6 +6290,69 @@ var MemoryMySQL = class MemoryMySQL extends _mastra_core_storage.MemoryStorage {
|
|
|
6118
6290
|
this.#skipDefaultIndexes = skipDefaultIndexes;
|
|
6119
6291
|
this.#indexes = indexes?.filter((idx) => MemoryMySQL.MANAGED_TABLES.includes(idx.table));
|
|
6120
6292
|
}
|
|
6293
|
+
supportsRunFencing() {
|
|
6294
|
+
return true;
|
|
6295
|
+
}
|
|
6296
|
+
async raiseRunFence(fence) {
|
|
6297
|
+
try {
|
|
6298
|
+
return await claimTransaction(this.pool, async (connection) => {
|
|
6299
|
+
const [rows] = await connection.execute(`SELECT generation, ${OWNER_ID$1} AS ownerId FROM ${RUN_FENCES_TABLE} WHERE ${RUN_ID$1} = ? FOR UPDATE`, [fence.runId]);
|
|
6300
|
+
const row = rows[0];
|
|
6301
|
+
if (!row) {
|
|
6302
|
+
await connection.execute(`INSERT INTO ${RUN_FENCES_TABLE} (${RUN_ID$1}, generation, ${OWNER_ID$1}) VALUES (?, ?, ?)`, [
|
|
6303
|
+
fence.runId,
|
|
6304
|
+
fence.generation,
|
|
6305
|
+
fence.ownerId
|
|
6306
|
+
]);
|
|
6307
|
+
return true;
|
|
6308
|
+
}
|
|
6309
|
+
const generation = Number(row.generation);
|
|
6310
|
+
const raises = generation < fence.generation;
|
|
6311
|
+
const reaffirms = generation === fence.generation && String(row.ownerId) === fence.ownerId;
|
|
6312
|
+
if (!raises && !reaffirms) return false;
|
|
6313
|
+
await connection.execute(`UPDATE ${RUN_FENCES_TABLE} SET generation = ?, ${OWNER_ID$1} = ?, ${RETIRED_AT} = NULL WHERE ${RUN_ID$1} = ?`, [
|
|
6314
|
+
fence.generation,
|
|
6315
|
+
fence.ownerId,
|
|
6316
|
+
fence.runId
|
|
6317
|
+
]);
|
|
6318
|
+
return true;
|
|
6319
|
+
});
|
|
6320
|
+
} catch (error) {
|
|
6321
|
+
throw new _mastra_core_error.MastraError({
|
|
6322
|
+
id: (0, _mastra_core_storage.createStorageErrorId)("MYSQL", "RAISE_RUN_FENCE", "FAILED"),
|
|
6323
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
6324
|
+
category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
|
|
6325
|
+
details: { runId: fence.runId }
|
|
6326
|
+
}, error);
|
|
6327
|
+
}
|
|
6328
|
+
}
|
|
6329
|
+
async retireRunFence(fence) {
|
|
6330
|
+
try {
|
|
6331
|
+
return await inTransaction(this.pool, async (connection) => {
|
|
6332
|
+
const [rows] = await connection.execute(`SELECT generation, ${OWNER_ID$1} AS ownerId FROM ${RUN_FENCES_TABLE} WHERE ${RUN_ID$1} = ? FOR UPDATE`, [fence.runId]);
|
|
6333
|
+
const row = rows[0];
|
|
6334
|
+
if (!row || Number(row.generation) !== fence.generation || String(row.ownerId) !== fence.ownerId) return false;
|
|
6335
|
+
await connection.execute(`UPDATE ${RUN_FENCES_TABLE} SET ${RETIRED_AT} = ${DB_NOW_MS} WHERE ${RUN_ID$1} = ?`, [fence.runId]);
|
|
6336
|
+
return true;
|
|
6337
|
+
});
|
|
6338
|
+
} catch (error) {
|
|
6339
|
+
throw new _mastra_core_error.MastraError({
|
|
6340
|
+
id: (0, _mastra_core_storage.createStorageErrorId)("MYSQL", "RETIRE_RUN_FENCE", "FAILED"),
|
|
6341
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
6342
|
+
category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
|
|
6343
|
+
details: { runId: fence.runId }
|
|
6344
|
+
}, error);
|
|
6345
|
+
}
|
|
6346
|
+
}
|
|
6347
|
+
/** Runs `write` behind its own fence, otherwise the one in scope for this store. */
|
|
6348
|
+
#fenced(fence, operation, write) {
|
|
6349
|
+
return withRunFence(this.pool, RUN_FENCES_TABLE, (0, _mastra_core_storage.resolveRunFence)(this, fence), operation, write);
|
|
6350
|
+
}
|
|
6351
|
+
/** The fence check for a write that already runs in its own transaction. */
|
|
6352
|
+
async #assertFence(connection, fence, operation) {
|
|
6353
|
+
const resolved = (0, _mastra_core_storage.resolveRunFence)(this, fence);
|
|
6354
|
+
if (resolved) await assertRunFence(connection, RUN_FENCES_TABLE, resolved, operation);
|
|
6355
|
+
}
|
|
6121
6356
|
async init() {
|
|
6122
6357
|
await this.operations.createTable({
|
|
6123
6358
|
tableName: _mastra_core_storage.TABLE_THREADS,
|
|
@@ -6131,7 +6366,11 @@ var MemoryMySQL = class MemoryMySQL extends _mastra_core_storage.MemoryStorage {
|
|
|
6131
6366
|
tableName: _mastra_core_storage.TABLE_RESOURCES,
|
|
6132
6367
|
schema: _mastra_core_storage.TABLE_SCHEMAS[_mastra_core_storage.TABLE_RESOURCES]
|
|
6133
6368
|
});
|
|
6134
|
-
|
|
6369
|
+
await this.operations.createTable({
|
|
6370
|
+
tableName: _mastra_core_storage.TABLE_MEMORY_RUN_FENCES,
|
|
6371
|
+
schema: _mastra_core_storage.RUN_FENCING_TABLE_SCHEMAS[_mastra_core_storage.TABLE_MEMORY_RUN_FENCES]
|
|
6372
|
+
});
|
|
6373
|
+
const omSchema = withObservationalMemoryLifecycleColumns(_mastra_core_storage.OBSERVATIONAL_MEMORY_TABLE_SCHEMA?.[OM_TABLE]);
|
|
6135
6374
|
if (omSchema) {
|
|
6136
6375
|
await this.operations.createTable({
|
|
6137
6376
|
tableName: OM_TABLE,
|
|
@@ -6154,7 +6393,8 @@ var MemoryMySQL = class MemoryMySQL extends _mastra_core_storage.MemoryStorage {
|
|
|
6154
6393
|
"isBufferingObservation",
|
|
6155
6394
|
"isBufferingReflection",
|
|
6156
6395
|
"lastBufferedAtTokens",
|
|
6157
|
-
"lastBufferedAtTime"
|
|
6396
|
+
"lastBufferedAtTime",
|
|
6397
|
+
"supersededBy"
|
|
6158
6398
|
]
|
|
6159
6399
|
});
|
|
6160
6400
|
}
|
|
@@ -6172,6 +6412,7 @@ var MemoryMySQL = class MemoryMySQL extends _mastra_core_storage.MemoryStorage {
|
|
|
6172
6412
|
if (err?.errno !== 1061) throw err;
|
|
6173
6413
|
}
|
|
6174
6414
|
}
|
|
6415
|
+
if (omSchema) await this.backfillSupersededBy();
|
|
6175
6416
|
await this.createDefaultIndexes();
|
|
6176
6417
|
await this.createCustomIndexes();
|
|
6177
6418
|
}
|
|
@@ -6196,6 +6437,10 @@ var MemoryMySQL = class MemoryMySQL extends _mastra_core_storage.MemoryStorage {
|
|
|
6196
6437
|
tableName,
|
|
6197
6438
|
schema: _mastra_core_storage.TABLE_SCHEMAS[tableName]
|
|
6198
6439
|
}));
|
|
6440
|
+
statements.push(generateTableSQL({
|
|
6441
|
+
tableName: _mastra_core_storage.TABLE_MEMORY_RUN_FENCES,
|
|
6442
|
+
schema: _mastra_core_storage.RUN_FENCING_TABLE_SCHEMAS[_mastra_core_storage.TABLE_MEMORY_RUN_FENCES]
|
|
6443
|
+
}));
|
|
6199
6444
|
for (const idx of MemoryMySQL.getDefaultIndexDefs()) statements.push(generateIndexSQL(idx));
|
|
6200
6445
|
return statements;
|
|
6201
6446
|
}
|
|
@@ -6214,6 +6459,7 @@ var MemoryMySQL = class MemoryMySQL extends _mastra_core_storage.MemoryStorage {
|
|
|
6214
6459
|
await this.pool.execute(`DELETE FROM ${formatTableName(_mastra_core_storage.TABLE_MESSAGES)}`);
|
|
6215
6460
|
await this.pool.execute(`DELETE FROM ${formatTableName(_mastra_core_storage.TABLE_THREADS)}`);
|
|
6216
6461
|
await this.pool.execute(`DELETE FROM ${formatTableName(_mastra_core_storage.TABLE_RESOURCES)}`);
|
|
6462
|
+
await this.pool.execute(`DELETE FROM ${RUN_FENCES_TABLE}`);
|
|
6217
6463
|
try {
|
|
6218
6464
|
await this.pool.execute(`DELETE FROM ${OM_TABLE_QUOTED}`);
|
|
6219
6465
|
} catch (err) {
|
|
@@ -6394,8 +6640,8 @@ var MemoryMySQL = class MemoryMySQL extends _mastra_core_storage.MemoryStorage {
|
|
|
6394
6640
|
}
|
|
6395
6641
|
return includeMessages;
|
|
6396
6642
|
}
|
|
6397
|
-
async upsertThread(thread) {
|
|
6398
|
-
|
|
6643
|
+
async upsertThread(q, thread) {
|
|
6644
|
+
const statement = prepareStatement({
|
|
6399
6645
|
tableName: _mastra_core_storage.TABLE_THREADS,
|
|
6400
6646
|
record: {
|
|
6401
6647
|
id: thread.id,
|
|
@@ -6406,18 +6652,22 @@ var MemoryMySQL = class MemoryMySQL extends _mastra_core_storage.MemoryStorage {
|
|
|
6406
6652
|
updatedAt: thread.updatedAt
|
|
6407
6653
|
}
|
|
6408
6654
|
});
|
|
6655
|
+
await q.execute(statement.sql, statement.args);
|
|
6656
|
+
}
|
|
6657
|
+
async #readThread(q, threadId, resourceId) {
|
|
6658
|
+
let sql = `SELECT * FROM ${formatTableName(_mastra_core_storage.TABLE_THREADS)} WHERE ${quoteIdentifier("id", "column name")} = ?`;
|
|
6659
|
+
const params = [threadId];
|
|
6660
|
+
if (resourceId !== void 0) {
|
|
6661
|
+
sql += ` AND ${quoteIdentifier("resourceId", "column name")} = ?`;
|
|
6662
|
+
params.push(resourceId);
|
|
6663
|
+
}
|
|
6664
|
+
const [rows] = await q.execute(sql, params);
|
|
6665
|
+
const row = rows[0];
|
|
6666
|
+
return row ? this.mapThread(row) : null;
|
|
6409
6667
|
}
|
|
6410
6668
|
async getThreadById({ threadId, resourceId }) {
|
|
6411
6669
|
try {
|
|
6412
|
-
|
|
6413
|
-
const params = [threadId];
|
|
6414
|
-
if (resourceId !== void 0) {
|
|
6415
|
-
sql += ` AND ${quoteIdentifier("resourceId", "column name")} = ?`;
|
|
6416
|
-
params.push(resourceId);
|
|
6417
|
-
}
|
|
6418
|
-
const [rows] = await this.pool.execute(sql, params);
|
|
6419
|
-
const row = rows[0];
|
|
6420
|
-
return row ? this.mapThread(row) : null;
|
|
6670
|
+
return await this.#readThread(this.pool, threadId, resourceId);
|
|
6421
6671
|
} catch (error) {
|
|
6422
6672
|
throw new _mastra_core_error.MastraError({
|
|
6423
6673
|
id: "MYSQL_MEMORY_GET_THREAD_BY_ID_FAILED",
|
|
@@ -6570,11 +6820,12 @@ var MemoryMySQL = class MemoryMySQL extends _mastra_core_storage.MemoryStorage {
|
|
|
6570
6820
|
} : void 0
|
|
6571
6821
|
});
|
|
6572
6822
|
}
|
|
6573
|
-
async saveThread({ thread }) {
|
|
6823
|
+
async saveThread({ thread, fence }) {
|
|
6574
6824
|
try {
|
|
6575
|
-
await this.upsertThread(thread);
|
|
6825
|
+
await this.#fenced(fence, "saveThread", (q) => this.upsertThread(q, thread));
|
|
6576
6826
|
return thread;
|
|
6577
6827
|
} catch (error) {
|
|
6828
|
+
if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
6578
6829
|
throw new _mastra_core_error.MastraError({
|
|
6579
6830
|
id: "MYSQL_MEMORY_SAVE_THREAD_FAILED",
|
|
6580
6831
|
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
@@ -6583,36 +6834,39 @@ var MemoryMySQL = class MemoryMySQL extends _mastra_core_storage.MemoryStorage {
|
|
|
6583
6834
|
}, error);
|
|
6584
6835
|
}
|
|
6585
6836
|
}
|
|
6586
|
-
async updateThread({ id, title, metadata }) {
|
|
6837
|
+
async updateThread({ id, title, metadata, fence }) {
|
|
6587
6838
|
try {
|
|
6588
|
-
|
|
6589
|
-
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
|
|
6601
|
-
|
|
6602
|
-
|
|
6603
|
-
|
|
6604
|
-
|
|
6839
|
+
return await this.#fenced(fence, "updateThread", async (q) => {
|
|
6840
|
+
const existing = await this.#readThread(q, id);
|
|
6841
|
+
if (!existing) throw new _mastra_core_error.MastraError({
|
|
6842
|
+
id: (0, _mastra_core_storage.createStorageErrorId)("MYSQL", "UPDATE_THREAD", "NOT_FOUND"),
|
|
6843
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
6844
|
+
category: _mastra_core_error.ErrorCategory.USER,
|
|
6845
|
+
text: `Thread ${id} not found`,
|
|
6846
|
+
details: { threadId: id }
|
|
6847
|
+
});
|
|
6848
|
+
const mergedMetadata = {
|
|
6849
|
+
...existing.metadata ?? {},
|
|
6850
|
+
...metadata ?? {}
|
|
6851
|
+
};
|
|
6852
|
+
const updatedAt = /* @__PURE__ */ new Date();
|
|
6853
|
+
const statement = prepareUpdateStatement({
|
|
6854
|
+
tableName: _mastra_core_storage.TABLE_THREADS,
|
|
6855
|
+
keys: { id },
|
|
6856
|
+
updates: {
|
|
6857
|
+
title: title ?? existing.title,
|
|
6858
|
+
metadata: JSON.stringify(mergedMetadata),
|
|
6859
|
+
updatedAt
|
|
6860
|
+
}
|
|
6861
|
+
});
|
|
6862
|
+
await q.execute(statement.sql, statement.args);
|
|
6863
|
+
return {
|
|
6864
|
+
...existing,
|
|
6605
6865
|
title: title ?? existing.title,
|
|
6606
|
-
metadata:
|
|
6866
|
+
metadata: mergedMetadata,
|
|
6607
6867
|
updatedAt
|
|
6608
|
-
}
|
|
6868
|
+
};
|
|
6609
6869
|
});
|
|
6610
|
-
return {
|
|
6611
|
-
...existing,
|
|
6612
|
-
title: title ?? existing.title,
|
|
6613
|
-
metadata: mergedMetadata,
|
|
6614
|
-
updatedAt
|
|
6615
|
-
};
|
|
6616
6870
|
} catch (error) {
|
|
6617
6871
|
if (error instanceof _mastra_core_error.MastraError) throw error;
|
|
6618
6872
|
throw new _mastra_core_error.MastraError({
|
|
@@ -6693,6 +6947,7 @@ var MemoryMySQL = class MemoryMySQL extends _mastra_core_storage.MemoryStorage {
|
|
|
6693
6947
|
const insertedIds = [];
|
|
6694
6948
|
try {
|
|
6695
6949
|
await connection.beginTransaction();
|
|
6950
|
+
await this.#assertFence(connection, args.fence, "saveMessages");
|
|
6696
6951
|
for (const message of messages) {
|
|
6697
6952
|
if (!message.threadId) throw new _mastra_core_error.MastraError({
|
|
6698
6953
|
id: (0, _mastra_core_storage.createStorageErrorId)("MYSQL", "SAVE_MESSAGES", "INVALID_INPUT"),
|
|
@@ -6786,18 +7041,29 @@ var MemoryMySQL = class MemoryMySQL extends _mastra_core_storage.MemoryStorage {
|
|
|
6786
7041
|
if (update.threadId && update.threadId !== current.threadId && current.threadId) affectedThreads.set(current.threadId, now);
|
|
6787
7042
|
}
|
|
6788
7043
|
if (!updates.length) return existing;
|
|
6789
|
-
await this.
|
|
6790
|
-
|
|
6791
|
-
|
|
6792
|
-
|
|
6793
|
-
|
|
6794
|
-
|
|
6795
|
-
|
|
6796
|
-
|
|
7044
|
+
await inTransaction(this.pool, async (connection) => {
|
|
7045
|
+
await this.#assertFence(connection, args.fence, "updateMessages");
|
|
7046
|
+
for (const { keys, data } of updates) {
|
|
7047
|
+
const statement = prepareUpdateStatement({
|
|
7048
|
+
tableName: _mastra_core_storage.TABLE_MESSAGES,
|
|
7049
|
+
updates: data,
|
|
7050
|
+
keys
|
|
7051
|
+
});
|
|
7052
|
+
await connection.execute(statement.sql, statement.args);
|
|
7053
|
+
}
|
|
7054
|
+
for (const [threadId, timestamp] of affectedThreads.entries()) {
|
|
7055
|
+
const statement = prepareUpdateStatement({
|
|
7056
|
+
tableName: _mastra_core_storage.TABLE_THREADS,
|
|
7057
|
+
updates: { updatedAt: timestamp },
|
|
7058
|
+
keys: { id: threadId }
|
|
7059
|
+
});
|
|
7060
|
+
await connection.execute(statement.sql, statement.args);
|
|
7061
|
+
}
|
|
6797
7062
|
});
|
|
6798
7063
|
const { messages: updated } = await this.listMessagesById({ messageIds: messages.map((m) => m.id) });
|
|
6799
7064
|
return updated;
|
|
6800
7065
|
} catch (error) {
|
|
7066
|
+
if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
6801
7067
|
throw new _mastra_core_error.MastraError({
|
|
6802
7068
|
id: "MYSQL_MEMORY_UPDATE_MESSAGES_FAILED",
|
|
6803
7069
|
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
@@ -6805,19 +7071,22 @@ var MemoryMySQL = class MemoryMySQL extends _mastra_core_storage.MemoryStorage {
|
|
|
6805
7071
|
}, error);
|
|
6806
7072
|
}
|
|
6807
7073
|
}
|
|
6808
|
-
async deleteMessages(messageIds) {
|
|
7074
|
+
async deleteMessages(messageIds, options) {
|
|
6809
7075
|
if (!messageIds.length) return;
|
|
6810
7076
|
try {
|
|
6811
|
-
|
|
6812
|
-
|
|
6813
|
-
|
|
6814
|
-
|
|
6815
|
-
|
|
6816
|
-
|
|
6817
|
-
|
|
6818
|
-
|
|
6819
|
-
|
|
7077
|
+
await this.#fenced(options?.fence, "deleteMessages", async (q) => {
|
|
7078
|
+
const placeholders = messageIds.map(() => "?").join(", ");
|
|
7079
|
+
const [rows] = await q.execute(`SELECT DISTINCT thread_id FROM ${formatTableName(_mastra_core_storage.TABLE_MESSAGES)} WHERE id IN (${placeholders})`, messageIds);
|
|
7080
|
+
const threadIds = rows.map((row) => row.thread_id).filter((threadId) => Boolean(threadId));
|
|
7081
|
+
await q.execute(`DELETE FROM ${formatTableName(_mastra_core_storage.TABLE_MESSAGES)} WHERE id IN (${placeholders})`, messageIds);
|
|
7082
|
+
if (threadIds.length) {
|
|
7083
|
+
const threadPlaceholders = threadIds.map(() => "?").join(", ");
|
|
7084
|
+
const timestamp = transformToSqlValue(/* @__PURE__ */ new Date());
|
|
7085
|
+
await q.execute(`UPDATE ${formatTableName(_mastra_core_storage.TABLE_THREADS)} SET ${quoteIdentifier("updatedAt", "column name")} = ? WHERE id IN (${threadPlaceholders})`, [timestamp, ...threadIds]);
|
|
7086
|
+
}
|
|
7087
|
+
});
|
|
6820
7088
|
} catch (error) {
|
|
7089
|
+
if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
6821
7090
|
throw new _mastra_core_error.MastraError({
|
|
6822
7091
|
id: "MYSQL_MEMORY_DELETE_MESSAGES_FAILED",
|
|
6823
7092
|
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
@@ -7216,19 +7485,23 @@ var MemoryMySQL = class MemoryMySQL extends _mastra_core_storage.MemoryStorage {
|
|
|
7216
7485
|
}, error);
|
|
7217
7486
|
}
|
|
7218
7487
|
}
|
|
7488
|
+
async #insertResource(q, resource) {
|
|
7489
|
+
const metadataValue = resource.metadata === void 0 || resource.metadata === null ? null : JSON.stringify(resource.metadata);
|
|
7490
|
+
const statement = prepareStatement({
|
|
7491
|
+
tableName: _mastra_core_storage.TABLE_RESOURCES,
|
|
7492
|
+
record: {
|
|
7493
|
+
id: resource.id,
|
|
7494
|
+
workingMemory: resource.workingMemory ?? null,
|
|
7495
|
+
metadata: metadataValue,
|
|
7496
|
+
createdAt: resource.createdAt ?? /* @__PURE__ */ new Date(),
|
|
7497
|
+
updatedAt: resource.updatedAt ?? /* @__PURE__ */ new Date()
|
|
7498
|
+
}
|
|
7499
|
+
});
|
|
7500
|
+
await q.execute(statement.sql, statement.args);
|
|
7501
|
+
}
|
|
7219
7502
|
async saveResource({ resource }) {
|
|
7220
7503
|
try {
|
|
7221
|
-
|
|
7222
|
-
await this.operations.insert({
|
|
7223
|
-
tableName: _mastra_core_storage.TABLE_RESOURCES,
|
|
7224
|
-
record: {
|
|
7225
|
-
id: resource.id,
|
|
7226
|
-
workingMemory: resource.workingMemory ?? null,
|
|
7227
|
-
metadata: metadataValue,
|
|
7228
|
-
createdAt: resource.createdAt ?? /* @__PURE__ */ new Date(),
|
|
7229
|
-
updatedAt: resource.updatedAt ?? /* @__PURE__ */ new Date()
|
|
7230
|
-
}
|
|
7231
|
-
});
|
|
7504
|
+
await this.#insertResource(this.pool, resource);
|
|
7232
7505
|
return resource;
|
|
7233
7506
|
} catch (error) {
|
|
7234
7507
|
throw new _mastra_core_error.MastraError({
|
|
@@ -7239,7 +7512,7 @@ var MemoryMySQL = class MemoryMySQL extends _mastra_core_storage.MemoryStorage {
|
|
|
7239
7512
|
}, error);
|
|
7240
7513
|
}
|
|
7241
7514
|
}
|
|
7242
|
-
async updateResource({ resourceId, workingMemory, metadata }) {
|
|
7515
|
+
async updateResource({ resourceId, workingMemory, metadata, fence }) {
|
|
7243
7516
|
try {
|
|
7244
7517
|
const existing = await this.getResourceById({ resourceId });
|
|
7245
7518
|
if (!existing) {
|
|
@@ -7250,22 +7523,24 @@ var MemoryMySQL = class MemoryMySQL extends _mastra_core_storage.MemoryStorage {
|
|
|
7250
7523
|
createdAt: /* @__PURE__ */ new Date(),
|
|
7251
7524
|
updatedAt: /* @__PURE__ */ new Date()
|
|
7252
7525
|
};
|
|
7253
|
-
|
|
7526
|
+
await this.#fenced(fence, "updateResource", (q) => this.#insertResource(q, newResource));
|
|
7527
|
+
return newResource;
|
|
7254
7528
|
}
|
|
7255
7529
|
const mergedMetadata = metadata !== void 0 ? {
|
|
7256
7530
|
...existing.metadata ?? {},
|
|
7257
7531
|
...metadata ?? {}
|
|
7258
7532
|
} : existing.metadata;
|
|
7259
7533
|
const metadataValue = metadata !== void 0 ? metadata === null ? null : JSON.stringify(mergedMetadata ?? {}) : void 0;
|
|
7260
|
-
|
|
7534
|
+
const statement = prepareUpdateStatement({
|
|
7261
7535
|
tableName: _mastra_core_storage.TABLE_RESOURCES,
|
|
7262
7536
|
keys: { id: resourceId },
|
|
7263
|
-
|
|
7537
|
+
updates: {
|
|
7264
7538
|
...workingMemory !== void 0 ? { workingMemory } : {},
|
|
7265
7539
|
...metadataValue !== void 0 ? { metadata: metadataValue } : {},
|
|
7266
7540
|
updatedAt: /* @__PURE__ */ new Date()
|
|
7267
7541
|
}
|
|
7268
7542
|
});
|
|
7543
|
+
await this.#fenced(fence, "updateResource", (q) => q.execute(statement.sql, statement.args));
|
|
7269
7544
|
const updated = await this.getResourceById({ resourceId });
|
|
7270
7545
|
if (!updated) throw new _mastra_core_error.MastraError({
|
|
7271
7546
|
id: (0, _mastra_core_storage.createStorageErrorId)("MYSQL", "UPDATE_RESOURCE", "NOT_FOUND_AFTER_UPDATE"),
|
|
@@ -7288,7 +7563,7 @@ var MemoryMySQL = class MemoryMySQL extends _mastra_core_storage.MemoryStorage {
|
|
|
7288
7563
|
async getObservationalMemory(threadId, resourceId) {
|
|
7289
7564
|
try {
|
|
7290
7565
|
const lookupKey = this.getOMKey(threadId, resourceId);
|
|
7291
|
-
const [rows] = await this.pool.execute(`SELECT * FROM ${OM_TABLE_QUOTED} WHERE ${omCol("lookupKey")} = ? ORDER BY ${
|
|
7566
|
+
const [rows] = await this.pool.execute(`SELECT * FROM ${OM_TABLE_QUOTED} WHERE ${omCol("lookupKey")} = ? ORDER BY ${OM_HEAD_ORDER} LIMIT 1`, [lookupKey]);
|
|
7292
7567
|
if (!rows || rows.length === 0) return null;
|
|
7293
7568
|
return this.parseOMRow(rows[0]);
|
|
7294
7569
|
} catch (error) {
|
|
@@ -7350,32 +7625,11 @@ var MemoryMySQL = class MemoryMySQL extends _mastra_core_storage.MemoryStorage {
|
|
|
7350
7625
|
}
|
|
7351
7626
|
async initializeObservationalMemory(input) {
|
|
7352
7627
|
try {
|
|
7628
|
+
const lookupKey = this.getOMKey(input.threadId, input.resourceId);
|
|
7353
7629
|
const id = globalThis.crypto.randomUUID();
|
|
7354
7630
|
const now = /* @__PURE__ */ new Date();
|
|
7355
|
-
const
|
|
7356
|
-
|
|
7357
|
-
id,
|
|
7358
|
-
scope: input.scope,
|
|
7359
|
-
threadId: input.threadId,
|
|
7360
|
-
resourceId: input.resourceId,
|
|
7361
|
-
createdAt: now,
|
|
7362
|
-
updatedAt: now,
|
|
7363
|
-
lastObservedAt: void 0,
|
|
7364
|
-
originType: "initial",
|
|
7365
|
-
generationCount: 0,
|
|
7366
|
-
activeObservations: "",
|
|
7367
|
-
totalTokensObserved: 0,
|
|
7368
|
-
observationTokenCount: 0,
|
|
7369
|
-
pendingMessageTokens: 0,
|
|
7370
|
-
isReflecting: false,
|
|
7371
|
-
isObserving: false,
|
|
7372
|
-
isBufferingObservation: false,
|
|
7373
|
-
isBufferingReflection: false,
|
|
7374
|
-
lastBufferedAtTokens: 0,
|
|
7375
|
-
lastBufferedAtTime: null,
|
|
7376
|
-
config: input.config,
|
|
7377
|
-
observedTimezone: input.observedTimezone
|
|
7378
|
-
};
|
|
7631
|
+
const existing = await this.getObservationalMemory(input.threadId, input.resourceId);
|
|
7632
|
+
if (existing) return existing;
|
|
7379
7633
|
const nowSql = transformToSqlValue(now);
|
|
7380
7634
|
const cols = [
|
|
7381
7635
|
"id",
|
|
@@ -7401,36 +7655,53 @@ var MemoryMySQL = class MemoryMySQL extends _mastra_core_storage.MemoryStorage {
|
|
|
7401
7655
|
"lastBufferedAtTime",
|
|
7402
7656
|
"observedTimezone",
|
|
7403
7657
|
"createdAt",
|
|
7404
|
-
"updatedAt"
|
|
7658
|
+
"updatedAt",
|
|
7659
|
+
"supersededBy"
|
|
7405
7660
|
].map(omCol).join(", ");
|
|
7406
|
-
const placeholders = Array.from({ length:
|
|
7407
|
-
|
|
7408
|
-
|
|
7409
|
-
|
|
7410
|
-
|
|
7411
|
-
|
|
7412
|
-
|
|
7413
|
-
|
|
7414
|
-
|
|
7415
|
-
|
|
7416
|
-
|
|
7417
|
-
|
|
7418
|
-
|
|
7419
|
-
|
|
7420
|
-
|
|
7421
|
-
|
|
7422
|
-
|
|
7423
|
-
|
|
7424
|
-
|
|
7425
|
-
|
|
7426
|
-
|
|
7427
|
-
|
|
7428
|
-
|
|
7429
|
-
|
|
7430
|
-
|
|
7431
|
-
|
|
7432
|
-
|
|
7433
|
-
|
|
7661
|
+
const placeholders = Array.from({ length: 25 }, () => "?").join(", ");
|
|
7662
|
+
const lockName = `mastra_om_init:${(0, crypto$1.createHash)("sha256").update(lookupKey).digest("hex").slice(0, 40)}`;
|
|
7663
|
+
const connection = await this.pool.getConnection();
|
|
7664
|
+
try {
|
|
7665
|
+
const [lockRows] = await connection.query("SELECT GET_LOCK(?, ?) AS acquired", [lockName, OM_INIT_LOCK_TIMEOUT_SECONDS]);
|
|
7666
|
+
if (Number(lockRows[0]?.acquired) !== 1) throw new Error(`Timed out waiting for the observational memory initialization lock for ${lookupKey}`);
|
|
7667
|
+
try {
|
|
7668
|
+
const [liveRows] = await connection.query(`SELECT 1 FROM ${OM_TABLE_QUOTED} WHERE ${omCol("lookupKey")} = ? AND ${omCol("supersededBy")} IS NULL LIMIT 1`, [lookupKey]);
|
|
7669
|
+
if (liveRows.length === 0) await connection.execute(`INSERT INTO ${OM_TABLE_QUOTED} (${cols}) VALUES (${placeholders})`, [
|
|
7670
|
+
id,
|
|
7671
|
+
lookupKey,
|
|
7672
|
+
input.scope,
|
|
7673
|
+
input.resourceId,
|
|
7674
|
+
input.threadId || null,
|
|
7675
|
+
"",
|
|
7676
|
+
null,
|
|
7677
|
+
"initial",
|
|
7678
|
+
JSON.stringify(input.config),
|
|
7679
|
+
0,
|
|
7680
|
+
null,
|
|
7681
|
+
null,
|
|
7682
|
+
0,
|
|
7683
|
+
0,
|
|
7684
|
+
0,
|
|
7685
|
+
false,
|
|
7686
|
+
false,
|
|
7687
|
+
false,
|
|
7688
|
+
false,
|
|
7689
|
+
0,
|
|
7690
|
+
null,
|
|
7691
|
+
input.observedTimezone || null,
|
|
7692
|
+
nowSql,
|
|
7693
|
+
nowSql,
|
|
7694
|
+
null
|
|
7695
|
+
]);
|
|
7696
|
+
} finally {
|
|
7697
|
+
await connection.query("SELECT RELEASE_LOCK(?)", [lockName]);
|
|
7698
|
+
}
|
|
7699
|
+
} finally {
|
|
7700
|
+
connection.release();
|
|
7701
|
+
}
|
|
7702
|
+
const head = await this.getObservationalMemory(input.threadId, input.resourceId);
|
|
7703
|
+
if (!head) throwOMNotFound(id, "INITIALIZE_OBSERVATIONAL_MEMORY");
|
|
7704
|
+
return head;
|
|
7434
7705
|
} catch (error) {
|
|
7435
7706
|
rethrowOrWrapOM(error, "", "INITIALIZE_OBSERVATIONAL_MEMORY", {
|
|
7436
7707
|
threadId: input.threadId,
|
|
@@ -7439,119 +7710,153 @@ var MemoryMySQL = class MemoryMySQL extends _mastra_core_storage.MemoryStorage {
|
|
|
7439
7710
|
}
|
|
7440
7711
|
}
|
|
7441
7712
|
async updateActiveObservations(input) {
|
|
7713
|
+
assertActiveObservationsApplied(await this.commitActiveObservations(input), input.id);
|
|
7714
|
+
}
|
|
7715
|
+
async commitActiveObservations(input) {
|
|
7442
7716
|
try {
|
|
7443
|
-
const now = /* @__PURE__ */ new Date();
|
|
7444
7717
|
const observedMessageIdsJson = input.observedMessageIds ? JSON.stringify(input.observedMessageIds) : null;
|
|
7445
|
-
|
|
7446
|
-
|
|
7447
|
-
|
|
7448
|
-
|
|
7449
|
-
|
|
7450
|
-
|
|
7451
|
-
|
|
7452
|
-
|
|
7453
|
-
|
|
7454
|
-
|
|
7455
|
-
|
|
7456
|
-
input.
|
|
7457
|
-
|
|
7458
|
-
|
|
7459
|
-
|
|
7460
|
-
|
|
7461
|
-
|
|
7462
|
-
|
|
7718
|
+
return await this.withOMTransaction(async (connection) => {
|
|
7719
|
+
const row = await this.lockOMRow(connection, input.id);
|
|
7720
|
+
if (!row) throwOMNotFound(input.id, "UPDATE_ACTIVE_OBSERVATIONS");
|
|
7721
|
+
if (row.supersededBy) return {
|
|
7722
|
+
applied: false,
|
|
7723
|
+
reason: "retired"
|
|
7724
|
+
};
|
|
7725
|
+
if (input.expectedActiveObservations !== void 0 && input.expectedActiveObservations !== (row.activeObservations || "")) return {
|
|
7726
|
+
applied: false,
|
|
7727
|
+
reason: "conflict"
|
|
7728
|
+
};
|
|
7729
|
+
const lastObservedAt = maxObservationCursor(parseDateTime(row.lastObservedAt), input.lastObservedAt);
|
|
7730
|
+
await connection.execute(`UPDATE ${OM_TABLE_QUOTED} SET
|
|
7731
|
+
${omCol("activeObservations")} = ?,
|
|
7732
|
+
${omCol("lastObservedAt")} = ?,
|
|
7733
|
+
${omCol("pendingMessageTokens")} = 0,
|
|
7734
|
+
${omCol("observationTokenCount")} = ?,
|
|
7735
|
+
${omCol("totalTokensObserved")} = ${omCol("totalTokensObserved")} + ?,
|
|
7736
|
+
${omCol("observedMessageIds")} = ?,
|
|
7737
|
+
${omCol("updatedAt")} = ?
|
|
7738
|
+
WHERE ${omCol("id")} = ?`, [
|
|
7739
|
+
input.observations,
|
|
7740
|
+
transformToSqlValue(lastObservedAt),
|
|
7741
|
+
input.tokenCount,
|
|
7742
|
+
input.tokenCount,
|
|
7743
|
+
observedMessageIdsJson,
|
|
7744
|
+
transformToSqlValue(/* @__PURE__ */ new Date()),
|
|
7745
|
+
input.id
|
|
7746
|
+
]);
|
|
7747
|
+
return { applied: true };
|
|
7748
|
+
}, "commitActiveObservations");
|
|
7463
7749
|
} catch (error) {
|
|
7464
7750
|
rethrowOrWrapOM(error, input.id, "UPDATE_ACTIVE_OBSERVATIONS");
|
|
7465
7751
|
}
|
|
7466
7752
|
}
|
|
7467
7753
|
async createReflectionGeneration(input) {
|
|
7468
7754
|
try {
|
|
7469
|
-
const
|
|
7470
|
-
|
|
7471
|
-
|
|
7472
|
-
|
|
7473
|
-
|
|
7474
|
-
|
|
7475
|
-
|
|
7476
|
-
|
|
7477
|
-
|
|
7478
|
-
|
|
7479
|
-
|
|
7480
|
-
|
|
7481
|
-
|
|
7482
|
-
|
|
7483
|
-
|
|
7484
|
-
|
|
7485
|
-
|
|
7486
|
-
isReflecting: false,
|
|
7487
|
-
isObserving: false,
|
|
7488
|
-
isBufferingObservation: false,
|
|
7489
|
-
isBufferingReflection: false,
|
|
7490
|
-
lastBufferedAtTokens: 0,
|
|
7491
|
-
lastBufferedAtTime: null,
|
|
7492
|
-
config: input.currentRecord.config,
|
|
7493
|
-
metadata: input.currentRecord.metadata,
|
|
7494
|
-
observedTimezone: input.currentRecord.observedTimezone
|
|
7495
|
-
};
|
|
7496
|
-
const nowSql = transformToSqlValue(now);
|
|
7497
|
-
const cols = [
|
|
7498
|
-
"id",
|
|
7499
|
-
"lookupKey",
|
|
7500
|
-
"scope",
|
|
7501
|
-
"resourceId",
|
|
7502
|
-
"threadId",
|
|
7503
|
-
"activeObservations",
|
|
7504
|
-
"activeObservationsPendingUpdate",
|
|
7505
|
-
"originType",
|
|
7506
|
-
"config",
|
|
7507
|
-
"generationCount",
|
|
7508
|
-
"lastObservedAt",
|
|
7509
|
-
"lastReflectionAt",
|
|
7510
|
-
"pendingMessageTokens",
|
|
7511
|
-
"totalTokensObserved",
|
|
7512
|
-
"observationTokenCount",
|
|
7513
|
-
"isObserving",
|
|
7514
|
-
"isReflecting",
|
|
7515
|
-
"isBufferingObservation",
|
|
7516
|
-
"isBufferingReflection",
|
|
7517
|
-
"lastBufferedAtTokens",
|
|
7518
|
-
"lastBufferedAtTime",
|
|
7519
|
-
"observedTimezone",
|
|
7520
|
-
"createdAt",
|
|
7521
|
-
"updatedAt"
|
|
7522
|
-
].map(omCol).join(", ");
|
|
7523
|
-
const placeholders = Array.from({ length: 24 }, () => "?").join(", ");
|
|
7524
|
-
await this.pool.execute(`INSERT INTO ${OM_TABLE_QUOTED} (${cols}) VALUES (${placeholders})`, [
|
|
7525
|
-
id,
|
|
7526
|
-
lookupKey,
|
|
7527
|
-
record.scope,
|
|
7528
|
-
record.resourceId,
|
|
7529
|
-
record.threadId || null,
|
|
7530
|
-
input.reflection,
|
|
7531
|
-
null,
|
|
7532
|
-
"reflection",
|
|
7533
|
-
JSON.stringify(record.config),
|
|
7534
|
-
input.currentRecord.generationCount + 1,
|
|
7535
|
-
record.lastObservedAt ? transformToSqlValue(record.lastObservedAt) : null,
|
|
7536
|
-
nowSql,
|
|
7537
|
-
record.pendingMessageTokens,
|
|
7538
|
-
record.totalTokensObserved,
|
|
7539
|
-
record.observationTokenCount,
|
|
7540
|
-
false,
|
|
7541
|
-
false,
|
|
7542
|
-
false,
|
|
7543
|
-
false,
|
|
7544
|
-
0,
|
|
7545
|
-
null,
|
|
7546
|
-
record.observedTimezone || null,
|
|
7547
|
-
nowSql,
|
|
7548
|
-
nowSql
|
|
7549
|
-
]);
|
|
7550
|
-
return record;
|
|
7755
|
+
const { currentRecord } = input;
|
|
7756
|
+
return await this.withOMTransaction(async (connection) => {
|
|
7757
|
+
const row = await this.lockOMRow(connection, currentRecord.id);
|
|
7758
|
+
if (!row) return currentRecord;
|
|
7759
|
+
if (row.supersededBy) return this.parseOMRow(await this.resolveLiveOMRow(connection, row));
|
|
7760
|
+
const stored = this.parseOMRow(row);
|
|
7761
|
+
const plan = planReflectionGenerationText({
|
|
7762
|
+
storedObservations: stored.activeObservations,
|
|
7763
|
+
storedObservationTokenCount: stored.observationTokenCount,
|
|
7764
|
+
snapshotObservations: currentRecord.activeObservations,
|
|
7765
|
+
snapshotObservationTokenCount: currentRecord.observationTokenCount,
|
|
7766
|
+
reflection: input.reflection,
|
|
7767
|
+
tokenCount: input.tokenCount
|
|
7768
|
+
});
|
|
7769
|
+
if (!plan) return stored;
|
|
7770
|
+
return this.rollOverOMRow(connection, stored.id, plan.observations, plan.tokenCount, input.newRecordId, false);
|
|
7771
|
+
}, "createReflectionGeneration");
|
|
7551
7772
|
} catch (error) {
|
|
7552
7773
|
rethrowOrWrapOM(error, input.currentRecord.id, "CREATE_REFLECTION_GENERATION", { currentRecordId: input.currentRecord.id });
|
|
7553
7774
|
}
|
|
7554
7775
|
}
|
|
7776
|
+
async #insertReflectionGeneration(q, input) {
|
|
7777
|
+
const id = globalThis.crypto.randomUUID();
|
|
7778
|
+
const now = /* @__PURE__ */ new Date();
|
|
7779
|
+
const lookupKey = this.getOMKey(input.currentRecord.threadId, input.currentRecord.resourceId);
|
|
7780
|
+
const record = {
|
|
7781
|
+
id,
|
|
7782
|
+
scope: input.currentRecord.scope,
|
|
7783
|
+
threadId: input.currentRecord.threadId,
|
|
7784
|
+
resourceId: input.currentRecord.resourceId,
|
|
7785
|
+
createdAt: now,
|
|
7786
|
+
updatedAt: now,
|
|
7787
|
+
lastObservedAt: input.currentRecord.lastObservedAt,
|
|
7788
|
+
originType: "reflection",
|
|
7789
|
+
generationCount: input.currentRecord.generationCount + 1,
|
|
7790
|
+
activeObservations: input.reflection,
|
|
7791
|
+
totalTokensObserved: input.currentRecord.totalTokensObserved,
|
|
7792
|
+
observationTokenCount: input.tokenCount,
|
|
7793
|
+
pendingMessageTokens: 0,
|
|
7794
|
+
isReflecting: false,
|
|
7795
|
+
isObserving: false,
|
|
7796
|
+
isBufferingObservation: false,
|
|
7797
|
+
isBufferingReflection: false,
|
|
7798
|
+
lastBufferedAtTokens: 0,
|
|
7799
|
+
lastBufferedAtTime: null,
|
|
7800
|
+
config: input.currentRecord.config,
|
|
7801
|
+
metadata: input.currentRecord.metadata,
|
|
7802
|
+
observedTimezone: input.currentRecord.observedTimezone
|
|
7803
|
+
};
|
|
7804
|
+
const nowSql = transformToSqlValue(now);
|
|
7805
|
+
const cols = [
|
|
7806
|
+
"id",
|
|
7807
|
+
"lookupKey",
|
|
7808
|
+
"scope",
|
|
7809
|
+
"resourceId",
|
|
7810
|
+
"threadId",
|
|
7811
|
+
"activeObservations",
|
|
7812
|
+
"activeObservationsPendingUpdate",
|
|
7813
|
+
"originType",
|
|
7814
|
+
"config",
|
|
7815
|
+
"generationCount",
|
|
7816
|
+
"lastObservedAt",
|
|
7817
|
+
"lastReflectionAt",
|
|
7818
|
+
"pendingMessageTokens",
|
|
7819
|
+
"totalTokensObserved",
|
|
7820
|
+
"observationTokenCount",
|
|
7821
|
+
"isObserving",
|
|
7822
|
+
"isReflecting",
|
|
7823
|
+
"isBufferingObservation",
|
|
7824
|
+
"isBufferingReflection",
|
|
7825
|
+
"lastBufferedAtTokens",
|
|
7826
|
+
"lastBufferedAtTime",
|
|
7827
|
+
"observedTimezone",
|
|
7828
|
+
"createdAt",
|
|
7829
|
+
"updatedAt"
|
|
7830
|
+
].map(omCol).join(", ");
|
|
7831
|
+
const placeholders = Array.from({ length: 24 }, () => "?").join(", ");
|
|
7832
|
+
await q.execute(`INSERT INTO ${OM_TABLE_QUOTED} (${cols}) VALUES (${placeholders})`, [
|
|
7833
|
+
id,
|
|
7834
|
+
lookupKey,
|
|
7835
|
+
record.scope,
|
|
7836
|
+
record.resourceId,
|
|
7837
|
+
record.threadId || null,
|
|
7838
|
+
input.reflection,
|
|
7839
|
+
null,
|
|
7840
|
+
"reflection",
|
|
7841
|
+
JSON.stringify(record.config),
|
|
7842
|
+
input.currentRecord.generationCount + 1,
|
|
7843
|
+
record.lastObservedAt ? transformToSqlValue(record.lastObservedAt) : null,
|
|
7844
|
+
nowSql,
|
|
7845
|
+
record.pendingMessageTokens,
|
|
7846
|
+
record.totalTokensObserved,
|
|
7847
|
+
record.observationTokenCount,
|
|
7848
|
+
false,
|
|
7849
|
+
false,
|
|
7850
|
+
false,
|
|
7851
|
+
false,
|
|
7852
|
+
0,
|
|
7853
|
+
null,
|
|
7854
|
+
record.observedTimezone || null,
|
|
7855
|
+
nowSql,
|
|
7856
|
+
nowSql
|
|
7857
|
+
]);
|
|
7858
|
+
return record;
|
|
7859
|
+
}
|
|
7555
7860
|
async setReflectingFlag(id, isReflecting) {
|
|
7556
7861
|
await this.updateOMFlag(id, "isReflecting", isReflecting, "SET_REFLECTING_FLAG");
|
|
7557
7862
|
}
|
|
@@ -7560,20 +7865,22 @@ var MemoryMySQL = class MemoryMySQL extends _mastra_core_storage.MemoryStorage {
|
|
|
7560
7865
|
}
|
|
7561
7866
|
async setBufferingObservationFlag(id, isBuffering, lastBufferedAtTokens) {
|
|
7562
7867
|
try {
|
|
7563
|
-
|
|
7564
|
-
|
|
7565
|
-
|
|
7566
|
-
|
|
7567
|
-
|
|
7568
|
-
|
|
7569
|
-
|
|
7570
|
-
|
|
7571
|
-
|
|
7572
|
-
|
|
7573
|
-
|
|
7574
|
-
|
|
7575
|
-
|
|
7576
|
-
|
|
7868
|
+
await this.withOMTransaction(async (connection) => {
|
|
7869
|
+
const row = await this.lockOMRow(connection, id);
|
|
7870
|
+
if (!row) throwOMNotFound(id, "SET_BUFFERING_OBSERVATION_FLAG");
|
|
7871
|
+
const target = await this.resolveLiveOMRow(connection, row);
|
|
7872
|
+
const sets = [
|
|
7873
|
+
`${omCol("isBufferingObservation")} = ?`,
|
|
7874
|
+
...lastBufferedAtTokens !== void 0 ? [`${omCol("lastBufferedAtTokens")} = ?`] : [],
|
|
7875
|
+
`${omCol("updatedAt")} = ?`
|
|
7876
|
+
].join(", ");
|
|
7877
|
+
await connection.execute(`UPDATE ${OM_TABLE_QUOTED} SET ${sets} WHERE ${omCol("id")} = ?`, [
|
|
7878
|
+
isBuffering,
|
|
7879
|
+
...lastBufferedAtTokens !== void 0 ? [lastBufferedAtTokens] : [],
|
|
7880
|
+
transformToSqlValue(/* @__PURE__ */ new Date()),
|
|
7881
|
+
target.id
|
|
7882
|
+
]);
|
|
7883
|
+
});
|
|
7577
7884
|
} catch (error) {
|
|
7578
7885
|
rethrowOrWrapOM(error, id, "SET_BUFFERING_OBSERVATION_FLAG");
|
|
7579
7886
|
}
|
|
@@ -7594,83 +7901,78 @@ var MemoryMySQL = class MemoryMySQL extends _mastra_core_storage.MemoryStorage {
|
|
|
7594
7901
|
}
|
|
7595
7902
|
async setPendingMessageTokens(id, tokenCount) {
|
|
7596
7903
|
try {
|
|
7597
|
-
|
|
7598
|
-
|
|
7599
|
-
|
|
7600
|
-
|
|
7601
|
-
|
|
7602
|
-
|
|
7603
|
-
|
|
7604
|
-
|
|
7605
|
-
|
|
7904
|
+
await this.withOMTransaction(async (connection) => {
|
|
7905
|
+
const row = await this.lockOMRow(connection, id);
|
|
7906
|
+
if (!row) throwOMNotFound(id, "SET_PENDING_MESSAGE_TOKENS");
|
|
7907
|
+
const target = await this.resolveLiveOMRow(connection, row);
|
|
7908
|
+
await connection.execute(`UPDATE ${OM_TABLE_QUOTED} SET
|
|
7909
|
+
${omCol("pendingMessageTokens")} = ?,
|
|
7910
|
+
${omCol("updatedAt")} = ?
|
|
7911
|
+
WHERE ${omCol("id")} = ?`, [
|
|
7912
|
+
tokenCount,
|
|
7913
|
+
transformToSqlValue(/* @__PURE__ */ new Date()),
|
|
7914
|
+
target.id
|
|
7915
|
+
]);
|
|
7916
|
+
});
|
|
7606
7917
|
} catch (error) {
|
|
7607
7918
|
rethrowOrWrapOM(error, id, "SET_PENDING_MESSAGE_TOKENS");
|
|
7608
7919
|
}
|
|
7609
7920
|
}
|
|
7610
7921
|
async updateBufferedObservations(input) {
|
|
7611
|
-
|
|
7612
|
-
|
|
7613
|
-
|
|
7614
|
-
|
|
7615
|
-
|
|
7616
|
-
|
|
7617
|
-
|
|
7618
|
-
|
|
7619
|
-
|
|
7620
|
-
|
|
7621
|
-
|
|
7622
|
-
|
|
7623
|
-
|
|
7624
|
-
|
|
7625
|
-
|
|
7626
|
-
|
|
7627
|
-
|
|
7628
|
-
|
|
7629
|
-
|
|
7630
|
-
|
|
7631
|
-
|
|
7632
|
-
|
|
7633
|
-
|
|
7634
|
-
|
|
7635
|
-
|
|
7636
|
-
|
|
7922
|
+
await this.appendBufferedObservations(input);
|
|
7923
|
+
}
|
|
7924
|
+
async appendBufferedObservations(input) {
|
|
7925
|
+
try {
|
|
7926
|
+
const newChunk = {
|
|
7927
|
+
id: `ombuf-${globalThis.crypto.randomUUID()}`,
|
|
7928
|
+
cycleId: input.chunk.cycleId,
|
|
7929
|
+
observations: input.chunk.observations,
|
|
7930
|
+
tokenCount: input.chunk.tokenCount,
|
|
7931
|
+
messageIds: input.chunk.messageIds,
|
|
7932
|
+
messageTokens: input.chunk.messageTokens,
|
|
7933
|
+
lastObservedAt: input.chunk.lastObservedAt,
|
|
7934
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
7935
|
+
suggestedContinuation: input.chunk.suggestedContinuation,
|
|
7936
|
+
currentTask: input.chunk.currentTask,
|
|
7937
|
+
threadTitle: input.chunk.threadTitle,
|
|
7938
|
+
extractedValues: input.chunk.extractedValues,
|
|
7939
|
+
extractionFailures: input.chunk.extractionFailures
|
|
7940
|
+
};
|
|
7941
|
+
return await this.withOMTransaction(async (connection) => {
|
|
7942
|
+
const row = await this.lockOMRow(connection, input.id);
|
|
7943
|
+
if (!row) throwOMNotFound(input.id, "UPDATE_BUFFERED_OBSERVATIONS");
|
|
7944
|
+
const target = await this.resolveLiveOMRow(connection, row);
|
|
7945
|
+
const existingChunks = parseBufferedChunks(target.bufferedObservationChunks);
|
|
7946
|
+
if (existingChunks.some((existing) => existing.cycleId === input.chunk.cycleId) || isBufferedChunkCoveredByCursor(input.chunk.lastObservedAt, parseDateTime(target.lastObservedAt))) return {
|
|
7947
|
+
persisted: false,
|
|
7948
|
+
recordId: target.id
|
|
7637
7949
|
};
|
|
7638
|
-
const
|
|
7639
|
-
|
|
7640
|
-
const [result] = await connection.execute(`UPDATE ${OM_TABLE_QUOTED} SET
|
|
7950
|
+
const lastBufferedAtTime = maxObservationCursor(parseDateTime(target.lastBufferedAtTime), input.lastBufferedAtTime ?? void 0);
|
|
7951
|
+
await connection.execute(`UPDATE ${OM_TABLE_QUOTED} SET
|
|
7641
7952
|
${omCol("bufferedObservationChunks")} = ?,
|
|
7642
|
-
${omCol("lastBufferedAtTime")} =
|
|
7953
|
+
${omCol("lastBufferedAtTime")} = ?,
|
|
7643
7954
|
${omCol("updatedAt")} = ?
|
|
7644
7955
|
WHERE ${omCol("id")} = ?`, [
|
|
7645
|
-
JSON.stringify(
|
|
7646
|
-
lastBufferedAtTime,
|
|
7647
|
-
|
|
7648
|
-
|
|
7956
|
+
JSON.stringify([...existingChunks, newChunk]),
|
|
7957
|
+
transformToSqlValue(lastBufferedAtTime),
|
|
7958
|
+
transformToSqlValue(/* @__PURE__ */ new Date()),
|
|
7959
|
+
target.id
|
|
7649
7960
|
]);
|
|
7650
|
-
|
|
7651
|
-
|
|
7652
|
-
|
|
7653
|
-
|
|
7654
|
-
|
|
7655
|
-
} finally {
|
|
7656
|
-
connection.release();
|
|
7657
|
-
}
|
|
7961
|
+
return {
|
|
7962
|
+
persisted: true,
|
|
7963
|
+
recordId: target.id
|
|
7964
|
+
};
|
|
7965
|
+
}, "appendBufferedObservations");
|
|
7658
7966
|
} catch (error) {
|
|
7659
7967
|
rethrowOrWrapOM(error, input.id, "UPDATE_BUFFERED_OBSERVATIONS");
|
|
7660
7968
|
}
|
|
7661
7969
|
}
|
|
7662
7970
|
async swapBufferedToActive(input) {
|
|
7663
|
-
const connection = await this.pool.getConnection();
|
|
7664
7971
|
try {
|
|
7665
|
-
await
|
|
7666
|
-
|
|
7667
|
-
|
|
7668
|
-
|
|
7669
|
-
const row = currentRows[0];
|
|
7670
|
-
const chunks = parseBufferedChunks(row.bufferedObservationChunks);
|
|
7671
|
-
if (chunks.length === 0) {
|
|
7672
|
-
await connection.commit();
|
|
7673
|
-
return {
|
|
7972
|
+
return await this.withOMTransaction(async (connection) => {
|
|
7973
|
+
const row = await this.lockOMRow(connection, input.id);
|
|
7974
|
+
if (!row) throwOMNotFound(input.id, "SWAP_BUFFERED_TO_ACTIVE");
|
|
7975
|
+
const emptyResult = {
|
|
7674
7976
|
chunksActivated: 0,
|
|
7675
7977
|
messageTokensActivated: 0,
|
|
7676
7978
|
observationTokensActivated: 0,
|
|
@@ -7678,102 +7980,110 @@ var MemoryMySQL = class MemoryMySQL extends _mastra_core_storage.MemoryStorage {
|
|
|
7678
7980
|
activatedCycleIds: [],
|
|
7679
7981
|
activatedMessageIds: []
|
|
7680
7982
|
};
|
|
7681
|
-
|
|
7682
|
-
|
|
7683
|
-
|
|
7684
|
-
|
|
7685
|
-
|
|
7686
|
-
|
|
7687
|
-
|
|
7688
|
-
|
|
7689
|
-
|
|
7690
|
-
|
|
7691
|
-
|
|
7692
|
-
|
|
7693
|
-
|
|
7694
|
-
|
|
7695
|
-
|
|
7983
|
+
if (row.supersededBy) return {
|
|
7984
|
+
...emptyResult,
|
|
7985
|
+
retired: true
|
|
7986
|
+
};
|
|
7987
|
+
const persistedChunks = parseBufferedChunks(row.bufferedObservationChunks);
|
|
7988
|
+
const refreshedWeights = new Map((Array.isArray(input.bufferedChunks) ? input.bufferedChunks : []).map((c) => [c.id, c.messageTokens]));
|
|
7989
|
+
const chunks = persistedChunks.map((c) => refreshedWeights.has(c.id) ? {
|
|
7990
|
+
...c,
|
|
7991
|
+
messageTokens: refreshedWeights.get(c.id)
|
|
7992
|
+
} : c);
|
|
7993
|
+
if (chunks.length === 0) return emptyResult;
|
|
7994
|
+
const retentionFloor = input.messageTokensThreshold * (1 - input.activationRatio);
|
|
7995
|
+
const targetMessageTokens = Math.max(0, input.currentPendingTokens - retentionFloor);
|
|
7996
|
+
let cumulativeMessageTokens = 0;
|
|
7997
|
+
let bestOverBoundary = 0;
|
|
7998
|
+
let bestOverTokens = 0;
|
|
7999
|
+
let bestUnderBoundary = 0;
|
|
8000
|
+
let bestUnderTokens = 0;
|
|
8001
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
8002
|
+
cumulativeMessageTokens += chunks[i].messageTokens ?? 0;
|
|
8003
|
+
const boundary = i + 1;
|
|
8004
|
+
if (cumulativeMessageTokens >= targetMessageTokens) {
|
|
8005
|
+
if (bestOverBoundary === 0 || cumulativeMessageTokens < bestOverTokens) {
|
|
8006
|
+
bestOverBoundary = boundary;
|
|
8007
|
+
bestOverTokens = cumulativeMessageTokens;
|
|
8008
|
+
}
|
|
8009
|
+
} else if (cumulativeMessageTokens > bestUnderTokens) {
|
|
8010
|
+
bestUnderBoundary = boundary;
|
|
8011
|
+
bestUnderTokens = cumulativeMessageTokens;
|
|
7696
8012
|
}
|
|
7697
|
-
}
|
|
7698
|
-
|
|
7699
|
-
|
|
7700
|
-
|
|
7701
|
-
|
|
7702
|
-
|
|
7703
|
-
|
|
7704
|
-
|
|
7705
|
-
|
|
7706
|
-
|
|
7707
|
-
|
|
7708
|
-
|
|
7709
|
-
|
|
7710
|
-
|
|
7711
|
-
|
|
7712
|
-
|
|
7713
|
-
|
|
7714
|
-
|
|
7715
|
-
|
|
7716
|
-
|
|
7717
|
-
|
|
7718
|
-
|
|
7719
|
-
|
|
7720
|
-
|
|
7721
|
-
|
|
7722
|
-
|
|
7723
|
-
|
|
7724
|
-
|
|
7725
|
-
|
|
7726
|
-
|
|
7727
|
-
|
|
7728
|
-
|
|
7729
|
-
|
|
7730
|
-
|
|
7731
|
-
|
|
7732
|
-
|
|
7733
|
-
${omCol("
|
|
7734
|
-
|
|
7735
|
-
|
|
7736
|
-
|
|
7737
|
-
|
|
7738
|
-
|
|
7739
|
-
|
|
7740
|
-
|
|
7741
|
-
|
|
7742
|
-
|
|
7743
|
-
|
|
7744
|
-
|
|
7745
|
-
|
|
7746
|
-
|
|
7747
|
-
|
|
7748
|
-
|
|
7749
|
-
|
|
7750
|
-
|
|
7751
|
-
|
|
7752
|
-
|
|
7753
|
-
|
|
7754
|
-
|
|
7755
|
-
|
|
7756
|
-
|
|
7757
|
-
|
|
7758
|
-
|
|
7759
|
-
|
|
7760
|
-
|
|
7761
|
-
|
|
7762
|
-
suggestedContinuation: latestChunkHints?.suggestedContinuation ?? void 0,
|
|
7763
|
-
currentTask: latestChunkHints?.currentTask ?? void 0
|
|
7764
|
-
};
|
|
8013
|
+
}
|
|
8014
|
+
const maxOvershoot = retentionFloor * .95;
|
|
8015
|
+
const overshoot = bestOverTokens - targetMessageTokens;
|
|
8016
|
+
const remainingAfterOver = input.currentPendingTokens - bestOverTokens;
|
|
8017
|
+
const remainingAfterUnder = input.currentPendingTokens - bestUnderTokens;
|
|
8018
|
+
const minRemaining = Math.min(1e3, retentionFloor);
|
|
8019
|
+
let chunksToActivate;
|
|
8020
|
+
if (input.forceMaxActivation && bestOverBoundary > 0 && remainingAfterOver >= minRemaining) chunksToActivate = bestOverBoundary;
|
|
8021
|
+
else if (bestOverBoundary > 0 && overshoot <= maxOvershoot && remainingAfterOver >= minRemaining) chunksToActivate = bestOverBoundary;
|
|
8022
|
+
else if (bestUnderBoundary > 0 && remainingAfterUnder >= minRemaining) chunksToActivate = bestUnderBoundary;
|
|
8023
|
+
else if (bestOverBoundary > 0) chunksToActivate = bestOverBoundary;
|
|
8024
|
+
else chunksToActivate = 1;
|
|
8025
|
+
const activatedChunks = chunks.slice(0, chunksToActivate);
|
|
8026
|
+
const remainingChunks = persistedChunks.slice(chunksToActivate);
|
|
8027
|
+
const activatedContent = activatedChunks.map((c) => c.observations).join("\n\n");
|
|
8028
|
+
const activatedTokens = activatedChunks.reduce((sum, c) => sum + c.tokenCount, 0);
|
|
8029
|
+
const activatedMessageTokens = activatedChunks.reduce((sum, c) => sum + (c.messageTokens ?? 0), 0);
|
|
8030
|
+
const activatedMessageCount = activatedChunks.reduce((sum, c) => sum + c.messageIds.length, 0);
|
|
8031
|
+
const activatedCycleIds = activatedChunks.map((c) => c.cycleId).filter((cid) => !!cid);
|
|
8032
|
+
const activatedMessageIds = activatedChunks.flatMap((c) => c.messageIds ?? []);
|
|
8033
|
+
const latestChunk = activatedChunks[activatedChunks.length - 1];
|
|
8034
|
+
const derivedLastObservedAt = input.lastObservedAt ?? (latestChunk?.lastObservedAt ? new Date(latestChunk.lastObservedAt) : /* @__PURE__ */ new Date());
|
|
8035
|
+
const lastObservedAt = maxObservationCursor(parseDateTime(row.lastObservedAt), derivedLastObservedAt);
|
|
8036
|
+
const existingActive = row.activeObservations || "";
|
|
8037
|
+
const existingTokenCount = Number(row.observationTokenCount || 0);
|
|
8038
|
+
const newActive = existingActive ? `${existingActive}\n\n${activatedContent}` : activatedContent;
|
|
8039
|
+
const newTokenCount = existingTokenCount + activatedTokens;
|
|
8040
|
+
const existingPending = Number(row.pendingMessageTokens || 0);
|
|
8041
|
+
const newPending = Math.max(0, existingPending - activatedMessageTokens);
|
|
8042
|
+
await connection.execute(`UPDATE ${OM_TABLE_QUOTED} SET
|
|
8043
|
+
${omCol("activeObservations")} = ?,
|
|
8044
|
+
${omCol("observationTokenCount")} = ?,
|
|
8045
|
+
${omCol("pendingMessageTokens")} = ?,
|
|
8046
|
+
${omCol("bufferedObservationChunks")} = ?,
|
|
8047
|
+
${omCol("lastObservedAt")} = ?,
|
|
8048
|
+
${omCol("updatedAt")} = ?
|
|
8049
|
+
WHERE ${omCol("id")} = ?`, [
|
|
8050
|
+
newActive,
|
|
8051
|
+
newTokenCount,
|
|
8052
|
+
newPending,
|
|
8053
|
+
remainingChunks.length > 0 ? JSON.stringify(remainingChunks) : null,
|
|
8054
|
+
transformToSqlValue(lastObservedAt),
|
|
8055
|
+
transformToSqlValue(/* @__PURE__ */ new Date()),
|
|
8056
|
+
input.id
|
|
8057
|
+
]);
|
|
8058
|
+
const latestChunkHints = activatedChunks[activatedChunks.length - 1];
|
|
8059
|
+
return {
|
|
8060
|
+
chunksActivated: activatedChunks.length,
|
|
8061
|
+
messageTokensActivated: activatedMessageTokens,
|
|
8062
|
+
observationTokensActivated: activatedTokens,
|
|
8063
|
+
messagesActivated: activatedMessageCount,
|
|
8064
|
+
activatedCycleIds,
|
|
8065
|
+
activatedMessageIds,
|
|
8066
|
+
observations: activatedContent,
|
|
8067
|
+
perChunk: activatedChunks.map((c) => ({
|
|
8068
|
+
cycleId: c.cycleId ?? "",
|
|
8069
|
+
messageTokens: c.messageTokens ?? 0,
|
|
8070
|
+
observationTokens: c.tokenCount,
|
|
8071
|
+
messageCount: c.messageIds.length,
|
|
8072
|
+
observations: c.observations
|
|
8073
|
+
})),
|
|
8074
|
+
suggestedContinuation: latestChunkHints?.suggestedContinuation ?? void 0,
|
|
8075
|
+
currentTask: latestChunkHints?.currentTask ?? void 0
|
|
8076
|
+
};
|
|
8077
|
+
}, "swapBufferedToActive");
|
|
7765
8078
|
} catch (error) {
|
|
7766
|
-
await connection.rollback();
|
|
7767
8079
|
rethrowOrWrapOM(error, input.id, "SWAP_BUFFERED_TO_ACTIVE");
|
|
7768
|
-
} finally {
|
|
7769
|
-
connection.release();
|
|
7770
8080
|
}
|
|
7771
8081
|
}
|
|
7772
8082
|
async updateBufferedReflection(input) {
|
|
7773
8083
|
try {
|
|
7774
8084
|
const nowSql = transformToSqlValue(/* @__PURE__ */ new Date());
|
|
7775
8085
|
const br = omCol("bufferedReflection");
|
|
7776
|
-
const [result] = await this.
|
|
8086
|
+
const [result] = await this.#fenced(void 0, "updateBufferedReflection", (q) => q.execute(`UPDATE ${OM_TABLE_QUOTED} SET
|
|
7777
8087
|
${br} = CASE
|
|
7778
8088
|
WHEN ${br} IS NOT NULL AND ${br} != ''
|
|
7779
8089
|
THEN CONCAT(${br}, CHAR(10), CHAR(10), ?)
|
|
@@ -7791,7 +8101,7 @@ var MemoryMySQL = class MemoryMySQL extends _mastra_core_storage.MemoryStorage {
|
|
|
7791
8101
|
input.reflectedObservationLineCount,
|
|
7792
8102
|
nowSql,
|
|
7793
8103
|
input.id
|
|
7794
|
-
]);
|
|
8104
|
+
]));
|
|
7795
8105
|
if (result.affectedRows === 0) throwOMNotFound(input.id, "UPDATE_BUFFERED_REFLECTION");
|
|
7796
8106
|
} catch (error) {
|
|
7797
8107
|
rethrowOrWrapOM(error, input.id, "UPDATE_BUFFERED_REFLECTION");
|
|
@@ -7799,38 +8109,183 @@ var MemoryMySQL = class MemoryMySQL extends _mastra_core_storage.MemoryStorage {
|
|
|
7799
8109
|
}
|
|
7800
8110
|
async swapBufferedReflectionToActive(input) {
|
|
7801
8111
|
try {
|
|
7802
|
-
const
|
|
7803
|
-
|
|
7804
|
-
|
|
7805
|
-
|
|
7806
|
-
|
|
7807
|
-
|
|
7808
|
-
|
|
7809
|
-
|
|
7810
|
-
|
|
7811
|
-
|
|
7812
|
-
|
|
7813
|
-
|
|
7814
|
-
|
|
7815
|
-
|
|
7816
|
-
|
|
7817
|
-
|
|
7818
|
-
|
|
7819
|
-
|
|
7820
|
-
|
|
7821
|
-
|
|
7822
|
-
|
|
7823
|
-
${omCol("bufferedReflection")} = NULL,
|
|
7824
|
-
${omCol("bufferedReflectionTokens")} = NULL,
|
|
7825
|
-
${omCol("bufferedReflectionInputTokens")} = NULL,
|
|
7826
|
-
${omCol("reflectedObservationLineCount")} = NULL,
|
|
7827
|
-
${omCol("updatedAt")} = ?
|
|
7828
|
-
WHERE ${omCol("id")} = ?`, [nowSql, input.currentRecord.id]);
|
|
7829
|
-
return newRecord;
|
|
8112
|
+
const { currentRecord } = input;
|
|
8113
|
+
return await this.withOMTransaction(async (connection) => {
|
|
8114
|
+
const row = await this.lockOMRow(connection, currentRecord.id);
|
|
8115
|
+
if (!row) return currentRecord;
|
|
8116
|
+
if (row.supersededBy) return this.parseOMRow(await this.resolveLiveOMRow(connection, row));
|
|
8117
|
+
const stored = this.parseOMRow(row);
|
|
8118
|
+
const bufferedReflection = stored.bufferedReflection || "";
|
|
8119
|
+
if (!bufferedReflection) throw new _mastra_core_error.MastraError({
|
|
8120
|
+
id: (0, _mastra_core_storage.createStorageErrorId)("MYSQL", "SWAP_BUFFERED_REFLECTION_TO_ACTIVE", "NO_CONTENT"),
|
|
8121
|
+
text: "No buffered reflection to swap",
|
|
8122
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
8123
|
+
category: _mastra_core_error.ErrorCategory.USER,
|
|
8124
|
+
details: { id: currentRecord.id }
|
|
8125
|
+
});
|
|
8126
|
+
if (!isAppendOnlySince(stored.activeObservations, currentRecord.activeObservations)) return stored;
|
|
8127
|
+
const reflectedLineCount = stored.reflectedObservationLineCount ?? 0;
|
|
8128
|
+
const unreflectedContent = (stored.activeObservations || "").split("\n").slice(reflectedLineCount).join("\n").trim();
|
|
8129
|
+
const newObservations = unreflectedContent ? `${bufferedReflection}\n\n${unreflectedContent}` : bufferedReflection;
|
|
8130
|
+
const tokenCount = input.tokenCount + Math.max(0, (stored.observationTokenCount ?? 0) - (currentRecord.observationTokenCount ?? 0));
|
|
8131
|
+
return this.rollOverOMRow(connection, stored.id, newObservations, tokenCount, input.newRecordId, true);
|
|
8132
|
+
}, "swapBufferedReflectionToActive");
|
|
7830
8133
|
} catch (error) {
|
|
7831
8134
|
rethrowOrWrapOM(error, input.currentRecord.id, "SWAP_BUFFERED_REFLECTION_TO_ACTIVE");
|
|
7832
8135
|
}
|
|
7833
8136
|
}
|
|
8137
|
+
/**
|
|
8138
|
+
* Runs `fn` in one transaction on a dedicated connection. Every OM lifecycle write locks its
|
|
8139
|
+
* target row by primary key (`SELECT … FOR UPDATE`, a record lock — never a range), so the
|
|
8140
|
+
* only expected lock conflict is a wait; a deadlock or lock-wait timeout (e.g. against the
|
|
8141
|
+
* startup backfill's scan) is retried a bounded number of times.
|
|
8142
|
+
*/
|
|
8143
|
+
async withOMTransaction(fn, fencedOperation) {
|
|
8144
|
+
for (let attempt = 1;; attempt++) {
|
|
8145
|
+
const connection = await this.pool.getConnection();
|
|
8146
|
+
try {
|
|
8147
|
+
await connection.beginTransaction();
|
|
8148
|
+
if (fencedOperation) await this.#assertFence(connection, void 0, fencedOperation);
|
|
8149
|
+
const result = await fn(connection);
|
|
8150
|
+
await connection.commit();
|
|
8151
|
+
return result;
|
|
8152
|
+
} catch (error) {
|
|
8153
|
+
await connection.rollback().catch(() => {});
|
|
8154
|
+
if (attempt < OM_MAX_TRANSACTION_ATTEMPTS && isRetryableLockError(error)) continue;
|
|
8155
|
+
throw error;
|
|
8156
|
+
} finally {
|
|
8157
|
+
connection.release();
|
|
8158
|
+
}
|
|
8159
|
+
}
|
|
8160
|
+
}
|
|
8161
|
+
/** Row-locks one OM record by primary key for the rest of the transaction. */
|
|
8162
|
+
async lockOMRow(connection, id) {
|
|
8163
|
+
const [rows] = await connection.execute(`SELECT * FROM ${OM_TABLE_QUOTED} WHERE ${omCol("id")} = ? FOR UPDATE`, [id]);
|
|
8164
|
+
return rows?.[0] ?? null;
|
|
8165
|
+
}
|
|
8166
|
+
/**
|
|
8167
|
+
* The live, row-locked record a lifecycle write aimed at `row` lands on: `row` itself while
|
|
8168
|
+
* live, otherwise the head. The head is found with a plain (non-locking, so no range locks)
|
|
8169
|
+
* canonical head read, then locked by primary key. Under REPEATABLE READ that read can predate
|
|
8170
|
+
* a rollover committed after this transaction started; the locking read sees the latest
|
|
8171
|
+
* committed row, so a head retired since is followed through `supersededBy` (bounded).
|
|
8172
|
+
*/
|
|
8173
|
+
async resolveLiveOMRow(connection, row) {
|
|
8174
|
+
if (!row.supersededBy) return row;
|
|
8175
|
+
const [heads] = await connection.execute(`SELECT id FROM ${OM_TABLE_QUOTED} WHERE ${omCol("lookupKey")} = ? ORDER BY ${OM_HEAD_ORDER} LIMIT 1`, [row.lookupKey]);
|
|
8176
|
+
const headId = heads?.[0]?.id ?? row.supersededBy;
|
|
8177
|
+
let current = await this.lockOMRow(connection, headId) ?? row;
|
|
8178
|
+
for (let hop = 0; current.supersededBy; hop++) {
|
|
8179
|
+
const next = hop < OM_MAX_HEAD_HOPS ? await this.lockOMRow(connection, current.supersededBy) : null;
|
|
8180
|
+
if (!next) throw new _mastra_core_error.MastraError({
|
|
8181
|
+
id: (0, _mastra_core_storage.createStorageErrorId)("MYSQL", "RESOLVE_OBSERVATIONAL_MEMORY_HEAD", "FAILED"),
|
|
8182
|
+
text: `Observational memory record ${row.id} is superseded but no live head was found`,
|
|
8183
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
8184
|
+
category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
|
|
8185
|
+
details: { id: row.id }
|
|
8186
|
+
});
|
|
8187
|
+
current = next;
|
|
8188
|
+
}
|
|
8189
|
+
return current;
|
|
8190
|
+
}
|
|
8191
|
+
/**
|
|
8192
|
+
* Create the next generation from the stored (live, row-locked) record and retire the stored
|
|
8193
|
+
* record in the same transaction. Carried columns are copied in SQL from the stored row, so
|
|
8194
|
+
* nothing round-trips through a client-side date conversion. Buffered chunks move to the new
|
|
8195
|
+
* generation; the cursor, buffering markers, flags, and counters carry over; buffered
|
|
8196
|
+
* reflection state does not (and is cleared on the retired row after a buffered swap).
|
|
8197
|
+
*/
|
|
8198
|
+
async rollOverOMRow(connection, storedId, observations, tokenCount, newRecordId, clearBufferedReflection) {
|
|
8199
|
+
const id = newRecordId ?? globalThis.crypto.randomUUID();
|
|
8200
|
+
const nowSql = transformToSqlValue(/* @__PURE__ */ new Date());
|
|
8201
|
+
const c = omCol;
|
|
8202
|
+
await connection.execute(`INSERT INTO ${OM_TABLE_QUOTED} (
|
|
8203
|
+
${c("id")}, ${c("lookupKey")}, ${c("scope")}, ${c("resourceId")}, ${c("threadId")},
|
|
8204
|
+
${c("activeObservations")}, ${c("activeObservationsPendingUpdate")},
|
|
8205
|
+
${c("originType")}, ${c("config")}, ${c("generationCount")}, ${c("lastObservedAt")}, ${c("lastReflectionAt")},
|
|
8206
|
+
${c("pendingMessageTokens")}, ${c("totalTokensObserved")}, ${c("observationTokenCount")}, ${c("bufferedObservationChunks")},
|
|
8207
|
+
${c("isObserving")}, ${c("isReflecting")}, ${c("isBufferingObservation")}, ${c("isBufferingReflection")},
|
|
8208
|
+
${c("lastBufferedAtTokens")}, ${c("lastBufferedAtTime")},
|
|
8209
|
+
${c("observedTimezone")}, ${c("metadata")}, ${c("supersededBy")}, ${c("createdAt")}, ${c("updatedAt")}
|
|
8210
|
+
)
|
|
8211
|
+
SELECT
|
|
8212
|
+
?, ${c("lookupKey")}, ${c("scope")}, ${c("resourceId")}, ${c("threadId")},
|
|
8213
|
+
?, NULL,
|
|
8214
|
+
'reflection', ${c("config")}, ${c("generationCount")} + 1, ${c("lastObservedAt")}, ?,
|
|
8215
|
+
${c("pendingMessageTokens")}, ${c("totalTokensObserved")}, ?, ${c("bufferedObservationChunks")},
|
|
8216
|
+
false, false, ${c("isBufferingObservation")}, false,
|
|
8217
|
+
${c("lastBufferedAtTokens")}, ${c("lastBufferedAtTime")},
|
|
8218
|
+
${c("observedTimezone")}, ${c("metadata")}, NULL, ?, ?
|
|
8219
|
+
FROM ${OM_TABLE_QUOTED} WHERE ${c("id")} = ?`, [
|
|
8220
|
+
id,
|
|
8221
|
+
observations,
|
|
8222
|
+
nowSql,
|
|
8223
|
+
Math.round(tokenCount),
|
|
8224
|
+
nowSql,
|
|
8225
|
+
nowSql,
|
|
8226
|
+
storedId
|
|
8227
|
+
]);
|
|
8228
|
+
const [retired] = await connection.execute(`UPDATE ${OM_TABLE_QUOTED} SET
|
|
8229
|
+
${c("supersededBy")} = ?,
|
|
8230
|
+
${c("bufferedObservationChunks")} = NULL,
|
|
8231
|
+
${clearBufferedReflection ? `${c("bufferedReflection")} = NULL, ${c("bufferedReflectionTokens")} = NULL,
|
|
8232
|
+
${c("bufferedReflectionInputTokens")} = NULL, ${c("reflectedObservationLineCount")} = NULL,` : ""}
|
|
8233
|
+
${c("updatedAt")} = ?
|
|
8234
|
+
WHERE ${c("id")} = ? AND ${c("supersededBy")} IS NULL`, [
|
|
8235
|
+
id,
|
|
8236
|
+
nowSql,
|
|
8237
|
+
storedId
|
|
8238
|
+
]);
|
|
8239
|
+
if (retired.affectedRows !== 1) throw new _mastra_core_error.MastraError({
|
|
8240
|
+
id: (0, _mastra_core_storage.createStorageErrorId)("MYSQL", "CREATE_REFLECTION_GENERATION", "RETIRE_FAILED"),
|
|
8241
|
+
text: `Failed to retire observational memory record ${storedId}`,
|
|
8242
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
8243
|
+
category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
|
|
8244
|
+
details: { id: storedId }
|
|
8245
|
+
});
|
|
8246
|
+
const inserted = await this.lockOMRow(connection, id);
|
|
8247
|
+
if (!inserted) throwOMNotFound(id, "CREATE_REFLECTION_GENERATION");
|
|
8248
|
+
return this.parseOMRow(inserted);
|
|
8249
|
+
}
|
|
8250
|
+
/**
|
|
8251
|
+
* Retire every live row that sorts after its key's canonical head, marking it superseded by
|
|
8252
|
+
* the head. Rows retired by older adapter versions (which never set `supersededBy`) become
|
|
8253
|
+
* frozen. Only keys with more than one live row are touched; each update embeds the head's
|
|
8254
|
+
* ordering key, so a row created by a concurrent rollover (a newer generation) never matches.
|
|
8255
|
+
* Runs at READ COMMITTED so the scan takes no gap locks.
|
|
8256
|
+
*/
|
|
8257
|
+
async backfillSupersededBy() {
|
|
8258
|
+
const [keys] = await this.pool.execute(`SELECT ${omCol("lookupKey")} AS lookupKey FROM ${OM_TABLE_QUOTED}
|
|
8259
|
+
WHERE ${omCol("supersededBy")} IS NULL GROUP BY ${omCol("lookupKey")} HAVING COUNT(*) > 1`);
|
|
8260
|
+
for (const { lookupKey } of keys ?? []) for (let attempt = 1;; attempt++) {
|
|
8261
|
+
const connection = await this.pool.getConnection();
|
|
8262
|
+
try {
|
|
8263
|
+
await connection.query("SET TRANSACTION ISOLATION LEVEL READ COMMITTED");
|
|
8264
|
+
await connection.beginTransaction();
|
|
8265
|
+
await connection.execute(`UPDATE ${OM_TABLE_QUOTED} AS r
|
|
8266
|
+
JOIN (
|
|
8267
|
+
SELECT ${omCol("id")} AS id, ${omCol("generationCount")} AS generationCount, ${omCol("createdAt")} AS createdAt
|
|
8268
|
+
FROM ${OM_TABLE_QUOTED} WHERE ${omCol("lookupKey")} = ? ORDER BY ${OM_HEAD_ORDER} LIMIT 1
|
|
8269
|
+
) AS h
|
|
8270
|
+
SET r.${omCol("supersededBy")} = h.id
|
|
8271
|
+
WHERE r.${omCol("lookupKey")} = ?
|
|
8272
|
+
AND r.${omCol("supersededBy")} IS NULL
|
|
8273
|
+
AND (
|
|
8274
|
+
r.${omCol("generationCount")} < h.generationCount
|
|
8275
|
+
OR (r.${omCol("generationCount")} = h.generationCount AND r.${omCol("createdAt")} > h.createdAt)
|
|
8276
|
+
OR (r.${omCol("generationCount")} = h.generationCount AND r.${omCol("createdAt")} = h.createdAt AND r.${omCol("id")} > h.id)
|
|
8277
|
+
)`, [lookupKey, lookupKey]);
|
|
8278
|
+
await connection.commit();
|
|
8279
|
+
break;
|
|
8280
|
+
} catch (error) {
|
|
8281
|
+
await connection.rollback().catch(() => {});
|
|
8282
|
+
if (attempt < OM_MAX_TRANSACTION_ATTEMPTS && isRetryableLockError(error)) continue;
|
|
8283
|
+
throw error;
|
|
8284
|
+
} finally {
|
|
8285
|
+
connection.release();
|
|
8286
|
+
}
|
|
8287
|
+
}
|
|
8288
|
+
}
|
|
7834
8289
|
async updateOMFlag(id, column, value, operation) {
|
|
7835
8290
|
try {
|
|
7836
8291
|
const [result] = await this.pool.execute(`UPDATE ${OM_TABLE_QUOTED} SET ${omCol(column)} = ?, ${omCol("updatedAt")} = ? WHERE ${omCol("id")} = ?`, [
|
|
@@ -7878,7 +8333,8 @@ var MemoryMySQL = class MemoryMySQL extends _mastra_core_storage.MemoryStorage {
|
|
|
7878
8333
|
config: parseJSONColumn(row.config) ?? {},
|
|
7879
8334
|
metadata: parseJSONColumn(row.metadata),
|
|
7880
8335
|
observedMessageIds: parseJSONColumn(row.observedMessageIds),
|
|
7881
|
-
observedTimezone: row.observedTimezone || void 0
|
|
8336
|
+
observedTimezone: row.observedTimezone || void 0,
|
|
8337
|
+
supersededBy: row.supersededBy ?? null
|
|
7882
8338
|
};
|
|
7883
8339
|
}
|
|
7884
8340
|
};
|
|
@@ -10866,6 +11322,26 @@ function parseSnapshot(snapshot) {
|
|
|
10866
11322
|
}
|
|
10867
11323
|
return snapshot;
|
|
10868
11324
|
}
|
|
11325
|
+
const RUN_OWNERS_TABLE = formatTableName(_mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS);
|
|
11326
|
+
const RUN_ID = quoteIdentifier("runId", "column name");
|
|
11327
|
+
const OWNER_ID = quoteIdentifier("ownerId", "column name");
|
|
11328
|
+
const LEASE_EXPIRES_AT = quoteIdentifier("leaseExpiresAt", "column name");
|
|
11329
|
+
const UPDATED_AT = quoteIdentifier("updatedAt", "column name");
|
|
11330
|
+
const RUN_OWNER_COLUMNS = `generation, ${OWNER_ID} AS ownerId, ${LEASE_EXPIRES_AT} AS leaseExpiresAt, ${DB_NOW_MS} AS nowMs`;
|
|
11331
|
+
function toRunOwnershipRecord(runId, row) {
|
|
11332
|
+
const leaseExpiresAt = row.leaseExpiresAt === null ? null : Number(row.leaseExpiresAt);
|
|
11333
|
+
return {
|
|
11334
|
+
runId,
|
|
11335
|
+
generation: Number(row.generation),
|
|
11336
|
+
ownerId: String(row.ownerId),
|
|
11337
|
+
leaseExpiresAt: leaseExpiresAt === null ? null : new Date(leaseExpiresAt),
|
|
11338
|
+
live: leaseExpiresAt !== null && leaseExpiresAt > Number(row.nowMs)
|
|
11339
|
+
};
|
|
11340
|
+
}
|
|
11341
|
+
async function readRunOwner(q, runId) {
|
|
11342
|
+
const [rows] = await q.execute(`SELECT ${RUN_OWNER_COLUMNS} FROM ${RUN_OWNERS_TABLE} WHERE ${RUN_ID} = ?`, [runId]);
|
|
11343
|
+
return rows[0] ? toRunOwnershipRecord(runId, rows[0]) : null;
|
|
11344
|
+
}
|
|
10869
11345
|
function mapWorkflowRow(row) {
|
|
10870
11346
|
return {
|
|
10871
11347
|
workflowName: row.workflow_name,
|
|
@@ -10898,6 +11374,9 @@ var WorkflowsMySQL = class WorkflowsMySQL extends _mastra_core_storage.Workflows
|
|
|
10898
11374
|
tableName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
|
|
10899
11375
|
schema: _mastra_core_storage.TABLE_SCHEMAS[_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT],
|
|
10900
11376
|
compositePrimaryKey: ["workflow_name", "run_id"]
|
|
11377
|
+
}), generateTableSQL({
|
|
11378
|
+
tableName: _mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS,
|
|
11379
|
+
schema: _mastra_core_storage.RUN_FENCING_TABLE_SCHEMAS[_mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS]
|
|
10901
11380
|
})];
|
|
10902
11381
|
}
|
|
10903
11382
|
constructor({ operations, pool, skipDefaultIndexes, indexes }) {
|
|
@@ -10941,16 +11420,125 @@ var WorkflowsMySQL = class WorkflowsMySQL extends _mastra_core_storage.Workflows
|
|
|
10941
11420
|
schema,
|
|
10942
11421
|
ifNotExists: ["resourceId"]
|
|
10943
11422
|
});
|
|
11423
|
+
await this.operations.createTable({
|
|
11424
|
+
tableName: _mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS,
|
|
11425
|
+
schema: _mastra_core_storage.RUN_FENCING_TABLE_SCHEMAS[_mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS]
|
|
11426
|
+
});
|
|
10944
11427
|
await this.createDefaultIndexes();
|
|
10945
11428
|
await this.createCustomIndexes();
|
|
10946
11429
|
}
|
|
10947
11430
|
async dangerouslyClearAll() {
|
|
10948
11431
|
await this.pool.execute(`DELETE FROM ${formatTableName(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT)}`);
|
|
11432
|
+
await this.pool.execute(`DELETE FROM ${RUN_OWNERS_TABLE}`);
|
|
10949
11433
|
}
|
|
10950
|
-
|
|
11434
|
+
supportsRunFencing() {
|
|
11435
|
+
return true;
|
|
11436
|
+
}
|
|
11437
|
+
#ownershipError(operation, runId, error) {
|
|
11438
|
+
return new _mastra_core_error.MastraError({
|
|
11439
|
+
id: (0, _mastra_core_storage.createStorageErrorId)("MYSQL", operation, "FAILED"),
|
|
11440
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
11441
|
+
category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
|
|
11442
|
+
details: { runId }
|
|
11443
|
+
}, error);
|
|
11444
|
+
}
|
|
11445
|
+
async claimRunOwnership({ runId, ownerId, leaseMs, force, expectedGeneration }) {
|
|
11446
|
+
try {
|
|
11447
|
+
return await claimTransaction(this.pool, async (connection) => {
|
|
11448
|
+
const [existing] = await connection.execute(`SELECT generation FROM ${RUN_OWNERS_TABLE} WHERE ${RUN_ID} = ? FOR UPDATE`, [runId]);
|
|
11449
|
+
if (existing.length === 0) {
|
|
11450
|
+
if (expectedGeneration !== void 0 && expectedGeneration !== 0) return {
|
|
11451
|
+
acquired: false,
|
|
11452
|
+
record: null
|
|
11453
|
+
};
|
|
11454
|
+
await connection.execute(`INSERT INTO ${RUN_OWNERS_TABLE} (${RUN_ID}, generation, ${OWNER_ID}, ${LEASE_EXPIRES_AT}, ${UPDATED_AT})
|
|
11455
|
+
VALUES (?, GREATEST(1, ${DB_NOW_MS}), ?, ${DB_NOW_MS} + ?, ${DB_NOW_MS})`, [
|
|
11456
|
+
runId,
|
|
11457
|
+
ownerId,
|
|
11458
|
+
leaseMs
|
|
11459
|
+
]);
|
|
11460
|
+
return {
|
|
11461
|
+
acquired: true,
|
|
11462
|
+
record: await readRunOwner(connection, runId)
|
|
11463
|
+
};
|
|
11464
|
+
}
|
|
11465
|
+
await connection.execute(`UPDATE ${RUN_OWNERS_TABLE}
|
|
11466
|
+
SET generation = generation + 1, ${OWNER_ID} = ?, ${LEASE_EXPIRES_AT} = ${DB_NOW_MS} + ?,
|
|
11467
|
+
${UPDATED_AT} = ${DB_NOW_MS}
|
|
11468
|
+
WHERE ${RUN_ID} = ?
|
|
11469
|
+
AND (? IS NULL OR generation = ?)
|
|
11470
|
+
AND (? OR ${LEASE_EXPIRES_AT} IS NULL OR ${LEASE_EXPIRES_AT} <= ${DB_NOW_MS})`, [
|
|
11471
|
+
ownerId,
|
|
11472
|
+
leaseMs,
|
|
11473
|
+
runId,
|
|
11474
|
+
expectedGeneration ?? null,
|
|
11475
|
+
expectedGeneration ?? null,
|
|
11476
|
+
force === true
|
|
11477
|
+
]);
|
|
11478
|
+
const record = await readRunOwner(connection, runId);
|
|
11479
|
+
return record && record.generation === Number(existing[0].generation) + 1 ? {
|
|
11480
|
+
acquired: true,
|
|
11481
|
+
record
|
|
11482
|
+
} : {
|
|
11483
|
+
acquired: false,
|
|
11484
|
+
record
|
|
11485
|
+
};
|
|
11486
|
+
});
|
|
11487
|
+
} catch (error) {
|
|
11488
|
+
throw this.#ownershipError("CLAIM_RUN_OWNERSHIP", runId, error);
|
|
11489
|
+
}
|
|
11490
|
+
}
|
|
11491
|
+
async renewRunOwnership({ leaseMs, ...fence }) {
|
|
10951
11492
|
try {
|
|
10952
|
-
await this.pool
|
|
11493
|
+
return await inTransaction(this.pool, async (connection) => {
|
|
11494
|
+
await connection.execute(`UPDATE ${RUN_OWNERS_TABLE}
|
|
11495
|
+
SET ${LEASE_EXPIRES_AT} = ${DB_NOW_MS} + ?, ${UPDATED_AT} = ${DB_NOW_MS}
|
|
11496
|
+
WHERE ${RUN_ID} = ? AND generation = ? AND ${OWNER_ID} = ? AND ${LEASE_EXPIRES_AT} IS NOT NULL`, [
|
|
11497
|
+
leaseMs,
|
|
11498
|
+
fence.runId,
|
|
11499
|
+
fence.generation,
|
|
11500
|
+
fence.ownerId
|
|
11501
|
+
]);
|
|
11502
|
+
const record = await readRunOwner(connection, fence.runId);
|
|
11503
|
+
return record && (0, _mastra_core_storage.matchesRunFence)(record, fence) && record.leaseExpiresAt !== null ? {
|
|
11504
|
+
renewed: true,
|
|
11505
|
+
record
|
|
11506
|
+
} : {
|
|
11507
|
+
renewed: false,
|
|
11508
|
+
record
|
|
11509
|
+
};
|
|
11510
|
+
});
|
|
10953
11511
|
} catch (error) {
|
|
11512
|
+
throw this.#ownershipError("RENEW_RUN_OWNERSHIP", fence.runId, error);
|
|
11513
|
+
}
|
|
11514
|
+
}
|
|
11515
|
+
async releaseRunOwnership(fence) {
|
|
11516
|
+
try {
|
|
11517
|
+
return await inTransaction(this.pool, async (connection) => {
|
|
11518
|
+
await connection.execute(`UPDATE ${RUN_OWNERS_TABLE} SET ${LEASE_EXPIRES_AT} = NULL, ${UPDATED_AT} = ${DB_NOW_MS}
|
|
11519
|
+
WHERE ${RUN_ID} = ? AND generation = ? AND ${OWNER_ID} = ?`, [
|
|
11520
|
+
fence.runId,
|
|
11521
|
+
fence.generation,
|
|
11522
|
+
fence.ownerId
|
|
11523
|
+
]);
|
|
11524
|
+
return (0, _mastra_core_storage.matchesRunFence)(await readRunOwner(connection, fence.runId), fence);
|
|
11525
|
+
});
|
|
11526
|
+
} catch (error) {
|
|
11527
|
+
throw this.#ownershipError("RELEASE_RUN_OWNERSHIP", fence.runId, error);
|
|
11528
|
+
}
|
|
11529
|
+
}
|
|
11530
|
+
async getRunOwnership({ runId }) {
|
|
11531
|
+
try {
|
|
11532
|
+
return await readRunOwner(this.pool, runId);
|
|
11533
|
+
} catch (error) {
|
|
11534
|
+
throw this.#ownershipError("GET_RUN_OWNERSHIP", runId, error);
|
|
11535
|
+
}
|
|
11536
|
+
}
|
|
11537
|
+
async deleteWorkflowRunById({ runId, workflowName, fence }) {
|
|
11538
|
+
try {
|
|
11539
|
+
await withRunFence(this.pool, RUN_OWNERS_TABLE, (0, _mastra_core_storage.resolveRunFence)(this, fence, runId), "deleteWorkflowRunById", (q) => q.execute(`DELETE FROM ${formatTableName(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT)} WHERE ${quoteIdentifier("workflow_name", "column name")} = ? AND ${quoteIdentifier("run_id", "column name")} = ?`, [workflowName, runId]));
|
|
11540
|
+
} catch (error) {
|
|
11541
|
+
if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
10954
11542
|
throw new _mastra_core_error.MastraError({
|
|
10955
11543
|
id: "MYSQL_WORKFLOWS_DELETE_RUN_BY_ID_FAILED",
|
|
10956
11544
|
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
@@ -10962,10 +11550,12 @@ var WorkflowsMySQL = class WorkflowsMySQL extends _mastra_core_storage.Workflows
|
|
|
10962
11550
|
}, error);
|
|
10963
11551
|
}
|
|
10964
11552
|
}
|
|
10965
|
-
async updateWorkflowResults({ workflowName, runId, stepId, result, requestContext, state }) {
|
|
11553
|
+
async updateWorkflowResults({ workflowName, runId, stepId, result, requestContext, state, fence: explicitFence }) {
|
|
11554
|
+
const fence = (0, _mastra_core_storage.resolveRunFence)(this, explicitFence, runId);
|
|
10966
11555
|
const connection = await this.pool.getConnection();
|
|
10967
11556
|
try {
|
|
10968
11557
|
await connection.beginTransaction();
|
|
11558
|
+
if (fence) await assertRunFence(connection, RUN_OWNERS_TABLE, fence, "updateWorkflowResults");
|
|
10969
11559
|
const [rows] = await connection.execute(`SELECT snapshot FROM ${formatTableName(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT)} WHERE ${quoteIdentifier("workflow_name", "column name")} = ? AND ${quoteIdentifier("run_id", "column name")} = ? FOR UPDATE`, [workflowName, runId]);
|
|
10970
11560
|
if (!Array.isArray(rows) || rows.length === 0) {
|
|
10971
11561
|
await connection.rollback();
|
|
@@ -11016,10 +11606,12 @@ var WorkflowsMySQL = class WorkflowsMySQL extends _mastra_core_storage.Workflows
|
|
|
11016
11606
|
connection.release();
|
|
11017
11607
|
}
|
|
11018
11608
|
}
|
|
11019
|
-
async updateWorkflowState({ workflowName, runId, opts }) {
|
|
11609
|
+
async updateWorkflowState({ workflowName, runId, opts, fence: explicitFence }) {
|
|
11610
|
+
const fence = (0, _mastra_core_storage.resolveRunFence)(this, explicitFence, runId);
|
|
11020
11611
|
const connection = await this.pool.getConnection();
|
|
11021
11612
|
try {
|
|
11022
11613
|
await connection.beginTransaction();
|
|
11614
|
+
if (fence) await assertRunFence(connection, RUN_OWNERS_TABLE, fence, "updateWorkflowState");
|
|
11023
11615
|
const [rows] = await connection.execute(`SELECT snapshot FROM ${formatTableName(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT)} WHERE ${quoteIdentifier("workflow_name", "column name")} = ? AND ${quoteIdentifier("run_id", "column name")} = ? FOR UPDATE`, [workflowName, runId]);
|
|
11024
11616
|
if (!Array.isArray(rows) || rows.length === 0) {
|
|
11025
11617
|
await connection.rollback();
|
|
@@ -11045,6 +11637,7 @@ var WorkflowsMySQL = class WorkflowsMySQL extends _mastra_core_storage.Workflows
|
|
|
11045
11637
|
return updatedSnapshot;
|
|
11046
11638
|
} catch (error) {
|
|
11047
11639
|
await connection.rollback();
|
|
11640
|
+
if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
11048
11641
|
throw new _mastra_core_error.MastraError({
|
|
11049
11642
|
id: "MYSQL_WORKFLOWS_UPDATE_STATE_FAILED",
|
|
11050
11643
|
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
@@ -11058,35 +11651,38 @@ var WorkflowsMySQL = class WorkflowsMySQL extends _mastra_core_storage.Workflows
|
|
|
11058
11651
|
connection.release();
|
|
11059
11652
|
}
|
|
11060
11653
|
}
|
|
11061
|
-
async persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot, createdAt, updatedAt }) {
|
|
11654
|
+
async persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot, createdAt, updatedAt, fence }) {
|
|
11062
11655
|
const now = /* @__PURE__ */ new Date();
|
|
11063
11656
|
const createdAtValue = createdAt ?? now;
|
|
11064
11657
|
const updatedAtValue = updatedAt ?? now;
|
|
11658
|
+
const tableName = formatTableName(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT);
|
|
11065
11659
|
try {
|
|
11066
|
-
|
|
11067
|
-
|
|
11068
|
-
|
|
11069
|
-
|
|
11070
|
-
|
|
11071
|
-
|
|
11072
|
-
|
|
11073
|
-
|
|
11074
|
-
|
|
11075
|
-
|
|
11076
|
-
|
|
11077
|
-
|
|
11078
|
-
|
|
11079
|
-
|
|
11080
|
-
|
|
11081
|
-
|
|
11082
|
-
|
|
11083
|
-
|
|
11084
|
-
|
|
11085
|
-
|
|
11086
|
-
|
|
11087
|
-
|
|
11088
|
-
|
|
11660
|
+
await withRunFence(this.pool, RUN_OWNERS_TABLE, (0, _mastra_core_storage.resolveRunFence)(this, fence, runId), "persistWorkflowSnapshot", async (q) => {
|
|
11661
|
+
const [updateResult] = await q.execute(`UPDATE ${tableName}
|
|
11662
|
+
SET ${quoteIdentifier("resourceId", "column name")} = ?,
|
|
11663
|
+
${quoteIdentifier("snapshot", "column name")} = ?,
|
|
11664
|
+
${quoteIdentifier("updatedAt", "column name")} = ?
|
|
11665
|
+
WHERE ${quoteIdentifier("workflow_name", "column name")} = ? AND ${quoteIdentifier("run_id", "column name")} = ?`, [
|
|
11666
|
+
resourceId ?? null,
|
|
11667
|
+
JSON.stringify(snapshot),
|
|
11668
|
+
transformToSqlValue(updatedAtValue),
|
|
11669
|
+
workflowName,
|
|
11670
|
+
runId
|
|
11671
|
+
]);
|
|
11672
|
+
if (updateResult.affectedRows > 0) return;
|
|
11673
|
+
await q.execute(`INSERT INTO ${tableName} (${quoteIdentifier("workflow_name", "column name")}, ${quoteIdentifier("run_id", "column name")}, ${quoteIdentifier("resourceId", "column name")}, ${quoteIdentifier("snapshot", "column name")}, ${quoteIdentifier("createdAt", "column name")}, ${quoteIdentifier("updatedAt", "column name")})
|
|
11674
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
11675
|
+
ON DUPLICATE KEY UPDATE ${quoteIdentifier("resourceId", "column name")} = VALUES(${quoteIdentifier("resourceId", "column name")}), ${quoteIdentifier("snapshot", "column name")} = VALUES(${quoteIdentifier("snapshot", "column name")}), ${quoteIdentifier("updatedAt", "column name")} = VALUES(${quoteIdentifier("updatedAt", "column name")})`, [
|
|
11676
|
+
workflowName,
|
|
11677
|
+
runId,
|
|
11678
|
+
resourceId ?? null,
|
|
11679
|
+
JSON.stringify(snapshot),
|
|
11680
|
+
transformToSqlValue(createdAtValue),
|
|
11681
|
+
transformToSqlValue(updatedAtValue)
|
|
11682
|
+
]);
|
|
11683
|
+
});
|
|
11089
11684
|
} catch (error) {
|
|
11685
|
+
if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
11090
11686
|
throw new _mastra_core_error.MastraError({
|
|
11091
11687
|
id: "MYSQL_WORKFLOWS_PERSIST_SNAPSHOT_FAILED",
|
|
11092
11688
|
domain: _mastra_core_error.ErrorDomain.STORAGE,
|