@mastra/lance 1.0.0-beta.7 → 1.0.0-beta.8
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/CHANGELOG.md +194 -0
- package/dist/index.cjs +1087 -1125
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1088 -1126
- package/dist/index.js.map +1 -1
- package/dist/storage/{domains/operations → db}/index.d.ts +21 -2
- package/dist/storage/db/index.d.ts.map +1 -0
- package/dist/storage/db/utils.d.ts.map +1 -0
- package/dist/storage/domains/memory/index.d.ts +6 -7
- package/dist/storage/domains/memory/index.d.ts.map +1 -1
- package/dist/storage/domains/scores/index.d.ts +11 -22
- package/dist/storage/domains/scores/index.d.ts.map +1 -1
- package/dist/storage/domains/workflows/index.d.ts +10 -6
- package/dist/storage/domains/workflows/index.d.ts.map +1 -1
- package/dist/storage/index.d.ts +62 -174
- package/dist/storage/index.d.ts.map +1 -1
- package/package.json +2 -2
- package/dist/storage/domains/operations/index.d.ts.map +0 -1
- package/dist/storage/domains/utils.d.ts.map +0 -1
- /package/dist/storage/{domains → db}/utils.d.ts +0 -0
package/dist/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { connect, Index } from '@lancedb/lancedb';
|
|
2
2
|
import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
|
|
3
|
-
import { MastraStorage, createStorageErrorId, createVectorErrorId,
|
|
3
|
+
import { MastraStorage, createStorageErrorId, createVectorErrorId, MemoryStorage, TABLE_SCHEMAS, TABLE_THREADS, TABLE_MESSAGES, TABLE_RESOURCES, normalizePerPage, calculatePagination, ScoresStorage, SCORERS_SCHEMA, TABLE_SCORERS, WorkflowsStorage, TABLE_WORKFLOW_SNAPSHOT, getDefaultValue, ensureDate } from '@mastra/core/storage';
|
|
4
4
|
import { MessageList } from '@mastra/core/agent';
|
|
5
|
+
import { MastraBase } from '@mastra/core/base';
|
|
5
6
|
import { Utf8, Float64, Binary, Float32, Int32, Field, Schema } from 'apache-arrow';
|
|
6
7
|
import { saveScorePayloadSchema } from '@mastra/core/evals';
|
|
7
8
|
import { MastraVector } from '@mastra/core/vector';
|
|
@@ -122,1119 +123,1198 @@ async function getTableSchema({
|
|
|
122
123
|
}
|
|
123
124
|
}
|
|
124
125
|
|
|
125
|
-
// src/storage/
|
|
126
|
-
|
|
126
|
+
// src/storage/db/index.ts
|
|
127
|
+
function resolveLanceConfig(config) {
|
|
128
|
+
return config.client;
|
|
129
|
+
}
|
|
130
|
+
var LanceDB = class extends MastraBase {
|
|
127
131
|
client;
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
super();
|
|
132
|
+
constructor({ client }) {
|
|
133
|
+
super({ name: "lance-db" });
|
|
131
134
|
this.client = client;
|
|
132
|
-
this.operations = operations;
|
|
133
135
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
136
|
+
getDefaultValue(type) {
|
|
137
|
+
switch (type) {
|
|
138
|
+
case "text":
|
|
139
|
+
return "''";
|
|
140
|
+
case "timestamp":
|
|
141
|
+
return "CURRENT_TIMESTAMP";
|
|
142
|
+
case "integer":
|
|
143
|
+
case "bigint":
|
|
144
|
+
return "0";
|
|
145
|
+
case "jsonb":
|
|
146
|
+
return "'{}'";
|
|
147
|
+
case "uuid":
|
|
148
|
+
return "''";
|
|
149
|
+
default:
|
|
150
|
+
return getDefaultValue(type);
|
|
151
|
+
}
|
|
137
152
|
}
|
|
138
|
-
async
|
|
153
|
+
async hasColumn(tableName, columnName) {
|
|
154
|
+
const table = await this.client.openTable(tableName);
|
|
155
|
+
const schema = await table.schema();
|
|
156
|
+
return schema.fields.some((field) => field.name === columnName);
|
|
157
|
+
}
|
|
158
|
+
translateSchema(schema) {
|
|
159
|
+
const fields = Object.entries(schema).map(([name, column]) => {
|
|
160
|
+
let arrowType;
|
|
161
|
+
switch (column.type.toLowerCase()) {
|
|
162
|
+
case "text":
|
|
163
|
+
case "uuid":
|
|
164
|
+
arrowType = new Utf8();
|
|
165
|
+
break;
|
|
166
|
+
case "int":
|
|
167
|
+
case "integer":
|
|
168
|
+
arrowType = new Int32();
|
|
169
|
+
break;
|
|
170
|
+
case "bigint":
|
|
171
|
+
arrowType = new Float64();
|
|
172
|
+
break;
|
|
173
|
+
case "float":
|
|
174
|
+
arrowType = new Float32();
|
|
175
|
+
break;
|
|
176
|
+
case "jsonb":
|
|
177
|
+
case "json":
|
|
178
|
+
arrowType = new Utf8();
|
|
179
|
+
break;
|
|
180
|
+
case "binary":
|
|
181
|
+
arrowType = new Binary();
|
|
182
|
+
break;
|
|
183
|
+
case "timestamp":
|
|
184
|
+
arrowType = new Float64();
|
|
185
|
+
break;
|
|
186
|
+
default:
|
|
187
|
+
arrowType = new Utf8();
|
|
188
|
+
}
|
|
189
|
+
return new Field(name, arrowType, column.nullable ?? true);
|
|
190
|
+
});
|
|
191
|
+
return new Schema(fields);
|
|
192
|
+
}
|
|
193
|
+
async createTable({
|
|
194
|
+
tableName,
|
|
195
|
+
schema
|
|
196
|
+
}) {
|
|
139
197
|
try {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
198
|
+
if (!this.client) {
|
|
199
|
+
throw new Error("LanceDB client not initialized. Call LanceStorage.create() first.");
|
|
200
|
+
}
|
|
201
|
+
if (!tableName) {
|
|
202
|
+
throw new Error("tableName is required for createTable.");
|
|
203
|
+
}
|
|
204
|
+
if (!schema) {
|
|
205
|
+
throw new Error("schema is required for createTable.");
|
|
143
206
|
}
|
|
144
|
-
return {
|
|
145
|
-
...thread,
|
|
146
|
-
createdAt: new Date(thread.createdAt),
|
|
147
|
-
updatedAt: new Date(thread.updatedAt)
|
|
148
|
-
};
|
|
149
207
|
} catch (error) {
|
|
150
208
|
throw new MastraError(
|
|
151
209
|
{
|
|
152
|
-
id: createStorageErrorId("LANCE", "
|
|
210
|
+
id: createStorageErrorId("LANCE", "CREATE_TABLE", "INVALID_ARGS"),
|
|
153
211
|
domain: ErrorDomain.STORAGE,
|
|
154
|
-
category: ErrorCategory.
|
|
212
|
+
category: ErrorCategory.USER,
|
|
213
|
+
details: { tableName }
|
|
155
214
|
},
|
|
156
215
|
error
|
|
157
216
|
);
|
|
158
217
|
}
|
|
159
|
-
}
|
|
160
|
-
/**
|
|
161
|
-
* Saves a thread to the database. This function doesn't overwrite existing threads.
|
|
162
|
-
* @param thread - The thread to save
|
|
163
|
-
* @returns The saved thread
|
|
164
|
-
*/
|
|
165
|
-
async saveThread({ thread }) {
|
|
166
218
|
try {
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
await table.add([record], { mode: "append" });
|
|
170
|
-
return thread;
|
|
219
|
+
const arrowSchema = this.translateSchema(schema);
|
|
220
|
+
await this.client.createEmptyTable(tableName, arrowSchema);
|
|
171
221
|
} catch (error) {
|
|
222
|
+
if (error.message?.includes("already exists")) {
|
|
223
|
+
this.logger.debug(`Table '${tableName}' already exists, skipping create`);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
172
226
|
throw new MastraError(
|
|
173
227
|
{
|
|
174
|
-
id: createStorageErrorId("LANCE", "
|
|
228
|
+
id: createStorageErrorId("LANCE", "CREATE_TABLE", "FAILED"),
|
|
175
229
|
domain: ErrorDomain.STORAGE,
|
|
176
|
-
category: ErrorCategory.THIRD_PARTY
|
|
230
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
231
|
+
details: { tableName }
|
|
177
232
|
},
|
|
178
233
|
error
|
|
179
234
|
);
|
|
180
235
|
}
|
|
181
236
|
}
|
|
182
|
-
async
|
|
183
|
-
id,
|
|
184
|
-
title,
|
|
185
|
-
metadata
|
|
186
|
-
}) {
|
|
187
|
-
const maxRetries = 5;
|
|
188
|
-
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
189
|
-
try {
|
|
190
|
-
const current = await this.getThreadById({ threadId: id });
|
|
191
|
-
if (!current) {
|
|
192
|
-
throw new Error(`Thread with id ${id} not found`);
|
|
193
|
-
}
|
|
194
|
-
const mergedMetadata = { ...current.metadata, ...metadata };
|
|
195
|
-
const record = {
|
|
196
|
-
id,
|
|
197
|
-
title,
|
|
198
|
-
metadata: JSON.stringify(mergedMetadata),
|
|
199
|
-
updatedAt: (/* @__PURE__ */ new Date()).getTime()
|
|
200
|
-
};
|
|
201
|
-
const table = await this.client.openTable(TABLE_THREADS);
|
|
202
|
-
await table.mergeInsert("id").whenMatchedUpdateAll().whenNotMatchedInsertAll().execute([record]);
|
|
203
|
-
const updatedThread = await this.getThreadById({ threadId: id });
|
|
204
|
-
if (!updatedThread) {
|
|
205
|
-
throw new Error(`Failed to retrieve updated thread ${id}`);
|
|
206
|
-
}
|
|
207
|
-
return updatedThread;
|
|
208
|
-
} catch (error) {
|
|
209
|
-
if (error.message?.includes("Commit conflict") && attempt < maxRetries - 1) {
|
|
210
|
-
const delay = Math.pow(2, attempt) * 10;
|
|
211
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
212
|
-
continue;
|
|
213
|
-
}
|
|
214
|
-
throw new MastraError(
|
|
215
|
-
{
|
|
216
|
-
id: createStorageErrorId("LANCE", "UPDATE_THREAD", "FAILED"),
|
|
217
|
-
domain: ErrorDomain.STORAGE,
|
|
218
|
-
category: ErrorCategory.THIRD_PARTY
|
|
219
|
-
},
|
|
220
|
-
error
|
|
221
|
-
);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
throw new MastraError(
|
|
225
|
-
{
|
|
226
|
-
id: createStorageErrorId("LANCE", "UPDATE_THREAD", "FAILED"),
|
|
227
|
-
domain: ErrorDomain.STORAGE,
|
|
228
|
-
category: ErrorCategory.THIRD_PARTY
|
|
229
|
-
},
|
|
230
|
-
new Error("All retries exhausted")
|
|
231
|
-
);
|
|
232
|
-
}
|
|
233
|
-
async deleteThread({ threadId }) {
|
|
237
|
+
async dropTable({ tableName }) {
|
|
234
238
|
try {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
239
|
+
if (!this.client) {
|
|
240
|
+
throw new Error("LanceDB client not initialized. Call LanceStorage.create() first.");
|
|
241
|
+
}
|
|
242
|
+
if (!tableName) {
|
|
243
|
+
throw new Error("tableName is required for dropTable.");
|
|
244
|
+
}
|
|
245
|
+
} catch (validationError) {
|
|
240
246
|
throw new MastraError(
|
|
241
247
|
{
|
|
242
|
-
id: createStorageErrorId("LANCE", "
|
|
248
|
+
id: createStorageErrorId("LANCE", "DROP_TABLE", "INVALID_ARGS"),
|
|
243
249
|
domain: ErrorDomain.STORAGE,
|
|
244
|
-
category: ErrorCategory.
|
|
250
|
+
category: ErrorCategory.USER,
|
|
251
|
+
text: validationError.message,
|
|
252
|
+
details: { tableName }
|
|
245
253
|
},
|
|
246
|
-
|
|
254
|
+
validationError
|
|
247
255
|
);
|
|
248
256
|
}
|
|
249
|
-
}
|
|
250
|
-
normalizeMessage(message) {
|
|
251
|
-
const { thread_id, ...rest } = message;
|
|
252
|
-
return {
|
|
253
|
-
...rest,
|
|
254
|
-
threadId: thread_id,
|
|
255
|
-
content: typeof message.content === "string" ? (() => {
|
|
256
|
-
try {
|
|
257
|
-
return JSON.parse(message.content);
|
|
258
|
-
} catch {
|
|
259
|
-
return message.content;
|
|
260
|
-
}
|
|
261
|
-
})() : message.content
|
|
262
|
-
};
|
|
263
|
-
}
|
|
264
|
-
async listMessagesById({ messageIds }) {
|
|
265
|
-
if (messageIds.length === 0) return { messages: [] };
|
|
266
257
|
try {
|
|
267
|
-
|
|
268
|
-
const quotedIds = messageIds.map((id) => `'${id}'`).join(", ");
|
|
269
|
-
const allRecords = await table.query().where(`id IN (${quotedIds})`).toArray();
|
|
270
|
-
const messages = processResultWithTypeConversion(
|
|
271
|
-
allRecords,
|
|
272
|
-
await getTableSchema({ tableName: TABLE_MESSAGES, client: this.client })
|
|
273
|
-
);
|
|
274
|
-
const list = new MessageList().add(
|
|
275
|
-
messages.map(this.normalizeMessage),
|
|
276
|
-
"memory"
|
|
277
|
-
);
|
|
278
|
-
return { messages: list.get.all.db() };
|
|
258
|
+
await this.client.dropTable(tableName);
|
|
279
259
|
} catch (error) {
|
|
260
|
+
if (error.toString().includes("was not found") || error.message?.includes("Table not found")) {
|
|
261
|
+
this.logger.debug(`Table '${tableName}' does not exist, skipping drop`);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
280
264
|
throw new MastraError(
|
|
281
265
|
{
|
|
282
|
-
id: createStorageErrorId("LANCE", "
|
|
266
|
+
id: createStorageErrorId("LANCE", "DROP_TABLE", "FAILED"),
|
|
283
267
|
domain: ErrorDomain.STORAGE,
|
|
284
268
|
category: ErrorCategory.THIRD_PARTY,
|
|
285
|
-
details: {
|
|
286
|
-
messageIds: JSON.stringify(messageIds)
|
|
287
|
-
}
|
|
269
|
+
details: { tableName }
|
|
288
270
|
},
|
|
289
271
|
error
|
|
290
272
|
);
|
|
291
273
|
}
|
|
292
274
|
}
|
|
293
|
-
async
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
275
|
+
async alterTable({
|
|
276
|
+
tableName,
|
|
277
|
+
schema,
|
|
278
|
+
ifNotExists
|
|
279
|
+
}) {
|
|
280
|
+
try {
|
|
281
|
+
if (!this.client) {
|
|
282
|
+
throw new Error("LanceDB client not initialized. Call LanceStorage.create() first.");
|
|
283
|
+
}
|
|
284
|
+
if (!tableName) {
|
|
285
|
+
throw new Error("tableName is required for alterTable.");
|
|
286
|
+
}
|
|
287
|
+
if (!schema) {
|
|
288
|
+
throw new Error("schema is required for alterTable.");
|
|
289
|
+
}
|
|
290
|
+
if (!ifNotExists || ifNotExists.length === 0) {
|
|
291
|
+
this.logger.debug("No columns specified to add in alterTable, skipping.");
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
} catch (validationError) {
|
|
297
295
|
throw new MastraError(
|
|
298
296
|
{
|
|
299
|
-
id: createStorageErrorId("LANCE", "
|
|
297
|
+
id: createStorageErrorId("LANCE", "ALTER_TABLE", "INVALID_ARGS"),
|
|
300
298
|
domain: ErrorDomain.STORAGE,
|
|
301
|
-
category: ErrorCategory.
|
|
302
|
-
|
|
299
|
+
category: ErrorCategory.USER,
|
|
300
|
+
text: validationError.message,
|
|
301
|
+
details: { tableName }
|
|
303
302
|
},
|
|
304
|
-
|
|
303
|
+
validationError
|
|
305
304
|
);
|
|
306
305
|
}
|
|
307
|
-
const perPage = normalizePerPage(perPageInput, 40);
|
|
308
|
-
const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
|
|
309
306
|
try {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
}
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
const threadCondition = threadIds.length === 1 ? `thread_id = '${this.escapeSql(threadIds[0])}'` : `thread_id IN (${threadIds.map((t) => `'${this.escapeSql(t)}'`).join(", ")})`;
|
|
324
|
-
const conditions = [threadCondition];
|
|
325
|
-
if (resourceId) {
|
|
326
|
-
conditions.push(`\`resourceId\` = '${this.escapeSql(resourceId)}'`);
|
|
327
|
-
}
|
|
328
|
-
if (filter?.dateRange?.start) {
|
|
329
|
-
const startTime = filter.dateRange.start instanceof Date ? filter.dateRange.start.getTime() : new Date(filter.dateRange.start).getTime();
|
|
330
|
-
conditions.push(`\`createdAt\` >= ${startTime}`);
|
|
331
|
-
}
|
|
332
|
-
if (filter?.dateRange?.end) {
|
|
333
|
-
const endTime = filter.dateRange.end instanceof Date ? filter.dateRange.end.getTime() : new Date(filter.dateRange.end).getTime();
|
|
334
|
-
conditions.push(`\`createdAt\` <= ${endTime}`);
|
|
335
|
-
}
|
|
336
|
-
const whereClause = conditions.join(" AND ");
|
|
337
|
-
const total = await table.countRows(whereClause);
|
|
338
|
-
const query = table.query().where(whereClause);
|
|
339
|
-
let allRecords = await query.toArray();
|
|
340
|
-
allRecords.sort((a, b) => {
|
|
341
|
-
const aValue = field === "createdAt" ? a.createdAt : a[field];
|
|
342
|
-
const bValue = field === "createdAt" ? b.createdAt : b[field];
|
|
343
|
-
if (aValue == null && bValue == null) return 0;
|
|
344
|
-
if (aValue == null) return direction === "ASC" ? -1 : 1;
|
|
345
|
-
if (bValue == null) return direction === "ASC" ? 1 : -1;
|
|
346
|
-
if (typeof aValue === "string" && typeof bValue === "string") {
|
|
347
|
-
return direction === "ASC" ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue);
|
|
348
|
-
}
|
|
349
|
-
return direction === "ASC" ? aValue - bValue : bValue - aValue;
|
|
350
|
-
});
|
|
351
|
-
const paginatedRecords = allRecords.slice(offset, offset + perPage);
|
|
352
|
-
const messages = paginatedRecords.map((row) => this.normalizeMessage(row));
|
|
353
|
-
if (total === 0 && messages.length === 0 && (!include || include.length === 0)) {
|
|
307
|
+
const table = await this.client.openTable(tableName);
|
|
308
|
+
const currentSchema = await table.schema();
|
|
309
|
+
const existingFields = new Set(currentSchema.fields.map((f) => f.name));
|
|
310
|
+
const typeMap = {
|
|
311
|
+
text: "string",
|
|
312
|
+
integer: "int",
|
|
313
|
+
bigint: "bigint",
|
|
314
|
+
timestamp: "timestamp",
|
|
315
|
+
jsonb: "string",
|
|
316
|
+
uuid: "string"
|
|
317
|
+
};
|
|
318
|
+
const columnsToAdd = ifNotExists.filter((col) => schema[col] && !existingFields.has(col)).map((col) => {
|
|
319
|
+
const colDef = schema[col];
|
|
354
320
|
return {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
page,
|
|
358
|
-
perPage: perPageForResponse,
|
|
359
|
-
hasMore: false
|
|
321
|
+
name: col,
|
|
322
|
+
valueSql: colDef?.nullable ? `cast(NULL as ${typeMap[colDef.type ?? "text"]})` : `cast(${this.getDefaultValue(colDef?.type ?? "text")} as ${typeMap[colDef?.type ?? "text"]})`
|
|
360
323
|
};
|
|
361
|
-
}
|
|
362
|
-
const messageIds = new Set(messages.map((m) => m.id));
|
|
363
|
-
if (include && include.length > 0) {
|
|
364
|
-
const threadIds2 = [...new Set(include.map((item) => item.threadId || threadId))];
|
|
365
|
-
const allThreadMessages = [];
|
|
366
|
-
for (const tid of threadIds2) {
|
|
367
|
-
const threadQuery = table.query().where(`thread_id = '${tid}'`);
|
|
368
|
-
let threadRecords = await threadQuery.toArray();
|
|
369
|
-
allThreadMessages.push(...threadRecords);
|
|
370
|
-
}
|
|
371
|
-
allThreadMessages.sort((a, b) => a.createdAt - b.createdAt);
|
|
372
|
-
const contextMessages = this.processMessagesWithContext(allThreadMessages, include);
|
|
373
|
-
const includedMessages = contextMessages.map((row) => this.normalizeMessage(row));
|
|
374
|
-
for (const includeMsg of includedMessages) {
|
|
375
|
-
if (!messageIds.has(includeMsg.id)) {
|
|
376
|
-
messages.push(includeMsg);
|
|
377
|
-
messageIds.add(includeMsg.id);
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
const list = new MessageList().add(messages, "memory");
|
|
382
|
-
let finalMessages = list.get.all.db();
|
|
383
|
-
finalMessages = finalMessages.sort((a, b) => {
|
|
384
|
-
const aValue = field === "createdAt" ? new Date(a.createdAt).getTime() : a[field];
|
|
385
|
-
const bValue = field === "createdAt" ? new Date(b.createdAt).getTime() : b[field];
|
|
386
|
-
if (aValue == null && bValue == null) return 0;
|
|
387
|
-
if (aValue == null) return direction === "ASC" ? -1 : 1;
|
|
388
|
-
if (bValue == null) return direction === "ASC" ? 1 : -1;
|
|
389
|
-
if (typeof aValue === "string" && typeof bValue === "string") {
|
|
390
|
-
return direction === "ASC" ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue);
|
|
391
|
-
}
|
|
392
|
-
return direction === "ASC" ? aValue - bValue : bValue - aValue;
|
|
393
324
|
});
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
return {
|
|
399
|
-
messages: finalMessages,
|
|
400
|
-
total,
|
|
401
|
-
page,
|
|
402
|
-
perPage: perPageForResponse,
|
|
403
|
-
hasMore
|
|
404
|
-
};
|
|
325
|
+
if (columnsToAdd.length > 0) {
|
|
326
|
+
await table.addColumns(columnsToAdd);
|
|
327
|
+
this.logger?.info?.(`Added columns [${columnsToAdd.map((c) => c.name).join(", ")}] to table ${tableName}`);
|
|
328
|
+
}
|
|
405
329
|
} catch (error) {
|
|
406
|
-
|
|
330
|
+
throw new MastraError(
|
|
407
331
|
{
|
|
408
|
-
id: createStorageErrorId("LANCE", "
|
|
332
|
+
id: createStorageErrorId("LANCE", "ALTER_TABLE", "FAILED"),
|
|
409
333
|
domain: ErrorDomain.STORAGE,
|
|
410
334
|
category: ErrorCategory.THIRD_PARTY,
|
|
411
|
-
details: {
|
|
412
|
-
threadId: Array.isArray(threadId) ? threadId.join(",") : threadId,
|
|
413
|
-
resourceId: resourceId ?? ""
|
|
414
|
-
}
|
|
335
|
+
details: { tableName }
|
|
415
336
|
},
|
|
416
337
|
error
|
|
417
338
|
);
|
|
418
|
-
this.logger?.error?.(mastraError.toString());
|
|
419
|
-
this.logger?.trackException?.(mastraError);
|
|
420
|
-
return {
|
|
421
|
-
messages: [],
|
|
422
|
-
total: 0,
|
|
423
|
-
page,
|
|
424
|
-
perPage: perPageForResponse,
|
|
425
|
-
hasMore: false
|
|
426
|
-
};
|
|
427
339
|
}
|
|
428
340
|
}
|
|
429
|
-
async
|
|
341
|
+
async clearTable({ tableName }) {
|
|
430
342
|
try {
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
return { messages: [] };
|
|
434
|
-
}
|
|
435
|
-
const threadId = messages[0]?.threadId;
|
|
436
|
-
if (!threadId) {
|
|
437
|
-
throw new Error("Thread ID is required");
|
|
343
|
+
if (!this.client) {
|
|
344
|
+
throw new Error("LanceDB client not initialized. Call LanceStorage.create() first.");
|
|
438
345
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
throw new Error("Message ID is required");
|
|
442
|
-
}
|
|
443
|
-
if (!message.threadId) {
|
|
444
|
-
throw new Error("Thread ID is required for all messages");
|
|
445
|
-
}
|
|
446
|
-
if (message.resourceId === null || message.resourceId === void 0) {
|
|
447
|
-
throw new Error("Resource ID cannot be null or undefined");
|
|
448
|
-
}
|
|
449
|
-
if (!message.content) {
|
|
450
|
-
throw new Error("Message content is required");
|
|
451
|
-
}
|
|
346
|
+
if (!tableName) {
|
|
347
|
+
throw new Error("tableName is required for clearTable.");
|
|
452
348
|
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
const
|
|
467
|
-
await
|
|
468
|
-
const list = new MessageList().add(messages, "memory");
|
|
469
|
-
return { messages: list.get.all.db() };
|
|
349
|
+
} catch (validationError) {
|
|
350
|
+
throw new MastraError(
|
|
351
|
+
{
|
|
352
|
+
id: createStorageErrorId("LANCE", "CLEAR_TABLE", "INVALID_ARGS"),
|
|
353
|
+
domain: ErrorDomain.STORAGE,
|
|
354
|
+
category: ErrorCategory.USER,
|
|
355
|
+
text: validationError.message,
|
|
356
|
+
details: { tableName }
|
|
357
|
+
},
|
|
358
|
+
validationError
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
try {
|
|
362
|
+
const table = await this.client.openTable(tableName);
|
|
363
|
+
await table.delete("1=1");
|
|
470
364
|
} catch (error) {
|
|
471
365
|
throw new MastraError(
|
|
472
366
|
{
|
|
473
|
-
id: createStorageErrorId("LANCE", "
|
|
367
|
+
id: createStorageErrorId("LANCE", "CLEAR_TABLE", "FAILED"),
|
|
474
368
|
domain: ErrorDomain.STORAGE,
|
|
475
|
-
category: ErrorCategory.THIRD_PARTY
|
|
369
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
370
|
+
details: { tableName }
|
|
476
371
|
},
|
|
477
372
|
error
|
|
478
373
|
);
|
|
479
374
|
}
|
|
480
375
|
}
|
|
481
|
-
async
|
|
376
|
+
async insert({ tableName, record }) {
|
|
482
377
|
try {
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
if (page < 0) {
|
|
486
|
-
throw new MastraError(
|
|
487
|
-
{
|
|
488
|
-
id: createStorageErrorId("LANCE", "LIST_THREADS_BY_RESOURCE_ID", "INVALID_PAGE"),
|
|
489
|
-
domain: ErrorDomain.STORAGE,
|
|
490
|
-
category: ErrorCategory.USER,
|
|
491
|
-
details: { page }
|
|
492
|
-
},
|
|
493
|
-
new Error("page must be >= 0")
|
|
494
|
-
);
|
|
378
|
+
if (!this.client) {
|
|
379
|
+
throw new Error("LanceDB client not initialized. Call LanceStorage.create() first.");
|
|
495
380
|
}
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
const aValue = ["createdAt", "updatedAt"].includes(field) ? new Date(a[field]).getTime() : a[field];
|
|
504
|
-
const bValue = ["createdAt", "updatedAt"].includes(field) ? new Date(b[field]).getTime() : b[field];
|
|
505
|
-
if (aValue == null && bValue == null) return 0;
|
|
506
|
-
if (aValue == null) return direction === "ASC" ? -1 : 1;
|
|
507
|
-
if (bValue == null) return direction === "ASC" ? 1 : -1;
|
|
508
|
-
if (typeof aValue === "string" && typeof bValue === "string") {
|
|
509
|
-
return direction === "ASC" ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue);
|
|
510
|
-
}
|
|
511
|
-
return direction === "ASC" ? aValue - bValue : bValue - aValue;
|
|
512
|
-
});
|
|
513
|
-
const paginatedRecords = records.slice(offset, offset + perPage);
|
|
514
|
-
const schema = await getTableSchema({ tableName: TABLE_THREADS, client: this.client });
|
|
515
|
-
const threads = paginatedRecords.map(
|
|
516
|
-
(record) => processResultWithTypeConversion(record, schema)
|
|
517
|
-
);
|
|
518
|
-
return {
|
|
519
|
-
threads,
|
|
520
|
-
total,
|
|
521
|
-
page,
|
|
522
|
-
perPage: perPageForResponse,
|
|
523
|
-
hasMore: offset + perPage < total
|
|
524
|
-
};
|
|
525
|
-
} catch (error) {
|
|
381
|
+
if (!tableName) {
|
|
382
|
+
throw new Error("tableName is required for insert.");
|
|
383
|
+
}
|
|
384
|
+
if (!record || Object.keys(record).length === 0) {
|
|
385
|
+
throw new Error("record is required and cannot be empty for insert.");
|
|
386
|
+
}
|
|
387
|
+
} catch (validationError) {
|
|
526
388
|
throw new MastraError(
|
|
527
389
|
{
|
|
528
|
-
id: createStorageErrorId("LANCE", "
|
|
390
|
+
id: createStorageErrorId("LANCE", "INSERT", "INVALID_ARGS"),
|
|
529
391
|
domain: ErrorDomain.STORAGE,
|
|
530
|
-
category: ErrorCategory.
|
|
392
|
+
category: ErrorCategory.USER,
|
|
393
|
+
text: validationError.message,
|
|
394
|
+
details: { tableName }
|
|
531
395
|
},
|
|
532
|
-
|
|
396
|
+
validationError
|
|
533
397
|
);
|
|
534
398
|
}
|
|
535
|
-
}
|
|
536
|
-
/**
|
|
537
|
-
* Processes messages to include context messages based on withPreviousMessages and withNextMessages
|
|
538
|
-
* @param records - The sorted array of records to process
|
|
539
|
-
* @param include - The array of include specifications with context parameters
|
|
540
|
-
* @returns The processed array with context messages included
|
|
541
|
-
*/
|
|
542
|
-
processMessagesWithContext(records, include) {
|
|
543
|
-
const messagesWithContext = include.filter((item) => item.withPreviousMessages || item.withNextMessages);
|
|
544
|
-
if (messagesWithContext.length === 0) {
|
|
545
|
-
return records;
|
|
546
|
-
}
|
|
547
|
-
const messageIndexMap = /* @__PURE__ */ new Map();
|
|
548
|
-
records.forEach((message, index) => {
|
|
549
|
-
messageIndexMap.set(message.id, index);
|
|
550
|
-
});
|
|
551
|
-
const additionalIndices = /* @__PURE__ */ new Set();
|
|
552
|
-
for (const item of messagesWithContext) {
|
|
553
|
-
const messageIndex = messageIndexMap.get(item.id);
|
|
554
|
-
if (messageIndex !== void 0) {
|
|
555
|
-
if (item.withPreviousMessages) {
|
|
556
|
-
const startIdx = Math.max(0, messageIndex - item.withPreviousMessages);
|
|
557
|
-
for (let i = startIdx; i < messageIndex; i++) {
|
|
558
|
-
additionalIndices.add(i);
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
if (item.withNextMessages) {
|
|
562
|
-
const endIdx = Math.min(records.length - 1, messageIndex + item.withNextMessages);
|
|
563
|
-
for (let i = messageIndex + 1; i <= endIdx; i++) {
|
|
564
|
-
additionalIndices.add(i);
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
if (additionalIndices.size === 0) {
|
|
570
|
-
return records;
|
|
571
|
-
}
|
|
572
|
-
const originalMatchIds = new Set(include.map((item) => item.id));
|
|
573
|
-
const allIndices = /* @__PURE__ */ new Set();
|
|
574
|
-
records.forEach((record, index) => {
|
|
575
|
-
if (originalMatchIds.has(record.id)) {
|
|
576
|
-
allIndices.add(index);
|
|
577
|
-
}
|
|
578
|
-
});
|
|
579
|
-
additionalIndices.forEach((index) => {
|
|
580
|
-
allIndices.add(index);
|
|
581
|
-
});
|
|
582
|
-
return Array.from(allIndices).sort((a, b) => a - b).map((index) => records[index]);
|
|
583
|
-
}
|
|
584
|
-
/**
|
|
585
|
-
* Parse message data from LanceDB record format to MastraDBMessage format
|
|
586
|
-
*/
|
|
587
|
-
parseMessageData(data) {
|
|
588
|
-
const { thread_id, ...rest } = data;
|
|
589
|
-
return {
|
|
590
|
-
...rest,
|
|
591
|
-
threadId: thread_id,
|
|
592
|
-
content: typeof data.content === "string" ? (() => {
|
|
593
|
-
try {
|
|
594
|
-
return JSON.parse(data.content);
|
|
595
|
-
} catch {
|
|
596
|
-
return data.content;
|
|
597
|
-
}
|
|
598
|
-
})() : data.content,
|
|
599
|
-
createdAt: new Date(data.createdAt),
|
|
600
|
-
updatedAt: new Date(data.updatedAt)
|
|
601
|
-
};
|
|
602
|
-
}
|
|
603
|
-
async updateMessages(args) {
|
|
604
|
-
const { messages } = args;
|
|
605
|
-
this.logger.debug("Updating messages", { count: messages.length });
|
|
606
|
-
if (!messages.length) {
|
|
607
|
-
return [];
|
|
608
|
-
}
|
|
609
|
-
const updatedMessages = [];
|
|
610
|
-
const affectedThreadIds = /* @__PURE__ */ new Set();
|
|
611
399
|
try {
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
const existingMsg = this.parseMessageData(existingMessage);
|
|
620
|
-
const originalThreadId = existingMsg.threadId;
|
|
621
|
-
affectedThreadIds.add(originalThreadId);
|
|
622
|
-
const updatePayload = {};
|
|
623
|
-
if ("role" in updates && updates.role !== void 0) updatePayload.role = updates.role;
|
|
624
|
-
if ("type" in updates && updates.type !== void 0) updatePayload.type = updates.type;
|
|
625
|
-
if ("resourceId" in updates && updates.resourceId !== void 0) updatePayload.resourceId = updates.resourceId;
|
|
626
|
-
if ("threadId" in updates && updates.threadId !== void 0 && updates.threadId !== null) {
|
|
627
|
-
updatePayload.thread_id = updates.threadId;
|
|
628
|
-
affectedThreadIds.add(updates.threadId);
|
|
629
|
-
}
|
|
630
|
-
if (updates.content) {
|
|
631
|
-
const existingContent = existingMsg.content;
|
|
632
|
-
let newContent = { ...existingContent };
|
|
633
|
-
if (updates.content.metadata !== void 0) {
|
|
634
|
-
newContent.metadata = {
|
|
635
|
-
...existingContent.metadata || {},
|
|
636
|
-
...updates.content.metadata || {}
|
|
637
|
-
};
|
|
638
|
-
}
|
|
639
|
-
if (updates.content.content !== void 0) {
|
|
640
|
-
newContent.content = updates.content.content;
|
|
641
|
-
}
|
|
642
|
-
if ("parts" in updates.content && updates.content.parts !== void 0) {
|
|
643
|
-
newContent.parts = updates.content.parts;
|
|
644
|
-
}
|
|
645
|
-
updatePayload.content = JSON.stringify(newContent);
|
|
646
|
-
}
|
|
647
|
-
await this.operations.insert({ tableName: TABLE_MESSAGES, record: { id, ...updatePayload } });
|
|
648
|
-
const updatedMessage = await this.operations.load({ tableName: TABLE_MESSAGES, keys: { id } });
|
|
649
|
-
if (updatedMessage) {
|
|
650
|
-
updatedMessages.push(this.parseMessageData(updatedMessage));
|
|
400
|
+
const table = await this.client.openTable(tableName);
|
|
401
|
+
const primaryId = getPrimaryKeys(tableName);
|
|
402
|
+
const processedRecord = { ...record };
|
|
403
|
+
for (const key in processedRecord) {
|
|
404
|
+
if (processedRecord[key] !== null && typeof processedRecord[key] === "object" && !(processedRecord[key] instanceof Date)) {
|
|
405
|
+
this.logger.debug("Converting object to JSON string: ", processedRecord[key]);
|
|
406
|
+
processedRecord[key] = JSON.stringify(processedRecord[key]);
|
|
651
407
|
}
|
|
652
408
|
}
|
|
653
|
-
|
|
654
|
-
await this.operations.insert({
|
|
655
|
-
tableName: TABLE_THREADS,
|
|
656
|
-
record: { id: threadId, updatedAt: Date.now() }
|
|
657
|
-
});
|
|
658
|
-
}
|
|
659
|
-
return updatedMessages;
|
|
409
|
+
await table.mergeInsert(primaryId).whenMatchedUpdateAll().whenNotMatchedInsertAll().execute([processedRecord]);
|
|
660
410
|
} catch (error) {
|
|
661
411
|
throw new MastraError(
|
|
662
412
|
{
|
|
663
|
-
id: createStorageErrorId("LANCE", "
|
|
413
|
+
id: createStorageErrorId("LANCE", "INSERT", "FAILED"),
|
|
664
414
|
domain: ErrorDomain.STORAGE,
|
|
665
415
|
category: ErrorCategory.THIRD_PARTY,
|
|
666
|
-
details: {
|
|
416
|
+
details: { tableName }
|
|
667
417
|
},
|
|
668
418
|
error
|
|
669
419
|
);
|
|
670
420
|
}
|
|
671
421
|
}
|
|
672
|
-
async
|
|
422
|
+
async batchInsert({ tableName, records }) {
|
|
673
423
|
try {
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
return null;
|
|
677
|
-
}
|
|
678
|
-
let createdAt;
|
|
679
|
-
let updatedAt;
|
|
680
|
-
try {
|
|
681
|
-
if (resource.createdAt instanceof Date) {
|
|
682
|
-
createdAt = resource.createdAt;
|
|
683
|
-
} else if (typeof resource.createdAt === "string") {
|
|
684
|
-
createdAt = new Date(resource.createdAt);
|
|
685
|
-
} else if (typeof resource.createdAt === "number") {
|
|
686
|
-
createdAt = new Date(resource.createdAt);
|
|
687
|
-
} else {
|
|
688
|
-
createdAt = /* @__PURE__ */ new Date();
|
|
689
|
-
}
|
|
690
|
-
if (isNaN(createdAt.getTime())) {
|
|
691
|
-
createdAt = /* @__PURE__ */ new Date();
|
|
692
|
-
}
|
|
693
|
-
} catch {
|
|
694
|
-
createdAt = /* @__PURE__ */ new Date();
|
|
695
|
-
}
|
|
696
|
-
try {
|
|
697
|
-
if (resource.updatedAt instanceof Date) {
|
|
698
|
-
updatedAt = resource.updatedAt;
|
|
699
|
-
} else if (typeof resource.updatedAt === "string") {
|
|
700
|
-
updatedAt = new Date(resource.updatedAt);
|
|
701
|
-
} else if (typeof resource.updatedAt === "number") {
|
|
702
|
-
updatedAt = new Date(resource.updatedAt);
|
|
703
|
-
} else {
|
|
704
|
-
updatedAt = /* @__PURE__ */ new Date();
|
|
705
|
-
}
|
|
706
|
-
if (isNaN(updatedAt.getTime())) {
|
|
707
|
-
updatedAt = /* @__PURE__ */ new Date();
|
|
708
|
-
}
|
|
709
|
-
} catch {
|
|
710
|
-
updatedAt = /* @__PURE__ */ new Date();
|
|
424
|
+
if (!this.client) {
|
|
425
|
+
throw new Error("LanceDB client not initialized. Call LanceStorage.create() first.");
|
|
711
426
|
}
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
workingMemory = void 0;
|
|
715
|
-
} else if (workingMemory === "") {
|
|
716
|
-
workingMemory = "";
|
|
717
|
-
} else if (typeof workingMemory === "object") {
|
|
718
|
-
workingMemory = JSON.stringify(workingMemory);
|
|
427
|
+
if (!tableName) {
|
|
428
|
+
throw new Error("tableName is required for batchInsert.");
|
|
719
429
|
}
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
metadata = void 0;
|
|
723
|
-
} else if (typeof metadata === "string") {
|
|
724
|
-
try {
|
|
725
|
-
metadata = JSON.parse(metadata);
|
|
726
|
-
} catch {
|
|
727
|
-
metadata = metadata;
|
|
728
|
-
}
|
|
430
|
+
if (!records || records.length === 0) {
|
|
431
|
+
throw new Error("records array is required and cannot be empty for batchInsert.");
|
|
729
432
|
}
|
|
730
|
-
|
|
731
|
-
...resource,
|
|
732
|
-
createdAt,
|
|
733
|
-
updatedAt,
|
|
734
|
-
workingMemory,
|
|
735
|
-
metadata
|
|
736
|
-
};
|
|
737
|
-
} catch (error) {
|
|
433
|
+
} catch (validationError) {
|
|
738
434
|
throw new MastraError(
|
|
739
435
|
{
|
|
740
|
-
id: createStorageErrorId("LANCE", "
|
|
436
|
+
id: createStorageErrorId("LANCE", "BATCH_INSERT", "INVALID_ARGS"),
|
|
741
437
|
domain: ErrorDomain.STORAGE,
|
|
742
|
-
category: ErrorCategory.
|
|
438
|
+
category: ErrorCategory.USER,
|
|
439
|
+
text: validationError.message,
|
|
440
|
+
details: { tableName }
|
|
743
441
|
},
|
|
744
|
-
|
|
442
|
+
validationError
|
|
745
443
|
);
|
|
746
444
|
}
|
|
747
|
-
}
|
|
748
|
-
async saveResource({ resource }) {
|
|
749
445
|
try {
|
|
750
|
-
const
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
446
|
+
const table = await this.client.openTable(tableName);
|
|
447
|
+
const primaryId = getPrimaryKeys(tableName);
|
|
448
|
+
const processedRecords = records.map((record) => {
|
|
449
|
+
const processedRecord = { ...record };
|
|
450
|
+
for (const key in processedRecord) {
|
|
451
|
+
if (processedRecord[key] == null) continue;
|
|
452
|
+
if (processedRecord[key] !== null && typeof processedRecord[key] === "object" && !(processedRecord[key] instanceof Date)) {
|
|
453
|
+
processedRecord[key] = JSON.stringify(processedRecord[key]);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
return processedRecord;
|
|
457
|
+
});
|
|
458
|
+
await table.mergeInsert(primaryId).whenMatchedUpdateAll().whenNotMatchedInsertAll().execute(processedRecords);
|
|
761
459
|
} catch (error) {
|
|
762
460
|
throw new MastraError(
|
|
763
461
|
{
|
|
764
|
-
id: createStorageErrorId("LANCE", "
|
|
462
|
+
id: createStorageErrorId("LANCE", "BATCH_INSERT", "FAILED"),
|
|
765
463
|
domain: ErrorDomain.STORAGE,
|
|
766
|
-
category: ErrorCategory.THIRD_PARTY
|
|
464
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
465
|
+
details: { tableName }
|
|
767
466
|
},
|
|
768
467
|
error
|
|
769
468
|
);
|
|
770
469
|
}
|
|
771
470
|
}
|
|
772
|
-
async
|
|
773
|
-
resourceId,
|
|
774
|
-
workingMemory,
|
|
775
|
-
metadata
|
|
776
|
-
}) {
|
|
777
|
-
const maxRetries = 3;
|
|
778
|
-
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
779
|
-
try {
|
|
780
|
-
const existingResource = await this.getResourceById({ resourceId });
|
|
781
|
-
if (!existingResource) {
|
|
782
|
-
const newResource = {
|
|
783
|
-
id: resourceId,
|
|
784
|
-
workingMemory,
|
|
785
|
-
metadata: metadata || {},
|
|
786
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
787
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
788
|
-
};
|
|
789
|
-
return this.saveResource({ resource: newResource });
|
|
790
|
-
}
|
|
791
|
-
const updatedResource = {
|
|
792
|
-
...existingResource,
|
|
793
|
-
workingMemory: workingMemory !== void 0 ? workingMemory : existingResource.workingMemory,
|
|
794
|
-
metadata: {
|
|
795
|
-
...existingResource.metadata,
|
|
796
|
-
...metadata
|
|
797
|
-
},
|
|
798
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
799
|
-
};
|
|
800
|
-
const record = {
|
|
801
|
-
id: resourceId,
|
|
802
|
-
workingMemory: updatedResource.workingMemory || "",
|
|
803
|
-
metadata: updatedResource.metadata ? JSON.stringify(updatedResource.metadata) : "",
|
|
804
|
-
updatedAt: updatedResource.updatedAt.getTime()
|
|
805
|
-
// Store as timestamp (milliseconds)
|
|
806
|
-
};
|
|
807
|
-
const table = await this.client.openTable(TABLE_RESOURCES);
|
|
808
|
-
await table.mergeInsert("id").whenMatchedUpdateAll().whenNotMatchedInsertAll().execute([record]);
|
|
809
|
-
return updatedResource;
|
|
810
|
-
} catch (error) {
|
|
811
|
-
if (error.message?.includes("Commit conflict") && attempt < maxRetries - 1) {
|
|
812
|
-
const delay = Math.pow(2, attempt) * 10;
|
|
813
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
814
|
-
continue;
|
|
815
|
-
}
|
|
816
|
-
throw new MastraError(
|
|
817
|
-
{
|
|
818
|
-
id: createStorageErrorId("LANCE", "UPDATE_RESOURCE", "FAILED"),
|
|
819
|
-
domain: ErrorDomain.STORAGE,
|
|
820
|
-
category: ErrorCategory.THIRD_PARTY
|
|
821
|
-
},
|
|
822
|
-
error
|
|
823
|
-
);
|
|
824
|
-
}
|
|
825
|
-
}
|
|
826
|
-
throw new Error("Unexpected end of retry loop");
|
|
827
|
-
}
|
|
828
|
-
};
|
|
829
|
-
var StoreOperationsLance = class extends StoreOperations {
|
|
830
|
-
client;
|
|
831
|
-
constructor({ client }) {
|
|
832
|
-
super();
|
|
833
|
-
this.client = client;
|
|
834
|
-
}
|
|
835
|
-
getDefaultValue(type) {
|
|
836
|
-
switch (type) {
|
|
837
|
-
case "text":
|
|
838
|
-
return "''";
|
|
839
|
-
case "timestamp":
|
|
840
|
-
return "CURRENT_TIMESTAMP";
|
|
841
|
-
case "integer":
|
|
842
|
-
case "bigint":
|
|
843
|
-
return "0";
|
|
844
|
-
case "jsonb":
|
|
845
|
-
return "'{}'";
|
|
846
|
-
case "uuid":
|
|
847
|
-
return "''";
|
|
848
|
-
default:
|
|
849
|
-
return super.getDefaultValue(type);
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
async hasColumn(tableName, columnName) {
|
|
853
|
-
const table = await this.client.openTable(tableName);
|
|
854
|
-
const schema = await table.schema();
|
|
855
|
-
return schema.fields.some((field) => field.name === columnName);
|
|
856
|
-
}
|
|
857
|
-
translateSchema(schema) {
|
|
858
|
-
const fields = Object.entries(schema).map(([name, column]) => {
|
|
859
|
-
let arrowType;
|
|
860
|
-
switch (column.type.toLowerCase()) {
|
|
861
|
-
case "text":
|
|
862
|
-
case "uuid":
|
|
863
|
-
arrowType = new Utf8();
|
|
864
|
-
break;
|
|
865
|
-
case "int":
|
|
866
|
-
case "integer":
|
|
867
|
-
arrowType = new Int32();
|
|
868
|
-
break;
|
|
869
|
-
case "bigint":
|
|
870
|
-
arrowType = new Float64();
|
|
871
|
-
break;
|
|
872
|
-
case "float":
|
|
873
|
-
arrowType = new Float32();
|
|
874
|
-
break;
|
|
875
|
-
case "jsonb":
|
|
876
|
-
case "json":
|
|
877
|
-
arrowType = new Utf8();
|
|
878
|
-
break;
|
|
879
|
-
case "binary":
|
|
880
|
-
arrowType = new Binary();
|
|
881
|
-
break;
|
|
882
|
-
case "timestamp":
|
|
883
|
-
arrowType = new Float64();
|
|
884
|
-
break;
|
|
885
|
-
default:
|
|
886
|
-
arrowType = new Utf8();
|
|
887
|
-
}
|
|
888
|
-
return new Field(name, arrowType, column.nullable ?? true);
|
|
889
|
-
});
|
|
890
|
-
return new Schema(fields);
|
|
891
|
-
}
|
|
892
|
-
async createTable({
|
|
893
|
-
tableName,
|
|
894
|
-
schema
|
|
895
|
-
}) {
|
|
471
|
+
async load({ tableName, keys }) {
|
|
896
472
|
try {
|
|
897
473
|
if (!this.client) {
|
|
898
474
|
throw new Error("LanceDB client not initialized. Call LanceStorage.create() first.");
|
|
899
475
|
}
|
|
900
476
|
if (!tableName) {
|
|
901
|
-
throw new Error("tableName is required for
|
|
477
|
+
throw new Error("tableName is required for load.");
|
|
902
478
|
}
|
|
903
|
-
if (!
|
|
904
|
-
throw new Error("
|
|
479
|
+
if (!keys || Object.keys(keys).length === 0) {
|
|
480
|
+
throw new Error("keys are required and cannot be empty for load.");
|
|
905
481
|
}
|
|
906
|
-
} catch (
|
|
482
|
+
} catch (validationError) {
|
|
907
483
|
throw new MastraError(
|
|
908
484
|
{
|
|
909
|
-
id: createStorageErrorId("LANCE", "
|
|
485
|
+
id: createStorageErrorId("LANCE", "LOAD", "INVALID_ARGS"),
|
|
910
486
|
domain: ErrorDomain.STORAGE,
|
|
911
487
|
category: ErrorCategory.USER,
|
|
488
|
+
text: validationError.message,
|
|
912
489
|
details: { tableName }
|
|
913
490
|
},
|
|
914
|
-
|
|
491
|
+
validationError
|
|
915
492
|
);
|
|
916
493
|
}
|
|
917
494
|
try {
|
|
918
|
-
const
|
|
919
|
-
await
|
|
920
|
-
|
|
921
|
-
if (
|
|
922
|
-
|
|
923
|
-
|
|
495
|
+
const table = await this.client.openTable(tableName);
|
|
496
|
+
const tableSchema = await getTableSchema({ tableName, client: this.client });
|
|
497
|
+
const query = table.query();
|
|
498
|
+
if (Object.keys(keys).length > 0) {
|
|
499
|
+
validateKeyTypes(keys, tableSchema);
|
|
500
|
+
const filterConditions = Object.entries(keys).map(([key, value]) => {
|
|
501
|
+
const isCamelCase = /^[a-z][a-zA-Z]*$/.test(key) && /[A-Z]/.test(key);
|
|
502
|
+
const quotedKey = isCamelCase ? `\`${key}\`` : key;
|
|
503
|
+
if (typeof value === "string") {
|
|
504
|
+
return `${quotedKey} = '${value}'`;
|
|
505
|
+
} else if (value === null) {
|
|
506
|
+
return `${quotedKey} IS NULL`;
|
|
507
|
+
} else {
|
|
508
|
+
return `${quotedKey} = ${value}`;
|
|
509
|
+
}
|
|
510
|
+
}).join(" AND ");
|
|
511
|
+
this.logger.debug("where clause generated: " + filterConditions);
|
|
512
|
+
query.where(filterConditions);
|
|
513
|
+
}
|
|
514
|
+
const result = await query.limit(1).toArray();
|
|
515
|
+
if (result.length === 0) {
|
|
516
|
+
this.logger.debug("No record found");
|
|
517
|
+
return null;
|
|
924
518
|
}
|
|
519
|
+
return processResultWithTypeConversion(result[0], tableSchema);
|
|
520
|
+
} catch (error) {
|
|
521
|
+
if (error instanceof MastraError) throw error;
|
|
925
522
|
throw new MastraError(
|
|
926
523
|
{
|
|
927
|
-
id: createStorageErrorId("LANCE", "
|
|
524
|
+
id: createStorageErrorId("LANCE", "LOAD", "FAILED"),
|
|
928
525
|
domain: ErrorDomain.STORAGE,
|
|
929
526
|
category: ErrorCategory.THIRD_PARTY,
|
|
930
|
-
details: { tableName }
|
|
527
|
+
details: { tableName, keyCount: Object.keys(keys).length, firstKey: Object.keys(keys)[0] ?? "" }
|
|
931
528
|
},
|
|
932
529
|
error
|
|
933
530
|
);
|
|
934
531
|
}
|
|
935
532
|
}
|
|
936
|
-
|
|
533
|
+
};
|
|
534
|
+
|
|
535
|
+
// src/storage/domains/memory/index.ts
|
|
536
|
+
var StoreMemoryLance = class extends MemoryStorage {
|
|
537
|
+
client;
|
|
538
|
+
#db;
|
|
539
|
+
constructor(config) {
|
|
540
|
+
super();
|
|
541
|
+
const client = resolveLanceConfig(config);
|
|
542
|
+
this.client = client;
|
|
543
|
+
this.#db = new LanceDB({ client });
|
|
544
|
+
}
|
|
545
|
+
async init() {
|
|
546
|
+
await this.#db.createTable({ tableName: TABLE_THREADS, schema: TABLE_SCHEMAS[TABLE_THREADS] });
|
|
547
|
+
await this.#db.createTable({ tableName: TABLE_MESSAGES, schema: TABLE_SCHEMAS[TABLE_MESSAGES] });
|
|
548
|
+
await this.#db.createTable({ tableName: TABLE_RESOURCES, schema: TABLE_SCHEMAS[TABLE_RESOURCES] });
|
|
549
|
+
await this.#db.alterTable({
|
|
550
|
+
tableName: TABLE_MESSAGES,
|
|
551
|
+
schema: TABLE_SCHEMAS[TABLE_MESSAGES],
|
|
552
|
+
ifNotExists: ["resourceId"]
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
async dangerouslyClearAll() {
|
|
556
|
+
await this.#db.clearTable({ tableName: TABLE_THREADS });
|
|
557
|
+
await this.#db.clearTable({ tableName: TABLE_MESSAGES });
|
|
558
|
+
await this.#db.clearTable({ tableName: TABLE_RESOURCES });
|
|
559
|
+
}
|
|
560
|
+
async deleteMessages(messageIds) {
|
|
561
|
+
if (!messageIds || messageIds.length === 0) {
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
this.logger.debug("Deleting messages", { count: messageIds.length });
|
|
937
565
|
try {
|
|
938
|
-
|
|
939
|
-
|
|
566
|
+
const threadIds = /* @__PURE__ */ new Set();
|
|
567
|
+
for (const messageId of messageIds) {
|
|
568
|
+
const message = await this.#db.load({ tableName: TABLE_MESSAGES, keys: { id: messageId } });
|
|
569
|
+
if (message?.thread_id) {
|
|
570
|
+
threadIds.add(message.thread_id);
|
|
571
|
+
}
|
|
940
572
|
}
|
|
941
|
-
|
|
942
|
-
|
|
573
|
+
const messagesTable = await this.client.openTable(TABLE_MESSAGES);
|
|
574
|
+
const idConditions = messageIds.map((id) => `id = '${this.escapeSql(id)}'`).join(" OR ");
|
|
575
|
+
await messagesTable.delete(idConditions);
|
|
576
|
+
const now = (/* @__PURE__ */ new Date()).getTime();
|
|
577
|
+
const threadsTable = await this.client.openTable(TABLE_THREADS);
|
|
578
|
+
for (const threadId of threadIds) {
|
|
579
|
+
const thread = await this.getThreadById({ threadId });
|
|
580
|
+
if (thread) {
|
|
581
|
+
const record = {
|
|
582
|
+
id: threadId,
|
|
583
|
+
resourceId: thread.resourceId,
|
|
584
|
+
title: thread.title,
|
|
585
|
+
metadata: JSON.stringify(thread.metadata),
|
|
586
|
+
createdAt: new Date(thread.createdAt).getTime(),
|
|
587
|
+
updatedAt: now
|
|
588
|
+
};
|
|
589
|
+
await threadsTable.mergeInsert("id").whenMatchedUpdateAll().whenNotMatchedInsertAll().execute([record]);
|
|
590
|
+
}
|
|
943
591
|
}
|
|
944
|
-
} catch (
|
|
592
|
+
} catch (error) {
|
|
945
593
|
throw new MastraError(
|
|
946
594
|
{
|
|
947
|
-
id: createStorageErrorId("LANCE", "
|
|
595
|
+
id: createStorageErrorId("LANCE", "DELETE_MESSAGES", "FAILED"),
|
|
948
596
|
domain: ErrorDomain.STORAGE,
|
|
949
|
-
category: ErrorCategory.
|
|
950
|
-
|
|
951
|
-
details: { tableName }
|
|
597
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
598
|
+
details: { count: messageIds.length }
|
|
952
599
|
},
|
|
953
|
-
|
|
600
|
+
error
|
|
954
601
|
);
|
|
955
602
|
}
|
|
603
|
+
}
|
|
604
|
+
// Utility to escape single quotes in SQL strings
|
|
605
|
+
escapeSql(str) {
|
|
606
|
+
return str.replace(/'/g, "''");
|
|
607
|
+
}
|
|
608
|
+
async getThreadById({ threadId }) {
|
|
956
609
|
try {
|
|
957
|
-
await this.
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
this.logger.debug(`Table '${tableName}' does not exist, skipping drop`);
|
|
961
|
-
return;
|
|
610
|
+
const thread = await this.#db.load({ tableName: TABLE_THREADS, keys: { id: threadId } });
|
|
611
|
+
if (!thread) {
|
|
612
|
+
return null;
|
|
962
613
|
}
|
|
614
|
+
return {
|
|
615
|
+
...thread,
|
|
616
|
+
createdAt: new Date(thread.createdAt),
|
|
617
|
+
updatedAt: new Date(thread.updatedAt)
|
|
618
|
+
};
|
|
619
|
+
} catch (error) {
|
|
963
620
|
throw new MastraError(
|
|
964
621
|
{
|
|
965
|
-
id: createStorageErrorId("LANCE", "
|
|
622
|
+
id: createStorageErrorId("LANCE", "GET_THREAD_BY_ID", "FAILED"),
|
|
966
623
|
domain: ErrorDomain.STORAGE,
|
|
967
|
-
category: ErrorCategory.THIRD_PARTY
|
|
968
|
-
details: { tableName }
|
|
624
|
+
category: ErrorCategory.THIRD_PARTY
|
|
969
625
|
},
|
|
970
626
|
error
|
|
971
627
|
);
|
|
972
628
|
}
|
|
973
629
|
}
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
630
|
+
/**
|
|
631
|
+
* Saves a thread to the database. This function doesn't overwrite existing threads.
|
|
632
|
+
* @param thread - The thread to save
|
|
633
|
+
* @returns The saved thread
|
|
634
|
+
*/
|
|
635
|
+
async saveThread({ thread }) {
|
|
979
636
|
try {
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
}
|
|
986
|
-
if (!schema) {
|
|
987
|
-
throw new Error("schema is required for alterTable.");
|
|
988
|
-
}
|
|
989
|
-
if (!ifNotExists || ifNotExists.length === 0) {
|
|
990
|
-
this.logger.debug("No columns specified to add in alterTable, skipping.");
|
|
991
|
-
return;
|
|
992
|
-
}
|
|
993
|
-
} catch (validationError) {
|
|
637
|
+
const record = { ...thread, metadata: JSON.stringify(thread.metadata) };
|
|
638
|
+
const table = await this.client.openTable(TABLE_THREADS);
|
|
639
|
+
await table.add([record], { mode: "append" });
|
|
640
|
+
return thread;
|
|
641
|
+
} catch (error) {
|
|
994
642
|
throw new MastraError(
|
|
995
643
|
{
|
|
996
|
-
id: createStorageErrorId("LANCE", "
|
|
644
|
+
id: createStorageErrorId("LANCE", "SAVE_THREAD", "FAILED"),
|
|
997
645
|
domain: ErrorDomain.STORAGE,
|
|
998
|
-
category: ErrorCategory.
|
|
999
|
-
text: validationError.message,
|
|
1000
|
-
details: { tableName }
|
|
646
|
+
category: ErrorCategory.THIRD_PARTY
|
|
1001
647
|
},
|
|
1002
|
-
|
|
648
|
+
error
|
|
1003
649
|
);
|
|
1004
650
|
}
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
const
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
651
|
+
}
|
|
652
|
+
async updateThread({
|
|
653
|
+
id,
|
|
654
|
+
title,
|
|
655
|
+
metadata
|
|
656
|
+
}) {
|
|
657
|
+
const maxRetries = 5;
|
|
658
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
659
|
+
try {
|
|
660
|
+
const current = await this.getThreadById({ threadId: id });
|
|
661
|
+
if (!current) {
|
|
662
|
+
throw new Error(`Thread with id ${id} not found`);
|
|
663
|
+
}
|
|
664
|
+
const mergedMetadata = { ...current.metadata, ...metadata };
|
|
665
|
+
const record = {
|
|
666
|
+
id,
|
|
667
|
+
title,
|
|
668
|
+
metadata: JSON.stringify(mergedMetadata),
|
|
669
|
+
updatedAt: (/* @__PURE__ */ new Date()).getTime()
|
|
1022
670
|
};
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
await
|
|
1026
|
-
|
|
671
|
+
const table = await this.client.openTable(TABLE_THREADS);
|
|
672
|
+
await table.mergeInsert("id").whenMatchedUpdateAll().whenNotMatchedInsertAll().execute([record]);
|
|
673
|
+
const updatedThread = await this.getThreadById({ threadId: id });
|
|
674
|
+
if (!updatedThread) {
|
|
675
|
+
throw new Error(`Failed to retrieve updated thread ${id}`);
|
|
676
|
+
}
|
|
677
|
+
return updatedThread;
|
|
678
|
+
} catch (error) {
|
|
679
|
+
if (error.message?.includes("Commit conflict") && attempt < maxRetries - 1) {
|
|
680
|
+
const delay = Math.pow(2, attempt) * 10;
|
|
681
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
682
|
+
continue;
|
|
683
|
+
}
|
|
684
|
+
throw new MastraError(
|
|
685
|
+
{
|
|
686
|
+
id: createStorageErrorId("LANCE", "UPDATE_THREAD", "FAILED"),
|
|
687
|
+
domain: ErrorDomain.STORAGE,
|
|
688
|
+
category: ErrorCategory.THIRD_PARTY
|
|
689
|
+
},
|
|
690
|
+
error
|
|
691
|
+
);
|
|
1027
692
|
}
|
|
1028
|
-
} catch (error) {
|
|
1029
|
-
throw new MastraError(
|
|
1030
|
-
{
|
|
1031
|
-
id: createStorageErrorId("LANCE", "ALTER_TABLE", "FAILED"),
|
|
1032
|
-
domain: ErrorDomain.STORAGE,
|
|
1033
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
1034
|
-
details: { tableName }
|
|
1035
|
-
},
|
|
1036
|
-
error
|
|
1037
|
-
);
|
|
1038
693
|
}
|
|
694
|
+
throw new MastraError(
|
|
695
|
+
{
|
|
696
|
+
id: createStorageErrorId("LANCE", "UPDATE_THREAD", "FAILED"),
|
|
697
|
+
domain: ErrorDomain.STORAGE,
|
|
698
|
+
category: ErrorCategory.THIRD_PARTY
|
|
699
|
+
},
|
|
700
|
+
new Error("All retries exhausted")
|
|
701
|
+
);
|
|
1039
702
|
}
|
|
1040
|
-
async
|
|
1041
|
-
try {
|
|
1042
|
-
if (!this.client) {
|
|
1043
|
-
throw new Error("LanceDB client not initialized. Call LanceStorage.create() first.");
|
|
1044
|
-
}
|
|
1045
|
-
if (!tableName) {
|
|
1046
|
-
throw new Error("tableName is required for clearTable.");
|
|
1047
|
-
}
|
|
1048
|
-
} catch (validationError) {
|
|
1049
|
-
throw new MastraError(
|
|
1050
|
-
{
|
|
1051
|
-
id: createStorageErrorId("LANCE", "CLEAR_TABLE", "INVALID_ARGS"),
|
|
1052
|
-
domain: ErrorDomain.STORAGE,
|
|
1053
|
-
category: ErrorCategory.USER,
|
|
1054
|
-
text: validationError.message,
|
|
1055
|
-
details: { tableName }
|
|
1056
|
-
},
|
|
1057
|
-
validationError
|
|
1058
|
-
);
|
|
1059
|
-
}
|
|
703
|
+
async deleteThread({ threadId }) {
|
|
1060
704
|
try {
|
|
1061
|
-
const table = await this.client.openTable(
|
|
1062
|
-
await table.delete(
|
|
705
|
+
const table = await this.client.openTable(TABLE_THREADS);
|
|
706
|
+
await table.delete(`id = '${threadId}'`);
|
|
707
|
+
const messagesTable = await this.client.openTable(TABLE_MESSAGES);
|
|
708
|
+
await messagesTable.delete(`thread_id = '${threadId}'`);
|
|
1063
709
|
} catch (error) {
|
|
1064
710
|
throw new MastraError(
|
|
1065
711
|
{
|
|
1066
|
-
id: createStorageErrorId("LANCE", "
|
|
712
|
+
id: createStorageErrorId("LANCE", "DELETE_THREAD", "FAILED"),
|
|
1067
713
|
domain: ErrorDomain.STORAGE,
|
|
1068
|
-
category: ErrorCategory.THIRD_PARTY
|
|
1069
|
-
details: { tableName }
|
|
714
|
+
category: ErrorCategory.THIRD_PARTY
|
|
1070
715
|
},
|
|
1071
716
|
error
|
|
1072
717
|
);
|
|
1073
718
|
}
|
|
1074
719
|
}
|
|
1075
|
-
|
|
720
|
+
normalizeMessage(message) {
|
|
721
|
+
const { thread_id, ...rest } = message;
|
|
722
|
+
return {
|
|
723
|
+
...rest,
|
|
724
|
+
threadId: thread_id,
|
|
725
|
+
content: typeof message.content === "string" ? (() => {
|
|
726
|
+
try {
|
|
727
|
+
return JSON.parse(message.content);
|
|
728
|
+
} catch {
|
|
729
|
+
return message.content;
|
|
730
|
+
}
|
|
731
|
+
})() : message.content
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
async listMessagesById({ messageIds }) {
|
|
735
|
+
if (messageIds.length === 0) return { messages: [] };
|
|
1076
736
|
try {
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
if (!record || Object.keys(record).length === 0) {
|
|
1084
|
-
throw new Error("record is required and cannot be empty for insert.");
|
|
1085
|
-
}
|
|
1086
|
-
} catch (validationError) {
|
|
1087
|
-
throw new MastraError(
|
|
1088
|
-
{
|
|
1089
|
-
id: createStorageErrorId("LANCE", "INSERT", "INVALID_ARGS"),
|
|
1090
|
-
domain: ErrorDomain.STORAGE,
|
|
1091
|
-
category: ErrorCategory.USER,
|
|
1092
|
-
text: validationError.message,
|
|
1093
|
-
details: { tableName }
|
|
1094
|
-
},
|
|
1095
|
-
validationError
|
|
737
|
+
const table = await this.client.openTable(TABLE_MESSAGES);
|
|
738
|
+
const quotedIds = messageIds.map((id) => `'${id}'`).join(", ");
|
|
739
|
+
const allRecords = await table.query().where(`id IN (${quotedIds})`).toArray();
|
|
740
|
+
const messages = processResultWithTypeConversion(
|
|
741
|
+
allRecords,
|
|
742
|
+
await getTableSchema({ tableName: TABLE_MESSAGES, client: this.client })
|
|
1096
743
|
);
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
for (const key in processedRecord) {
|
|
1103
|
-
if (processedRecord[key] !== null && typeof processedRecord[key] === "object" && !(processedRecord[key] instanceof Date)) {
|
|
1104
|
-
this.logger.debug("Converting object to JSON string: ", processedRecord[key]);
|
|
1105
|
-
processedRecord[key] = JSON.stringify(processedRecord[key]);
|
|
1106
|
-
}
|
|
1107
|
-
}
|
|
1108
|
-
await table.mergeInsert(primaryId).whenMatchedUpdateAll().whenNotMatchedInsertAll().execute([processedRecord]);
|
|
744
|
+
const list = new MessageList().add(
|
|
745
|
+
messages.map(this.normalizeMessage),
|
|
746
|
+
"memory"
|
|
747
|
+
);
|
|
748
|
+
return { messages: list.get.all.db() };
|
|
1109
749
|
} catch (error) {
|
|
1110
750
|
throw new MastraError(
|
|
1111
751
|
{
|
|
1112
|
-
id: createStorageErrorId("LANCE", "
|
|
752
|
+
id: createStorageErrorId("LANCE", "LIST_MESSAGES_BY_ID", "FAILED"),
|
|
1113
753
|
domain: ErrorDomain.STORAGE,
|
|
1114
754
|
category: ErrorCategory.THIRD_PARTY,
|
|
1115
|
-
details: {
|
|
755
|
+
details: {
|
|
756
|
+
messageIds: JSON.stringify(messageIds)
|
|
757
|
+
}
|
|
1116
758
|
},
|
|
1117
759
|
error
|
|
1118
760
|
);
|
|
1119
761
|
}
|
|
1120
762
|
}
|
|
1121
|
-
async
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
}
|
|
1126
|
-
if (!tableName) {
|
|
1127
|
-
throw new Error("tableName is required for batchInsert.");
|
|
1128
|
-
}
|
|
1129
|
-
if (!records || records.length === 0) {
|
|
1130
|
-
throw new Error("records array is required and cannot be empty for batchInsert.");
|
|
1131
|
-
}
|
|
1132
|
-
} catch (validationError) {
|
|
1133
|
-
throw new MastraError(
|
|
1134
|
-
{
|
|
1135
|
-
id: createStorageErrorId("LANCE", "BATCH_INSERT", "INVALID_ARGS"),
|
|
1136
|
-
domain: ErrorDomain.STORAGE,
|
|
1137
|
-
category: ErrorCategory.USER,
|
|
1138
|
-
text: validationError.message,
|
|
1139
|
-
details: { tableName }
|
|
1140
|
-
},
|
|
1141
|
-
validationError
|
|
1142
|
-
);
|
|
1143
|
-
}
|
|
1144
|
-
try {
|
|
1145
|
-
const table = await this.client.openTable(tableName);
|
|
1146
|
-
const primaryId = getPrimaryKeys(tableName);
|
|
1147
|
-
const processedRecords = records.map((record) => {
|
|
1148
|
-
const processedRecord = { ...record };
|
|
1149
|
-
for (const key in processedRecord) {
|
|
1150
|
-
if (processedRecord[key] == null) continue;
|
|
1151
|
-
if (processedRecord[key] !== null && typeof processedRecord[key] === "object" && !(processedRecord[key] instanceof Date)) {
|
|
1152
|
-
processedRecord[key] = JSON.stringify(processedRecord[key]);
|
|
1153
|
-
}
|
|
1154
|
-
}
|
|
1155
|
-
return processedRecord;
|
|
1156
|
-
});
|
|
1157
|
-
await table.mergeInsert(primaryId).whenMatchedUpdateAll().whenNotMatchedInsertAll().execute(processedRecords);
|
|
1158
|
-
} catch (error) {
|
|
763
|
+
async listMessages(args) {
|
|
764
|
+
const { threadId, resourceId, include, filter, perPage: perPageInput, page = 0, orderBy } = args;
|
|
765
|
+
const threadIds = Array.isArray(threadId) ? threadId : [threadId];
|
|
766
|
+
if (threadIds.length === 0 || threadIds.some((id) => !id.trim())) {
|
|
1159
767
|
throw new MastraError(
|
|
1160
768
|
{
|
|
1161
|
-
id: createStorageErrorId("LANCE", "
|
|
769
|
+
id: createStorageErrorId("LANCE", "LIST_MESSAGES", "INVALID_THREAD_ID"),
|
|
1162
770
|
domain: ErrorDomain.STORAGE,
|
|
1163
771
|
category: ErrorCategory.THIRD_PARTY,
|
|
1164
|
-
details: {
|
|
772
|
+
details: { threadId: Array.isArray(threadId) ? threadId.join(",") : threadId }
|
|
1165
773
|
},
|
|
1166
|
-
|
|
774
|
+
new Error("threadId must be a non-empty string or array of non-empty strings")
|
|
1167
775
|
);
|
|
1168
776
|
}
|
|
1169
|
-
|
|
1170
|
-
|
|
777
|
+
const perPage = normalizePerPage(perPageInput, 40);
|
|
778
|
+
const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
|
|
1171
779
|
try {
|
|
1172
|
-
if (
|
|
1173
|
-
throw new
|
|
780
|
+
if (page < 0) {
|
|
781
|
+
throw new MastraError(
|
|
782
|
+
{
|
|
783
|
+
id: createStorageErrorId("LANCE", "LIST_MESSAGES", "INVALID_PAGE"),
|
|
784
|
+
domain: ErrorDomain.STORAGE,
|
|
785
|
+
category: ErrorCategory.USER,
|
|
786
|
+
details: { page }
|
|
787
|
+
},
|
|
788
|
+
new Error("page must be >= 0")
|
|
789
|
+
);
|
|
1174
790
|
}
|
|
1175
|
-
|
|
1176
|
-
|
|
791
|
+
const { field, direction } = this.parseOrderBy(orderBy, "ASC");
|
|
792
|
+
const table = await this.client.openTable(TABLE_MESSAGES);
|
|
793
|
+
const threadCondition = threadIds.length === 1 ? `thread_id = '${this.escapeSql(threadIds[0])}'` : `thread_id IN (${threadIds.map((t) => `'${this.escapeSql(t)}'`).join(", ")})`;
|
|
794
|
+
const conditions = [threadCondition];
|
|
795
|
+
if (resourceId) {
|
|
796
|
+
conditions.push(`\`resourceId\` = '${this.escapeSql(resourceId)}'`);
|
|
1177
797
|
}
|
|
1178
|
-
if (
|
|
1179
|
-
|
|
798
|
+
if (filter?.dateRange?.start) {
|
|
799
|
+
const startTime = filter.dateRange.start instanceof Date ? filter.dateRange.start.getTime() : new Date(filter.dateRange.start).getTime();
|
|
800
|
+
conditions.push(`\`createdAt\` >= ${startTime}`);
|
|
1180
801
|
}
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
{
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
802
|
+
if (filter?.dateRange?.end) {
|
|
803
|
+
const endTime = filter.dateRange.end instanceof Date ? filter.dateRange.end.getTime() : new Date(filter.dateRange.end).getTime();
|
|
804
|
+
conditions.push(`\`createdAt\` <= ${endTime}`);
|
|
805
|
+
}
|
|
806
|
+
const whereClause = conditions.join(" AND ");
|
|
807
|
+
const total = await table.countRows(whereClause);
|
|
808
|
+
const query = table.query().where(whereClause);
|
|
809
|
+
let allRecords = await query.toArray();
|
|
810
|
+
allRecords.sort((a, b) => {
|
|
811
|
+
const aValue = field === "createdAt" ? a.createdAt : a[field];
|
|
812
|
+
const bValue = field === "createdAt" ? b.createdAt : b[field];
|
|
813
|
+
if (aValue == null && bValue == null) return 0;
|
|
814
|
+
if (aValue == null) return direction === "ASC" ? -1 : 1;
|
|
815
|
+
if (bValue == null) return direction === "ASC" ? 1 : -1;
|
|
816
|
+
if (typeof aValue === "string" && typeof bValue === "string") {
|
|
817
|
+
return direction === "ASC" ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue);
|
|
818
|
+
}
|
|
819
|
+
return direction === "ASC" ? aValue - bValue : bValue - aValue;
|
|
820
|
+
});
|
|
821
|
+
const paginatedRecords = allRecords.slice(offset, offset + perPage);
|
|
822
|
+
const messages = paginatedRecords.map((row) => this.normalizeMessage(row));
|
|
823
|
+
if (total === 0 && messages.length === 0 && (!include || include.length === 0)) {
|
|
824
|
+
return {
|
|
825
|
+
messages: [],
|
|
826
|
+
total: 0,
|
|
827
|
+
page,
|
|
828
|
+
perPage: perPageForResponse,
|
|
829
|
+
hasMore: false
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
const messageIds = new Set(messages.map((m) => m.id));
|
|
833
|
+
if (include && include.length > 0) {
|
|
834
|
+
const threadIds2 = [...new Set(include.map((item) => item.threadId || threadId))];
|
|
835
|
+
const allThreadMessages = [];
|
|
836
|
+
for (const tid of threadIds2) {
|
|
837
|
+
const threadQuery = table.query().where(`thread_id = '${tid}'`);
|
|
838
|
+
let threadRecords = await threadQuery.toArray();
|
|
839
|
+
allThreadMessages.push(...threadRecords);
|
|
840
|
+
}
|
|
841
|
+
allThreadMessages.sort((a, b) => a.createdAt - b.createdAt);
|
|
842
|
+
const contextMessages = this.processMessagesWithContext(allThreadMessages, include);
|
|
843
|
+
const includedMessages = contextMessages.map((row) => this.normalizeMessage(row));
|
|
844
|
+
for (const includeMsg of includedMessages) {
|
|
845
|
+
if (!messageIds.has(includeMsg.id)) {
|
|
846
|
+
messages.push(includeMsg);
|
|
847
|
+
messageIds.add(includeMsg.id);
|
|
1208
848
|
}
|
|
1209
|
-
}
|
|
1210
|
-
this.logger.debug("where clause generated: " + filterConditions);
|
|
1211
|
-
query.where(filterConditions);
|
|
849
|
+
}
|
|
1212
850
|
}
|
|
1213
|
-
const
|
|
1214
|
-
|
|
1215
|
-
|
|
851
|
+
const list = new MessageList().add(messages, "memory");
|
|
852
|
+
let finalMessages = list.get.all.db();
|
|
853
|
+
finalMessages = finalMessages.sort((a, b) => {
|
|
854
|
+
const aValue = field === "createdAt" ? new Date(a.createdAt).getTime() : a[field];
|
|
855
|
+
const bValue = field === "createdAt" ? new Date(b.createdAt).getTime() : b[field];
|
|
856
|
+
if (aValue == null && bValue == null) return 0;
|
|
857
|
+
if (aValue == null) return direction === "ASC" ? -1 : 1;
|
|
858
|
+
if (bValue == null) return direction === "ASC" ? 1 : -1;
|
|
859
|
+
if (typeof aValue === "string" && typeof bValue === "string") {
|
|
860
|
+
return direction === "ASC" ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue);
|
|
861
|
+
}
|
|
862
|
+
return direction === "ASC" ? aValue - bValue : bValue - aValue;
|
|
863
|
+
});
|
|
864
|
+
const returnedThreadMessageIds = new Set(finalMessages.filter((m) => m.threadId === threadId).map((m) => m.id));
|
|
865
|
+
const allThreadMessagesReturned = returnedThreadMessageIds.size >= total;
|
|
866
|
+
const fetchedAll = perPageInput === false || allThreadMessagesReturned;
|
|
867
|
+
const hasMore = !fetchedAll && offset + perPage < total;
|
|
868
|
+
return {
|
|
869
|
+
messages: finalMessages,
|
|
870
|
+
total,
|
|
871
|
+
page,
|
|
872
|
+
perPage: perPageForResponse,
|
|
873
|
+
hasMore
|
|
874
|
+
};
|
|
875
|
+
} catch (error) {
|
|
876
|
+
const mastraError = new MastraError(
|
|
877
|
+
{
|
|
878
|
+
id: createStorageErrorId("LANCE", "LIST_MESSAGES", "FAILED"),
|
|
879
|
+
domain: ErrorDomain.STORAGE,
|
|
880
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
881
|
+
details: {
|
|
882
|
+
threadId: Array.isArray(threadId) ? threadId.join(",") : threadId,
|
|
883
|
+
resourceId: resourceId ?? ""
|
|
884
|
+
}
|
|
885
|
+
},
|
|
886
|
+
error
|
|
887
|
+
);
|
|
888
|
+
this.logger?.error?.(mastraError.toString());
|
|
889
|
+
this.logger?.trackException?.(mastraError);
|
|
890
|
+
return {
|
|
891
|
+
messages: [],
|
|
892
|
+
total: 0,
|
|
893
|
+
page,
|
|
894
|
+
perPage: perPageForResponse,
|
|
895
|
+
hasMore: false
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
async saveMessages(args) {
|
|
900
|
+
try {
|
|
901
|
+
const { messages } = args;
|
|
902
|
+
if (messages.length === 0) {
|
|
903
|
+
return { messages: [] };
|
|
904
|
+
}
|
|
905
|
+
const threadId = messages[0]?.threadId;
|
|
906
|
+
if (!threadId) {
|
|
907
|
+
throw new Error("Thread ID is required");
|
|
908
|
+
}
|
|
909
|
+
for (const message of messages) {
|
|
910
|
+
if (!message.id) {
|
|
911
|
+
throw new Error("Message ID is required");
|
|
912
|
+
}
|
|
913
|
+
if (!message.threadId) {
|
|
914
|
+
throw new Error("Thread ID is required for all messages");
|
|
915
|
+
}
|
|
916
|
+
if (message.resourceId === null || message.resourceId === void 0) {
|
|
917
|
+
throw new Error("Resource ID cannot be null or undefined");
|
|
918
|
+
}
|
|
919
|
+
if (!message.content) {
|
|
920
|
+
throw new Error("Message content is required");
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
const transformedMessages = messages.map((message) => {
|
|
924
|
+
const { threadId: threadId2, type, ...rest } = message;
|
|
925
|
+
return {
|
|
926
|
+
...rest,
|
|
927
|
+
thread_id: threadId2,
|
|
928
|
+
type: type ?? "v2",
|
|
929
|
+
content: JSON.stringify(message.content)
|
|
930
|
+
};
|
|
931
|
+
});
|
|
932
|
+
const table = await this.client.openTable(TABLE_MESSAGES);
|
|
933
|
+
await table.mergeInsert("id").whenMatchedUpdateAll().whenNotMatchedInsertAll().execute(transformedMessages);
|
|
934
|
+
const threadsTable = await this.client.openTable(TABLE_THREADS);
|
|
935
|
+
const currentTime = (/* @__PURE__ */ new Date()).getTime();
|
|
936
|
+
const updateRecord = { id: threadId, updatedAt: currentTime };
|
|
937
|
+
await threadsTable.mergeInsert("id").whenMatchedUpdateAll().whenNotMatchedInsertAll().execute([updateRecord]);
|
|
938
|
+
const list = new MessageList().add(messages, "memory");
|
|
939
|
+
return { messages: list.get.all.db() };
|
|
940
|
+
} catch (error) {
|
|
941
|
+
throw new MastraError(
|
|
942
|
+
{
|
|
943
|
+
id: createStorageErrorId("LANCE", "SAVE_MESSAGES", "FAILED"),
|
|
944
|
+
domain: ErrorDomain.STORAGE,
|
|
945
|
+
category: ErrorCategory.THIRD_PARTY
|
|
946
|
+
},
|
|
947
|
+
error
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
async listThreadsByResourceId(args) {
|
|
952
|
+
try {
|
|
953
|
+
const { resourceId, page = 0, perPage: perPageInput, orderBy } = args;
|
|
954
|
+
const perPage = normalizePerPage(perPageInput, 100);
|
|
955
|
+
if (page < 0) {
|
|
956
|
+
throw new MastraError(
|
|
957
|
+
{
|
|
958
|
+
id: createStorageErrorId("LANCE", "LIST_THREADS_BY_RESOURCE_ID", "INVALID_PAGE"),
|
|
959
|
+
domain: ErrorDomain.STORAGE,
|
|
960
|
+
category: ErrorCategory.USER,
|
|
961
|
+
details: { page }
|
|
962
|
+
},
|
|
963
|
+
new Error("page must be >= 0")
|
|
964
|
+
);
|
|
965
|
+
}
|
|
966
|
+
const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
|
|
967
|
+
const { field, direction } = this.parseOrderBy(orderBy);
|
|
968
|
+
const table = await this.client.openTable(TABLE_THREADS);
|
|
969
|
+
const total = await table.countRows(`\`resourceId\` = '${this.escapeSql(resourceId)}'`);
|
|
970
|
+
const query = table.query().where(`\`resourceId\` = '${this.escapeSql(resourceId)}'`);
|
|
971
|
+
const records = await query.toArray();
|
|
972
|
+
records.sort((a, b) => {
|
|
973
|
+
const aValue = ["createdAt", "updatedAt"].includes(field) ? new Date(a[field]).getTime() : a[field];
|
|
974
|
+
const bValue = ["createdAt", "updatedAt"].includes(field) ? new Date(b[field]).getTime() : b[field];
|
|
975
|
+
if (aValue == null && bValue == null) return 0;
|
|
976
|
+
if (aValue == null) return direction === "ASC" ? -1 : 1;
|
|
977
|
+
if (bValue == null) return direction === "ASC" ? 1 : -1;
|
|
978
|
+
if (typeof aValue === "string" && typeof bValue === "string") {
|
|
979
|
+
return direction === "ASC" ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue);
|
|
980
|
+
}
|
|
981
|
+
return direction === "ASC" ? aValue - bValue : bValue - aValue;
|
|
982
|
+
});
|
|
983
|
+
const paginatedRecords = records.slice(offset, offset + perPage);
|
|
984
|
+
const schema = await getTableSchema({ tableName: TABLE_THREADS, client: this.client });
|
|
985
|
+
const threads = paginatedRecords.map(
|
|
986
|
+
(record) => processResultWithTypeConversion(record, schema)
|
|
987
|
+
);
|
|
988
|
+
return {
|
|
989
|
+
threads,
|
|
990
|
+
total,
|
|
991
|
+
page,
|
|
992
|
+
perPage: perPageForResponse,
|
|
993
|
+
hasMore: offset + perPage < total
|
|
994
|
+
};
|
|
995
|
+
} catch (error) {
|
|
996
|
+
throw new MastraError(
|
|
997
|
+
{
|
|
998
|
+
id: createStorageErrorId("LANCE", "LIST_THREADS_BY_RESOURCE_ID", "FAILED"),
|
|
999
|
+
domain: ErrorDomain.STORAGE,
|
|
1000
|
+
category: ErrorCategory.THIRD_PARTY
|
|
1001
|
+
},
|
|
1002
|
+
error
|
|
1003
|
+
);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
/**
|
|
1007
|
+
* Processes messages to include context messages based on withPreviousMessages and withNextMessages
|
|
1008
|
+
* @param records - The sorted array of records to process
|
|
1009
|
+
* @param include - The array of include specifications with context parameters
|
|
1010
|
+
* @returns The processed array with context messages included
|
|
1011
|
+
*/
|
|
1012
|
+
processMessagesWithContext(records, include) {
|
|
1013
|
+
const messagesWithContext = include.filter((item) => item.withPreviousMessages || item.withNextMessages);
|
|
1014
|
+
if (messagesWithContext.length === 0) {
|
|
1015
|
+
return records;
|
|
1016
|
+
}
|
|
1017
|
+
const messageIndexMap = /* @__PURE__ */ new Map();
|
|
1018
|
+
records.forEach((message, index) => {
|
|
1019
|
+
messageIndexMap.set(message.id, index);
|
|
1020
|
+
});
|
|
1021
|
+
const additionalIndices = /* @__PURE__ */ new Set();
|
|
1022
|
+
for (const item of messagesWithContext) {
|
|
1023
|
+
const messageIndex = messageIndexMap.get(item.id);
|
|
1024
|
+
if (messageIndex !== void 0) {
|
|
1025
|
+
if (item.withPreviousMessages) {
|
|
1026
|
+
const startIdx = Math.max(0, messageIndex - item.withPreviousMessages);
|
|
1027
|
+
for (let i = startIdx; i < messageIndex; i++) {
|
|
1028
|
+
additionalIndices.add(i);
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
if (item.withNextMessages) {
|
|
1032
|
+
const endIdx = Math.min(records.length - 1, messageIndex + item.withNextMessages);
|
|
1033
|
+
for (let i = messageIndex + 1; i <= endIdx; i++) {
|
|
1034
|
+
additionalIndices.add(i);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
if (additionalIndices.size === 0) {
|
|
1040
|
+
return records;
|
|
1041
|
+
}
|
|
1042
|
+
const originalMatchIds = new Set(include.map((item) => item.id));
|
|
1043
|
+
const allIndices = /* @__PURE__ */ new Set();
|
|
1044
|
+
records.forEach((record, index) => {
|
|
1045
|
+
if (originalMatchIds.has(record.id)) {
|
|
1046
|
+
allIndices.add(index);
|
|
1047
|
+
}
|
|
1048
|
+
});
|
|
1049
|
+
additionalIndices.forEach((index) => {
|
|
1050
|
+
allIndices.add(index);
|
|
1051
|
+
});
|
|
1052
|
+
return Array.from(allIndices).sort((a, b) => a - b).map((index) => records[index]);
|
|
1053
|
+
}
|
|
1054
|
+
/**
|
|
1055
|
+
* Parse message data from LanceDB record format to MastraDBMessage format
|
|
1056
|
+
*/
|
|
1057
|
+
parseMessageData(data) {
|
|
1058
|
+
const { thread_id, ...rest } = data;
|
|
1059
|
+
return {
|
|
1060
|
+
...rest,
|
|
1061
|
+
threadId: thread_id,
|
|
1062
|
+
content: typeof data.content === "string" ? (() => {
|
|
1063
|
+
try {
|
|
1064
|
+
return JSON.parse(data.content);
|
|
1065
|
+
} catch {
|
|
1066
|
+
return data.content;
|
|
1067
|
+
}
|
|
1068
|
+
})() : data.content,
|
|
1069
|
+
createdAt: new Date(data.createdAt),
|
|
1070
|
+
updatedAt: new Date(data.updatedAt)
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
async updateMessages(args) {
|
|
1074
|
+
const { messages } = args;
|
|
1075
|
+
this.logger.debug("Updating messages", { count: messages.length });
|
|
1076
|
+
if (!messages.length) {
|
|
1077
|
+
return [];
|
|
1078
|
+
}
|
|
1079
|
+
const updatedMessages = [];
|
|
1080
|
+
const affectedThreadIds = /* @__PURE__ */ new Set();
|
|
1081
|
+
try {
|
|
1082
|
+
for (const updateData of messages) {
|
|
1083
|
+
const { id, ...updates } = updateData;
|
|
1084
|
+
const existingMessage = await this.#db.load({ tableName: TABLE_MESSAGES, keys: { id } });
|
|
1085
|
+
if (!existingMessage) {
|
|
1086
|
+
this.logger.warn("Message not found for update", { id });
|
|
1087
|
+
continue;
|
|
1088
|
+
}
|
|
1089
|
+
const existingMsg = this.parseMessageData(existingMessage);
|
|
1090
|
+
const originalThreadId = existingMsg.threadId;
|
|
1091
|
+
affectedThreadIds.add(originalThreadId);
|
|
1092
|
+
const updatePayload = {};
|
|
1093
|
+
if ("role" in updates && updates.role !== void 0) updatePayload.role = updates.role;
|
|
1094
|
+
if ("type" in updates && updates.type !== void 0) updatePayload.type = updates.type;
|
|
1095
|
+
if ("resourceId" in updates && updates.resourceId !== void 0) updatePayload.resourceId = updates.resourceId;
|
|
1096
|
+
if ("threadId" in updates && updates.threadId !== void 0 && updates.threadId !== null) {
|
|
1097
|
+
updatePayload.thread_id = updates.threadId;
|
|
1098
|
+
affectedThreadIds.add(updates.threadId);
|
|
1099
|
+
}
|
|
1100
|
+
if (updates.content) {
|
|
1101
|
+
const existingContent = existingMsg.content;
|
|
1102
|
+
let newContent = { ...existingContent };
|
|
1103
|
+
if (updates.content.metadata !== void 0) {
|
|
1104
|
+
newContent.metadata = {
|
|
1105
|
+
...existingContent.metadata || {},
|
|
1106
|
+
...updates.content.metadata || {}
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
if (updates.content.content !== void 0) {
|
|
1110
|
+
newContent.content = updates.content.content;
|
|
1111
|
+
}
|
|
1112
|
+
if ("parts" in updates.content && updates.content.parts !== void 0) {
|
|
1113
|
+
newContent.parts = updates.content.parts;
|
|
1114
|
+
}
|
|
1115
|
+
updatePayload.content = JSON.stringify(newContent);
|
|
1116
|
+
}
|
|
1117
|
+
await this.#db.insert({ tableName: TABLE_MESSAGES, record: { id, ...updatePayload } });
|
|
1118
|
+
const updatedMessage = await this.#db.load({ tableName: TABLE_MESSAGES, keys: { id } });
|
|
1119
|
+
if (updatedMessage) {
|
|
1120
|
+
updatedMessages.push(this.parseMessageData(updatedMessage));
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
for (const threadId of affectedThreadIds) {
|
|
1124
|
+
await this.#db.insert({
|
|
1125
|
+
tableName: TABLE_THREADS,
|
|
1126
|
+
record: { id: threadId, updatedAt: Date.now() }
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
return updatedMessages;
|
|
1130
|
+
} catch (error) {
|
|
1131
|
+
throw new MastraError(
|
|
1132
|
+
{
|
|
1133
|
+
id: createStorageErrorId("LANCE", "UPDATE_MESSAGES", "FAILED"),
|
|
1134
|
+
domain: ErrorDomain.STORAGE,
|
|
1135
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1136
|
+
details: { count: messages.length }
|
|
1137
|
+
},
|
|
1138
|
+
error
|
|
1139
|
+
);
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
async getResourceById({ resourceId }) {
|
|
1143
|
+
try {
|
|
1144
|
+
const resource = await this.#db.load({ tableName: TABLE_RESOURCES, keys: { id: resourceId } });
|
|
1145
|
+
if (!resource) {
|
|
1216
1146
|
return null;
|
|
1217
1147
|
}
|
|
1218
|
-
|
|
1148
|
+
let createdAt;
|
|
1149
|
+
let updatedAt;
|
|
1150
|
+
try {
|
|
1151
|
+
if (resource.createdAt instanceof Date) {
|
|
1152
|
+
createdAt = resource.createdAt;
|
|
1153
|
+
} else if (typeof resource.createdAt === "string") {
|
|
1154
|
+
createdAt = new Date(resource.createdAt);
|
|
1155
|
+
} else if (typeof resource.createdAt === "number") {
|
|
1156
|
+
createdAt = new Date(resource.createdAt);
|
|
1157
|
+
} else {
|
|
1158
|
+
createdAt = /* @__PURE__ */ new Date();
|
|
1159
|
+
}
|
|
1160
|
+
if (isNaN(createdAt.getTime())) {
|
|
1161
|
+
createdAt = /* @__PURE__ */ new Date();
|
|
1162
|
+
}
|
|
1163
|
+
} catch {
|
|
1164
|
+
createdAt = /* @__PURE__ */ new Date();
|
|
1165
|
+
}
|
|
1166
|
+
try {
|
|
1167
|
+
if (resource.updatedAt instanceof Date) {
|
|
1168
|
+
updatedAt = resource.updatedAt;
|
|
1169
|
+
} else if (typeof resource.updatedAt === "string") {
|
|
1170
|
+
updatedAt = new Date(resource.updatedAt);
|
|
1171
|
+
} else if (typeof resource.updatedAt === "number") {
|
|
1172
|
+
updatedAt = new Date(resource.updatedAt);
|
|
1173
|
+
} else {
|
|
1174
|
+
updatedAt = /* @__PURE__ */ new Date();
|
|
1175
|
+
}
|
|
1176
|
+
if (isNaN(updatedAt.getTime())) {
|
|
1177
|
+
updatedAt = /* @__PURE__ */ new Date();
|
|
1178
|
+
}
|
|
1179
|
+
} catch {
|
|
1180
|
+
updatedAt = /* @__PURE__ */ new Date();
|
|
1181
|
+
}
|
|
1182
|
+
let workingMemory = resource.workingMemory;
|
|
1183
|
+
if (workingMemory === null || workingMemory === void 0) {
|
|
1184
|
+
workingMemory = void 0;
|
|
1185
|
+
} else if (workingMemory === "") {
|
|
1186
|
+
workingMemory = "";
|
|
1187
|
+
} else if (typeof workingMemory === "object") {
|
|
1188
|
+
workingMemory = JSON.stringify(workingMemory);
|
|
1189
|
+
}
|
|
1190
|
+
let metadata = resource.metadata;
|
|
1191
|
+
if (metadata === "" || metadata === null || metadata === void 0) {
|
|
1192
|
+
metadata = void 0;
|
|
1193
|
+
} else if (typeof metadata === "string") {
|
|
1194
|
+
try {
|
|
1195
|
+
metadata = JSON.parse(metadata);
|
|
1196
|
+
} catch {
|
|
1197
|
+
metadata = metadata;
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
return {
|
|
1201
|
+
...resource,
|
|
1202
|
+
createdAt,
|
|
1203
|
+
updatedAt,
|
|
1204
|
+
workingMemory,
|
|
1205
|
+
metadata
|
|
1206
|
+
};
|
|
1207
|
+
} catch (error) {
|
|
1208
|
+
throw new MastraError(
|
|
1209
|
+
{
|
|
1210
|
+
id: createStorageErrorId("LANCE", "GET_RESOURCE_BY_ID", "FAILED"),
|
|
1211
|
+
domain: ErrorDomain.STORAGE,
|
|
1212
|
+
category: ErrorCategory.THIRD_PARTY
|
|
1213
|
+
},
|
|
1214
|
+
error
|
|
1215
|
+
);
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
async saveResource({ resource }) {
|
|
1219
|
+
try {
|
|
1220
|
+
const record = {
|
|
1221
|
+
...resource,
|
|
1222
|
+
metadata: resource.metadata ? JSON.stringify(resource.metadata) : "",
|
|
1223
|
+
createdAt: resource.createdAt.getTime(),
|
|
1224
|
+
// Store as timestamp (milliseconds)
|
|
1225
|
+
updatedAt: resource.updatedAt.getTime()
|
|
1226
|
+
// Store as timestamp (milliseconds)
|
|
1227
|
+
};
|
|
1228
|
+
const table = await this.client.openTable(TABLE_RESOURCES);
|
|
1229
|
+
await table.add([record], { mode: "append" });
|
|
1230
|
+
return resource;
|
|
1219
1231
|
} catch (error) {
|
|
1220
|
-
if (error instanceof MastraError) throw error;
|
|
1221
1232
|
throw new MastraError(
|
|
1222
1233
|
{
|
|
1223
|
-
id: createStorageErrorId("LANCE", "
|
|
1234
|
+
id: createStorageErrorId("LANCE", "SAVE_RESOURCE", "FAILED"),
|
|
1224
1235
|
domain: ErrorDomain.STORAGE,
|
|
1225
|
-
category: ErrorCategory.THIRD_PARTY
|
|
1226
|
-
details: { tableName, keyCount: Object.keys(keys).length, firstKey: Object.keys(keys)[0] ?? "" }
|
|
1236
|
+
category: ErrorCategory.THIRD_PARTY
|
|
1227
1237
|
},
|
|
1228
1238
|
error
|
|
1229
1239
|
);
|
|
1230
1240
|
}
|
|
1231
1241
|
}
|
|
1242
|
+
async updateResource({
|
|
1243
|
+
resourceId,
|
|
1244
|
+
workingMemory,
|
|
1245
|
+
metadata
|
|
1246
|
+
}) {
|
|
1247
|
+
const maxRetries = 3;
|
|
1248
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
1249
|
+
try {
|
|
1250
|
+
const existingResource = await this.getResourceById({ resourceId });
|
|
1251
|
+
if (!existingResource) {
|
|
1252
|
+
const newResource = {
|
|
1253
|
+
id: resourceId,
|
|
1254
|
+
workingMemory,
|
|
1255
|
+
metadata: metadata || {},
|
|
1256
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
1257
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1258
|
+
};
|
|
1259
|
+
return this.saveResource({ resource: newResource });
|
|
1260
|
+
}
|
|
1261
|
+
const updatedResource = {
|
|
1262
|
+
...existingResource,
|
|
1263
|
+
workingMemory: workingMemory !== void 0 ? workingMemory : existingResource.workingMemory,
|
|
1264
|
+
metadata: {
|
|
1265
|
+
...existingResource.metadata,
|
|
1266
|
+
...metadata
|
|
1267
|
+
},
|
|
1268
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1269
|
+
};
|
|
1270
|
+
const record = {
|
|
1271
|
+
id: resourceId,
|
|
1272
|
+
workingMemory: updatedResource.workingMemory || "",
|
|
1273
|
+
metadata: updatedResource.metadata ? JSON.stringify(updatedResource.metadata) : "",
|
|
1274
|
+
updatedAt: updatedResource.updatedAt.getTime()
|
|
1275
|
+
// Store as timestamp (milliseconds)
|
|
1276
|
+
};
|
|
1277
|
+
const table = await this.client.openTable(TABLE_RESOURCES);
|
|
1278
|
+
await table.mergeInsert("id").whenMatchedUpdateAll().whenNotMatchedInsertAll().execute([record]);
|
|
1279
|
+
return updatedResource;
|
|
1280
|
+
} catch (error) {
|
|
1281
|
+
if (error.message?.includes("Commit conflict") && attempt < maxRetries - 1) {
|
|
1282
|
+
const delay = Math.pow(2, attempt) * 10;
|
|
1283
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
1284
|
+
continue;
|
|
1285
|
+
}
|
|
1286
|
+
throw new MastraError(
|
|
1287
|
+
{
|
|
1288
|
+
id: createStorageErrorId("LANCE", "UPDATE_RESOURCE", "FAILED"),
|
|
1289
|
+
domain: ErrorDomain.STORAGE,
|
|
1290
|
+
category: ErrorCategory.THIRD_PARTY
|
|
1291
|
+
},
|
|
1292
|
+
error
|
|
1293
|
+
);
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
throw new Error("Unexpected end of retry loop");
|
|
1297
|
+
}
|
|
1232
1298
|
};
|
|
1233
1299
|
var StoreScoresLance = class extends ScoresStorage {
|
|
1234
1300
|
client;
|
|
1235
|
-
|
|
1301
|
+
#db;
|
|
1302
|
+
constructor(config) {
|
|
1236
1303
|
super();
|
|
1304
|
+
const client = resolveLanceConfig(config);
|
|
1237
1305
|
this.client = client;
|
|
1306
|
+
this.#db = new LanceDB({ client });
|
|
1307
|
+
}
|
|
1308
|
+
async init() {
|
|
1309
|
+
await this.#db.createTable({ tableName: TABLE_SCORERS, schema: SCORERS_SCHEMA });
|
|
1310
|
+
await this.#db.alterTable({
|
|
1311
|
+
tableName: TABLE_SCORERS,
|
|
1312
|
+
schema: SCORERS_SCHEMA,
|
|
1313
|
+
ifNotExists: ["spanId", "requestContext"]
|
|
1314
|
+
});
|
|
1315
|
+
}
|
|
1316
|
+
async dangerouslyClearAll() {
|
|
1317
|
+
await this.#db.clearTable({ tableName: TABLE_SCORERS });
|
|
1238
1318
|
}
|
|
1239
1319
|
async saveScore(score) {
|
|
1240
1320
|
let validatedScore;
|
|
@@ -1248,7 +1328,7 @@ var StoreScoresLance = class extends ScoresStorage {
|
|
|
1248
1328
|
domain: ErrorDomain.STORAGE,
|
|
1249
1329
|
category: ErrorCategory.USER,
|
|
1250
1330
|
details: {
|
|
1251
|
-
scorer: score.scorer?.id ?? "unknown",
|
|
1331
|
+
scorer: typeof score.scorer?.id === "string" ? score.scorer.id : String(score.scorer?.id ?? "unknown"),
|
|
1252
1332
|
entityId: score.entityId ?? "unknown",
|
|
1253
1333
|
entityType: score.entityType ?? "unknown",
|
|
1254
1334
|
traceId: score.traceId ?? "",
|
|
@@ -1518,6 +1598,9 @@ var StoreScoresLance = class extends ScoresStorage {
|
|
|
1518
1598
|
}
|
|
1519
1599
|
}
|
|
1520
1600
|
};
|
|
1601
|
+
function escapeSql(str) {
|
|
1602
|
+
return str.replace(/'/g, "''");
|
|
1603
|
+
}
|
|
1521
1604
|
function parseWorkflowRun(row) {
|
|
1522
1605
|
let parsedSnapshot = row.snapshot;
|
|
1523
1606
|
if (typeof parsedSnapshot === "string") {
|
|
@@ -1538,42 +1621,88 @@ function parseWorkflowRun(row) {
|
|
|
1538
1621
|
}
|
|
1539
1622
|
var StoreWorkflowsLance = class extends WorkflowsStorage {
|
|
1540
1623
|
client;
|
|
1541
|
-
|
|
1624
|
+
#db;
|
|
1625
|
+
constructor(config) {
|
|
1542
1626
|
super();
|
|
1627
|
+
const client = resolveLanceConfig(config);
|
|
1543
1628
|
this.client = client;
|
|
1629
|
+
this.#db = new LanceDB({ client });
|
|
1630
|
+
}
|
|
1631
|
+
async init() {
|
|
1632
|
+
const schema = TABLE_SCHEMAS[TABLE_WORKFLOW_SNAPSHOT];
|
|
1633
|
+
await this.#db.createTable({ tableName: TABLE_WORKFLOW_SNAPSHOT, schema });
|
|
1634
|
+
await this.#db.alterTable({
|
|
1635
|
+
tableName: TABLE_WORKFLOW_SNAPSHOT,
|
|
1636
|
+
schema,
|
|
1637
|
+
ifNotExists: ["resourceId"]
|
|
1638
|
+
});
|
|
1639
|
+
}
|
|
1640
|
+
async dangerouslyClearAll() {
|
|
1641
|
+
await this.#db.clearTable({ tableName: TABLE_WORKFLOW_SNAPSHOT });
|
|
1544
1642
|
}
|
|
1545
|
-
updateWorkflowResults({
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1643
|
+
async updateWorkflowResults({
|
|
1644
|
+
workflowName,
|
|
1645
|
+
runId,
|
|
1646
|
+
stepId,
|
|
1647
|
+
result,
|
|
1648
|
+
requestContext
|
|
1551
1649
|
}) {
|
|
1552
|
-
|
|
1650
|
+
let snapshot = await this.loadWorkflowSnapshot({ workflowName, runId });
|
|
1651
|
+
if (!snapshot) {
|
|
1652
|
+
snapshot = {
|
|
1653
|
+
context: {},
|
|
1654
|
+
activePaths: [],
|
|
1655
|
+
timestamp: Date.now(),
|
|
1656
|
+
suspendedPaths: {},
|
|
1657
|
+
activeStepsPath: {},
|
|
1658
|
+
resumeLabels: {},
|
|
1659
|
+
serializedStepGraph: [],
|
|
1660
|
+
status: "pending",
|
|
1661
|
+
value: {},
|
|
1662
|
+
waitingPaths: {},
|
|
1663
|
+
runId,
|
|
1664
|
+
requestContext: {}
|
|
1665
|
+
};
|
|
1666
|
+
}
|
|
1667
|
+
snapshot.context[stepId] = result;
|
|
1668
|
+
snapshot.requestContext = { ...snapshot.requestContext, ...requestContext };
|
|
1669
|
+
await this.persistWorkflowSnapshot({ workflowName, runId, snapshot });
|
|
1670
|
+
return snapshot.context;
|
|
1553
1671
|
}
|
|
1554
|
-
updateWorkflowState({
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1672
|
+
async updateWorkflowState({
|
|
1673
|
+
workflowName,
|
|
1674
|
+
runId,
|
|
1675
|
+
opts
|
|
1558
1676
|
}) {
|
|
1559
|
-
|
|
1677
|
+
const snapshot = await this.loadWorkflowSnapshot({ workflowName, runId });
|
|
1678
|
+
if (!snapshot) {
|
|
1679
|
+
return void 0;
|
|
1680
|
+
}
|
|
1681
|
+
if (!snapshot.context) {
|
|
1682
|
+
throw new Error(`Snapshot not found for runId ${runId}`);
|
|
1683
|
+
}
|
|
1684
|
+
const updatedSnapshot = { ...snapshot, ...opts };
|
|
1685
|
+
await this.persistWorkflowSnapshot({ workflowName, runId, snapshot: updatedSnapshot });
|
|
1686
|
+
return updatedSnapshot;
|
|
1560
1687
|
}
|
|
1561
1688
|
async persistWorkflowSnapshot({
|
|
1562
1689
|
workflowName,
|
|
1563
1690
|
runId,
|
|
1564
1691
|
resourceId,
|
|
1565
|
-
snapshot
|
|
1692
|
+
snapshot,
|
|
1693
|
+
createdAt,
|
|
1694
|
+
updatedAt
|
|
1566
1695
|
}) {
|
|
1567
1696
|
try {
|
|
1568
1697
|
const table = await this.client.openTable(TABLE_WORKFLOW_SNAPSHOT);
|
|
1569
|
-
const query = table.query().where(`workflow_name = '${workflowName}' AND run_id = '${runId}'`);
|
|
1698
|
+
const query = table.query().where(`workflow_name = '${escapeSql(workflowName)}' AND run_id = '${escapeSql(runId)}'`);
|
|
1570
1699
|
const records = await query.toArray();
|
|
1571
|
-
let
|
|
1572
|
-
const now = Date.now();
|
|
1700
|
+
let createdAtValue;
|
|
1701
|
+
const now = createdAt?.getTime() ?? Date.now();
|
|
1573
1702
|
if (records.length > 0) {
|
|
1574
|
-
|
|
1703
|
+
createdAtValue = records[0].createdAt ?? now;
|
|
1575
1704
|
} else {
|
|
1576
|
-
|
|
1705
|
+
createdAtValue = now;
|
|
1577
1706
|
}
|
|
1578
1707
|
const { status, value, ...rest } = snapshot;
|
|
1579
1708
|
const record = {
|
|
@@ -1582,8 +1711,8 @@ var StoreWorkflowsLance = class extends WorkflowsStorage {
|
|
|
1582
1711
|
resourceId,
|
|
1583
1712
|
snapshot: JSON.stringify({ status, value, ...rest }),
|
|
1584
1713
|
// this is to ensure status is always just before value, for when querying the db by status
|
|
1585
|
-
createdAt,
|
|
1586
|
-
updatedAt: now
|
|
1714
|
+
createdAt: createdAtValue,
|
|
1715
|
+
updatedAt: updatedAt ?? now
|
|
1587
1716
|
};
|
|
1588
1717
|
await table.mergeInsert(["workflow_name", "run_id"]).whenMatchedUpdateAll().whenNotMatchedInsertAll().execute([record]);
|
|
1589
1718
|
} catch (error) {
|
|
@@ -1604,7 +1733,7 @@ var StoreWorkflowsLance = class extends WorkflowsStorage {
|
|
|
1604
1733
|
}) {
|
|
1605
1734
|
try {
|
|
1606
1735
|
const table = await this.client.openTable(TABLE_WORKFLOW_SNAPSHOT);
|
|
1607
|
-
const query = table.query().where(`workflow_name = '${workflowName}' AND run_id = '${runId}'`);
|
|
1736
|
+
const query = table.query().where(`workflow_name = '${escapeSql(workflowName)}' AND run_id = '${escapeSql(runId)}'`);
|
|
1608
1737
|
const records = await query.toArray();
|
|
1609
1738
|
return records.length > 0 ? JSON.parse(records[0].snapshot) : null;
|
|
1610
1739
|
} catch (error) {
|
|
@@ -1622,9 +1751,9 @@ var StoreWorkflowsLance = class extends WorkflowsStorage {
|
|
|
1622
1751
|
async getWorkflowRunById(args) {
|
|
1623
1752
|
try {
|
|
1624
1753
|
const table = await this.client.openTable(TABLE_WORKFLOW_SNAPSHOT);
|
|
1625
|
-
let whereClause = `run_id = '${args.runId}'`;
|
|
1754
|
+
let whereClause = `run_id = '${escapeSql(args.runId)}'`;
|
|
1626
1755
|
if (args.workflowName) {
|
|
1627
|
-
whereClause += ` AND workflow_name = '${args.workflowName}'`;
|
|
1756
|
+
whereClause += ` AND workflow_name = '${escapeSql(args.workflowName)}'`;
|
|
1628
1757
|
}
|
|
1629
1758
|
const query = table.query().where(whereClause);
|
|
1630
1759
|
const records = await query.toArray();
|
|
@@ -1646,7 +1775,7 @@ var StoreWorkflowsLance = class extends WorkflowsStorage {
|
|
|
1646
1775
|
async deleteWorkflowRunById({ runId, workflowName }) {
|
|
1647
1776
|
try {
|
|
1648
1777
|
const table = await this.client.openTable(TABLE_WORKFLOW_SNAPSHOT);
|
|
1649
|
-
const whereClause = `run_id = '${runId
|
|
1778
|
+
const whereClause = `run_id = '${escapeSql(runId)}' AND workflow_name = '${escapeSql(workflowName)}'`;
|
|
1650
1779
|
await table.delete(whereClause);
|
|
1651
1780
|
} catch (error) {
|
|
1652
1781
|
throw new MastraError(
|
|
@@ -1666,14 +1795,14 @@ var StoreWorkflowsLance = class extends WorkflowsStorage {
|
|
|
1666
1795
|
let query = table.query();
|
|
1667
1796
|
const conditions = [];
|
|
1668
1797
|
if (args?.workflowName) {
|
|
1669
|
-
conditions.push(`workflow_name = '${args.workflowName
|
|
1798
|
+
conditions.push(`workflow_name = '${escapeSql(args.workflowName)}'`);
|
|
1670
1799
|
}
|
|
1671
1800
|
if (args?.status) {
|
|
1672
1801
|
const escapedStatus = args.status.replace(/\\/g, "\\\\").replace(/'/g, "''").replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
1673
1802
|
conditions.push(`\`snapshot\` LIKE '%"status":"${escapedStatus}","value"%'`);
|
|
1674
1803
|
}
|
|
1675
1804
|
if (args?.resourceId) {
|
|
1676
|
-
conditions.push(`\`resourceId\` = '${args.resourceId}'`);
|
|
1805
|
+
conditions.push(`\`resourceId\` = '${escapeSql(args.resourceId)}'`);
|
|
1677
1806
|
}
|
|
1678
1807
|
if (args?.fromDate instanceof Date) {
|
|
1679
1808
|
conditions.push(`\`createdAt\` >= ${args.fromDate.getTime()}`);
|
|
@@ -1762,12 +1891,10 @@ var LanceStorage = class _LanceStorage extends MastraStorage {
|
|
|
1762
1891
|
const instance = new _LanceStorage(id, name, storageOptions?.disableInit);
|
|
1763
1892
|
try {
|
|
1764
1893
|
instance.lanceClient = await connect(uri, connectionOptions);
|
|
1765
|
-
const operations = new StoreOperationsLance({ client: instance.lanceClient });
|
|
1766
1894
|
instance.stores = {
|
|
1767
|
-
operations: new StoreOperationsLance({ client: instance.lanceClient }),
|
|
1768
1895
|
workflows: new StoreWorkflowsLance({ client: instance.lanceClient }),
|
|
1769
1896
|
scores: new StoreScoresLance({ client: instance.lanceClient }),
|
|
1770
|
-
memory: new StoreMemoryLance({ client: instance.lanceClient
|
|
1897
|
+
memory: new StoreMemoryLance({ client: instance.lanceClient })
|
|
1771
1898
|
};
|
|
1772
1899
|
return instance;
|
|
1773
1900
|
} catch (e) {
|
|
@@ -1784,67 +1911,43 @@ var LanceStorage = class _LanceStorage extends MastraStorage {
|
|
|
1784
1911
|
}
|
|
1785
1912
|
}
|
|
1786
1913
|
/**
|
|
1787
|
-
*
|
|
1788
|
-
*
|
|
1914
|
+
* Creates a new instance of LanceStorage from a pre-configured LanceDB connection.
|
|
1915
|
+
* Use this when you need to configure the connection before initialization.
|
|
1916
|
+
*
|
|
1917
|
+
* @param id The unique identifier for this storage instance
|
|
1918
|
+
* @param name The name for this storage instance
|
|
1919
|
+
* @param client Pre-configured LanceDB connection
|
|
1920
|
+
* @param options Storage options including disableInit
|
|
1921
|
+
*
|
|
1922
|
+
* @example
|
|
1923
|
+
* ```typescript
|
|
1924
|
+
* import { connect } from '@lancedb/lancedb';
|
|
1925
|
+
*
|
|
1926
|
+
* const client = await connect('/path/to/db', {
|
|
1927
|
+
* // Custom connection options
|
|
1928
|
+
* });
|
|
1929
|
+
*
|
|
1930
|
+
* const store = LanceStorage.fromClient('my-id', 'MyStorage', client);
|
|
1931
|
+
* ```
|
|
1789
1932
|
*/
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
memory: new StoreMemoryLance({ client: this.lanceClient, operations })
|
|
1933
|
+
static fromClient(id, name, client, options) {
|
|
1934
|
+
const instance = new _LanceStorage(id, name, options?.disableInit);
|
|
1935
|
+
instance.lanceClient = client;
|
|
1936
|
+
instance.stores = {
|
|
1937
|
+
workflows: new StoreWorkflowsLance({ client }),
|
|
1938
|
+
scores: new StoreScoresLance({ client }),
|
|
1939
|
+
memory: new StoreMemoryLance({ client })
|
|
1798
1940
|
};
|
|
1799
|
-
|
|
1800
|
-
async createTable({
|
|
1801
|
-
tableName,
|
|
1802
|
-
schema
|
|
1803
|
-
}) {
|
|
1804
|
-
return this.stores.operations.createTable({ tableName, schema });
|
|
1805
|
-
}
|
|
1806
|
-
async dropTable({ tableName }) {
|
|
1807
|
-
return this.stores.operations.dropTable({ tableName });
|
|
1808
|
-
}
|
|
1809
|
-
async alterTable({
|
|
1810
|
-
tableName,
|
|
1811
|
-
schema,
|
|
1812
|
-
ifNotExists
|
|
1813
|
-
}) {
|
|
1814
|
-
return this.stores.operations.alterTable({ tableName, schema, ifNotExists });
|
|
1815
|
-
}
|
|
1816
|
-
async clearTable({ tableName }) {
|
|
1817
|
-
return this.stores.operations.clearTable({ tableName });
|
|
1818
|
-
}
|
|
1819
|
-
async insert({ tableName, record }) {
|
|
1820
|
-
return this.stores.operations.insert({ tableName, record });
|
|
1821
|
-
}
|
|
1822
|
-
async batchInsert({ tableName, records }) {
|
|
1823
|
-
return this.stores.operations.batchInsert({ tableName, records });
|
|
1824
|
-
}
|
|
1825
|
-
async load({ tableName, keys }) {
|
|
1826
|
-
return this.stores.operations.load({ tableName, keys });
|
|
1827
|
-
}
|
|
1828
|
-
async getThreadById({ threadId }) {
|
|
1829
|
-
return this.stores.memory.getThreadById({ threadId });
|
|
1941
|
+
return instance;
|
|
1830
1942
|
}
|
|
1831
1943
|
/**
|
|
1832
|
-
*
|
|
1833
|
-
*
|
|
1834
|
-
*
|
|
1944
|
+
* @internal
|
|
1945
|
+
* Private constructor to enforce using the create factory method.
|
|
1946
|
+
* Note: stores is initialized in create() after the lanceClient is connected.
|
|
1835
1947
|
*/
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
async updateThread({
|
|
1840
|
-
id,
|
|
1841
|
-
title,
|
|
1842
|
-
metadata
|
|
1843
|
-
}) {
|
|
1844
|
-
return this.stores.memory.updateThread({ id, title, metadata });
|
|
1845
|
-
}
|
|
1846
|
-
async deleteThread({ threadId }) {
|
|
1847
|
-
return this.stores.memory.deleteThread({ threadId });
|
|
1948
|
+
constructor(id, name, disableInit) {
|
|
1949
|
+
super({ id, name, disableInit });
|
|
1950
|
+
this.stores = {};
|
|
1848
1951
|
}
|
|
1849
1952
|
get supports() {
|
|
1850
1953
|
return {
|
|
@@ -1852,154 +1955,13 @@ var LanceStorage = class _LanceStorage extends MastraStorage {
|
|
|
1852
1955
|
resourceWorkingMemory: true,
|
|
1853
1956
|
hasColumn: true,
|
|
1854
1957
|
createTable: true,
|
|
1855
|
-
deleteMessages:
|
|
1856
|
-
|
|
1958
|
+
deleteMessages: true,
|
|
1959
|
+
observability: false,
|
|
1960
|
+
indexManagement: false,
|
|
1961
|
+
listScoresBySpan: true,
|
|
1962
|
+
agents: false
|
|
1857
1963
|
};
|
|
1858
1964
|
}
|
|
1859
|
-
async getResourceById({ resourceId }) {
|
|
1860
|
-
return this.stores.memory.getResourceById({ resourceId });
|
|
1861
|
-
}
|
|
1862
|
-
async saveResource({ resource }) {
|
|
1863
|
-
return this.stores.memory.saveResource({ resource });
|
|
1864
|
-
}
|
|
1865
|
-
async updateResource({
|
|
1866
|
-
resourceId,
|
|
1867
|
-
workingMemory,
|
|
1868
|
-
metadata
|
|
1869
|
-
}) {
|
|
1870
|
-
return this.stores.memory.updateResource({ resourceId, workingMemory, metadata });
|
|
1871
|
-
}
|
|
1872
|
-
/**
|
|
1873
|
-
* Processes messages to include context messages based on withPreviousMessages and withNextMessages
|
|
1874
|
-
* @param records - The sorted array of records to process
|
|
1875
|
-
* @param include - The array of include specifications with context parameters
|
|
1876
|
-
* @returns The processed array with context messages included
|
|
1877
|
-
*/
|
|
1878
|
-
processMessagesWithContext(records, include) {
|
|
1879
|
-
const messagesWithContext = include.filter((item) => item.withPreviousMessages || item.withNextMessages);
|
|
1880
|
-
if (messagesWithContext.length === 0) {
|
|
1881
|
-
return records;
|
|
1882
|
-
}
|
|
1883
|
-
const messageIndexMap = /* @__PURE__ */ new Map();
|
|
1884
|
-
records.forEach((message, index) => {
|
|
1885
|
-
messageIndexMap.set(message.id, index);
|
|
1886
|
-
});
|
|
1887
|
-
const additionalIndices = /* @__PURE__ */ new Set();
|
|
1888
|
-
for (const item of messagesWithContext) {
|
|
1889
|
-
const messageIndex = messageIndexMap.get(item.id);
|
|
1890
|
-
if (messageIndex !== void 0) {
|
|
1891
|
-
if (item.withPreviousMessages) {
|
|
1892
|
-
const startIdx = Math.max(0, messageIndex - item.withPreviousMessages);
|
|
1893
|
-
for (let i = startIdx; i < messageIndex; i++) {
|
|
1894
|
-
additionalIndices.add(i);
|
|
1895
|
-
}
|
|
1896
|
-
}
|
|
1897
|
-
if (item.withNextMessages) {
|
|
1898
|
-
const endIdx = Math.min(records.length - 1, messageIndex + item.withNextMessages);
|
|
1899
|
-
for (let i = messageIndex + 1; i <= endIdx; i++) {
|
|
1900
|
-
additionalIndices.add(i);
|
|
1901
|
-
}
|
|
1902
|
-
}
|
|
1903
|
-
}
|
|
1904
|
-
}
|
|
1905
|
-
if (additionalIndices.size === 0) {
|
|
1906
|
-
return records;
|
|
1907
|
-
}
|
|
1908
|
-
const originalMatchIds = new Set(include.map((item) => item.id));
|
|
1909
|
-
const allIndices = /* @__PURE__ */ new Set();
|
|
1910
|
-
records.forEach((record, index) => {
|
|
1911
|
-
if (originalMatchIds.has(record.id)) {
|
|
1912
|
-
allIndices.add(index);
|
|
1913
|
-
}
|
|
1914
|
-
});
|
|
1915
|
-
additionalIndices.forEach((index) => {
|
|
1916
|
-
allIndices.add(index);
|
|
1917
|
-
});
|
|
1918
|
-
return Array.from(allIndices).sort((a, b) => a - b).map((index) => records[index]);
|
|
1919
|
-
}
|
|
1920
|
-
async listMessagesById({ messageIds }) {
|
|
1921
|
-
return this.stores.memory.listMessagesById({ messageIds });
|
|
1922
|
-
}
|
|
1923
|
-
async saveMessages(args) {
|
|
1924
|
-
return this.stores.memory.saveMessages(args);
|
|
1925
|
-
}
|
|
1926
|
-
async updateMessages(_args) {
|
|
1927
|
-
return this.stores.memory.updateMessages(_args);
|
|
1928
|
-
}
|
|
1929
|
-
async listWorkflowRuns(args) {
|
|
1930
|
-
return this.stores.workflows.listWorkflowRuns(args);
|
|
1931
|
-
}
|
|
1932
|
-
async getWorkflowRunById(args) {
|
|
1933
|
-
return this.stores.workflows.getWorkflowRunById(args);
|
|
1934
|
-
}
|
|
1935
|
-
async deleteWorkflowRunById({ runId, workflowName }) {
|
|
1936
|
-
return this.stores.workflows.deleteWorkflowRunById({ runId, workflowName });
|
|
1937
|
-
}
|
|
1938
|
-
async updateWorkflowResults({
|
|
1939
|
-
workflowName,
|
|
1940
|
-
runId,
|
|
1941
|
-
stepId,
|
|
1942
|
-
result,
|
|
1943
|
-
requestContext
|
|
1944
|
-
}) {
|
|
1945
|
-
return this.stores.workflows.updateWorkflowResults({ workflowName, runId, stepId, result, requestContext });
|
|
1946
|
-
}
|
|
1947
|
-
async updateWorkflowState({
|
|
1948
|
-
workflowName,
|
|
1949
|
-
runId,
|
|
1950
|
-
opts
|
|
1951
|
-
}) {
|
|
1952
|
-
return this.stores.workflows.updateWorkflowState({ workflowName, runId, opts });
|
|
1953
|
-
}
|
|
1954
|
-
async persistWorkflowSnapshot({
|
|
1955
|
-
workflowName,
|
|
1956
|
-
runId,
|
|
1957
|
-
resourceId,
|
|
1958
|
-
snapshot
|
|
1959
|
-
}) {
|
|
1960
|
-
return this.stores.workflows.persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot });
|
|
1961
|
-
}
|
|
1962
|
-
async loadWorkflowSnapshot({
|
|
1963
|
-
workflowName,
|
|
1964
|
-
runId
|
|
1965
|
-
}) {
|
|
1966
|
-
return this.stores.workflows.loadWorkflowSnapshot({ workflowName, runId });
|
|
1967
|
-
}
|
|
1968
|
-
async getScoreById({ id: _id }) {
|
|
1969
|
-
return this.stores.scores.getScoreById({ id: _id });
|
|
1970
|
-
}
|
|
1971
|
-
async listScoresByScorerId({
|
|
1972
|
-
scorerId,
|
|
1973
|
-
source,
|
|
1974
|
-
entityId,
|
|
1975
|
-
entityType,
|
|
1976
|
-
pagination
|
|
1977
|
-
}) {
|
|
1978
|
-
return this.stores.scores.listScoresByScorerId({ scorerId, source, pagination, entityId, entityType });
|
|
1979
|
-
}
|
|
1980
|
-
async saveScore(score) {
|
|
1981
|
-
return this.stores.scores.saveScore(score);
|
|
1982
|
-
}
|
|
1983
|
-
async listScoresByRunId({
|
|
1984
|
-
runId,
|
|
1985
|
-
pagination
|
|
1986
|
-
}) {
|
|
1987
|
-
return this.stores.scores.listScoresByRunId({ runId, pagination });
|
|
1988
|
-
}
|
|
1989
|
-
async listScoresByEntityId({
|
|
1990
|
-
entityId,
|
|
1991
|
-
entityType,
|
|
1992
|
-
pagination
|
|
1993
|
-
}) {
|
|
1994
|
-
return this.stores.scores.listScoresByEntityId({ entityId, entityType, pagination });
|
|
1995
|
-
}
|
|
1996
|
-
async listScoresBySpan({
|
|
1997
|
-
traceId,
|
|
1998
|
-
spanId,
|
|
1999
|
-
pagination
|
|
2000
|
-
}) {
|
|
2001
|
-
return this.stores.scores.listScoresBySpan({ traceId, spanId, pagination });
|
|
2002
|
-
}
|
|
2003
1965
|
};
|
|
2004
1966
|
var LanceFilterTranslator = class extends BaseFilterTranslator {
|
|
2005
1967
|
translate(filter) {
|