@mastra/libsql 0.0.0-add-runtime-context-to-openai-realtime-20250516201052

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1443 @@
1
+ import { createClient } from '@libsql/client';
2
+ import { parseSqlIdentifier, parseFieldKey } from '@mastra/core/utils';
3
+ import { MastraVector } from '@mastra/core/vector';
4
+ import { BaseFilterTranslator } from '@mastra/core/vector/filter';
5
+ import { MastraStorage, TABLE_WORKFLOW_SNAPSHOT, TABLE_THREADS, TABLE_MESSAGES, TABLE_EVALS, TABLE_TRACES } from '@mastra/core/storage';
6
+
7
+ // src/vector/index.ts
8
+ var LibSQLFilterTranslator = class extends BaseFilterTranslator {
9
+ getSupportedOperators() {
10
+ return {
11
+ ...BaseFilterTranslator.DEFAULT_OPERATORS,
12
+ regex: [],
13
+ custom: ["$contains", "$size"]
14
+ };
15
+ }
16
+ translate(filter) {
17
+ if (this.isEmpty(filter)) {
18
+ return filter;
19
+ }
20
+ this.validateFilter(filter);
21
+ return this.translateNode(filter);
22
+ }
23
+ translateNode(node, currentPath = "") {
24
+ if (this.isRegex(node)) {
25
+ throw new Error("Direct regex pattern format is not supported in LibSQL");
26
+ }
27
+ const withPath = (result2) => currentPath ? { [currentPath]: result2 } : result2;
28
+ if (this.isPrimitive(node)) {
29
+ return withPath({ $eq: this.normalizeComparisonValue(node) });
30
+ }
31
+ if (Array.isArray(node)) {
32
+ return withPath({ $in: this.normalizeArrayValues(node) });
33
+ }
34
+ const entries = Object.entries(node);
35
+ const result = {};
36
+ for (const [key, value] of entries) {
37
+ const newPath = currentPath ? `${currentPath}.${key}` : key;
38
+ if (this.isLogicalOperator(key)) {
39
+ result[key] = Array.isArray(value) ? value.map((filter) => this.translateNode(filter)) : this.translateNode(value);
40
+ } else if (this.isOperator(key)) {
41
+ if (this.isArrayOperator(key) && !Array.isArray(value) && key !== "$elemMatch") {
42
+ result[key] = [value];
43
+ } else if (this.isBasicOperator(key) && Array.isArray(value)) {
44
+ result[key] = JSON.stringify(value);
45
+ } else {
46
+ result[key] = value;
47
+ }
48
+ } else if (typeof value === "object" && value !== null) {
49
+ const hasOperators = Object.keys(value).some((k) => this.isOperator(k));
50
+ if (hasOperators) {
51
+ result[newPath] = this.translateNode(value);
52
+ } else {
53
+ Object.assign(result, this.translateNode(value, newPath));
54
+ }
55
+ } else {
56
+ result[newPath] = this.translateNode(value);
57
+ }
58
+ }
59
+ return result;
60
+ }
61
+ // TODO: Look more into regex support for LibSQL
62
+ // private translateRegexPattern(pattern: string, options: string = ''): any {
63
+ // if (!options) return { $regex: pattern };
64
+ // const flags = options
65
+ // .split('')
66
+ // .filter(f => 'imsux'.includes(f))
67
+ // .join('');
68
+ // return {
69
+ // $regex: pattern,
70
+ // $options: flags,
71
+ // };
72
+ // }
73
+ };
74
+ var createBasicOperator = (symbol) => {
75
+ return (key, value) => {
76
+ const jsonPathKey = parseJsonPathKey(key);
77
+ return {
78
+ sql: `CASE
79
+ WHEN ? IS NULL THEN json_extract(metadata, '$."${jsonPathKey}"') IS ${symbol === "=" ? "" : "NOT"} NULL
80
+ ELSE json_extract(metadata, '$."${jsonPathKey}"') ${symbol} ?
81
+ END`,
82
+ needsValue: true,
83
+ transformValue: () => {
84
+ return [value, value];
85
+ }
86
+ };
87
+ };
88
+ };
89
+ var createNumericOperator = (symbol) => {
90
+ return (key) => {
91
+ const jsonPathKey = parseJsonPathKey(key);
92
+ return {
93
+ sql: `CAST(json_extract(metadata, '$."${jsonPathKey}"') AS NUMERIC) ${symbol} ?`,
94
+ needsValue: true
95
+ };
96
+ };
97
+ };
98
+ var validateJsonArray = (key) => `json_valid(json_extract(metadata, '$."${key}"'))
99
+ AND json_type(json_extract(metadata, '$."${key}"')) = 'array'`;
100
+ var pattern = /json_extract\(metadata, '\$\."[^"]*"(\."[^"]*")*'\)/g;
101
+ function buildElemMatchConditions(value) {
102
+ const conditions = Object.entries(value).map(([field, fieldValue]) => {
103
+ if (field.startsWith("$")) {
104
+ const { sql, values } = buildCondition("elem.value", { [field]: fieldValue });
105
+ const elemSql = sql.replace(pattern, "elem.value");
106
+ return { sql: elemSql, values };
107
+ } else if (typeof fieldValue === "object" && !Array.isArray(fieldValue)) {
108
+ const { sql, values } = buildCondition(field, fieldValue);
109
+ const elemSql = sql.replace(pattern, `json_extract(elem.value, '$."${field}"')`);
110
+ return { sql: elemSql, values };
111
+ } else {
112
+ const parsedFieldKey = parseFieldKey(field);
113
+ return {
114
+ sql: `json_extract(elem.value, '$."${parsedFieldKey}"') = ?`,
115
+ values: [fieldValue]
116
+ };
117
+ }
118
+ });
119
+ return conditions;
120
+ }
121
+ var FILTER_OPERATORS = {
122
+ $eq: createBasicOperator("="),
123
+ $ne: createBasicOperator("!="),
124
+ $gt: createNumericOperator(">"),
125
+ $gte: createNumericOperator(">="),
126
+ $lt: createNumericOperator("<"),
127
+ $lte: createNumericOperator("<="),
128
+ // Array Operators
129
+ $in: (key, value) => {
130
+ const jsonPathKey = parseJsonPathKey(key);
131
+ const arr = Array.isArray(value) ? value : [value];
132
+ if (arr.length === 0) {
133
+ return { sql: "1 = 0", needsValue: true, transformValue: () => [] };
134
+ }
135
+ const paramPlaceholders = arr.map(() => "?").join(",");
136
+ return {
137
+ sql: `(
138
+ CASE
139
+ WHEN ${validateJsonArray(jsonPathKey)} THEN
140
+ EXISTS (
141
+ SELECT 1 FROM json_each(json_extract(metadata, '$."${jsonPathKey}"')) as elem
142
+ WHERE elem.value IN (SELECT value FROM json_each(?))
143
+ )
144
+ ELSE json_extract(metadata, '$."${jsonPathKey}"') IN (${paramPlaceholders})
145
+ END
146
+ )`,
147
+ needsValue: true,
148
+ transformValue: () => [JSON.stringify(arr), ...arr]
149
+ };
150
+ },
151
+ $nin: (key, value) => {
152
+ const jsonPathKey = parseJsonPathKey(key);
153
+ const arr = Array.isArray(value) ? value : [value];
154
+ if (arr.length === 0) {
155
+ return { sql: "1 = 1", needsValue: true, transformValue: () => [] };
156
+ }
157
+ const paramPlaceholders = arr.map(() => "?").join(",");
158
+ return {
159
+ sql: `(
160
+ CASE
161
+ WHEN ${validateJsonArray(jsonPathKey)} THEN
162
+ NOT EXISTS (
163
+ SELECT 1 FROM json_each(json_extract(metadata, '$."${jsonPathKey}"')) as elem
164
+ WHERE elem.value IN (SELECT value FROM json_each(?))
165
+ )
166
+ ELSE json_extract(metadata, '$."${jsonPathKey}"') NOT IN (${paramPlaceholders})
167
+ END
168
+ )`,
169
+ needsValue: true,
170
+ transformValue: () => [JSON.stringify(arr), ...arr]
171
+ };
172
+ },
173
+ $all: (key, value) => {
174
+ const jsonPathKey = parseJsonPathKey(key);
175
+ let sql;
176
+ const arrayValue = Array.isArray(value) ? value : [value];
177
+ if (arrayValue.length === 0) {
178
+ sql = "1 = 0";
179
+ } else {
180
+ sql = `(
181
+ CASE
182
+ WHEN ${validateJsonArray(jsonPathKey)} THEN
183
+ NOT EXISTS (
184
+ SELECT value
185
+ FROM json_each(?)
186
+ WHERE value NOT IN (
187
+ SELECT value
188
+ FROM json_each(json_extract(metadata, '$."${jsonPathKey}"'))
189
+ )
190
+ )
191
+ ELSE FALSE
192
+ END
193
+ )`;
194
+ }
195
+ return {
196
+ sql,
197
+ needsValue: true,
198
+ transformValue: () => {
199
+ if (arrayValue.length === 0) {
200
+ return [];
201
+ }
202
+ return [JSON.stringify(arrayValue)];
203
+ }
204
+ };
205
+ },
206
+ $elemMatch: (key, value) => {
207
+ const jsonPathKey = parseJsonPathKey(key);
208
+ if (typeof value !== "object" || Array.isArray(value)) {
209
+ throw new Error("$elemMatch requires an object with conditions");
210
+ }
211
+ const conditions = buildElemMatchConditions(value);
212
+ return {
213
+ sql: `(
214
+ CASE
215
+ WHEN ${validateJsonArray(jsonPathKey)} THEN
216
+ EXISTS (
217
+ SELECT 1
218
+ FROM json_each(json_extract(metadata, '$."${jsonPathKey}"')) as elem
219
+ WHERE ${conditions.map((c) => c.sql).join(" AND ")}
220
+ )
221
+ ELSE FALSE
222
+ END
223
+ )`,
224
+ needsValue: true,
225
+ transformValue: () => conditions.flatMap((c) => c.values)
226
+ };
227
+ },
228
+ // Element Operators
229
+ $exists: (key) => {
230
+ const jsonPathKey = parseJsonPathKey(key);
231
+ return {
232
+ sql: `json_extract(metadata, '$."${jsonPathKey}"') IS NOT NULL`,
233
+ needsValue: false
234
+ };
235
+ },
236
+ // Logical Operators
237
+ $and: (key) => ({
238
+ sql: `(${key})`,
239
+ needsValue: false
240
+ }),
241
+ $or: (key) => ({
242
+ sql: `(${key})`,
243
+ needsValue: false
244
+ }),
245
+ $not: (key) => ({ sql: `NOT (${key})`, needsValue: false }),
246
+ $nor: (key) => ({
247
+ sql: `NOT (${key})`,
248
+ needsValue: false
249
+ }),
250
+ $size: (key, paramIndex) => {
251
+ const jsonPathKey = parseJsonPathKey(key);
252
+ return {
253
+ sql: `(
254
+ CASE
255
+ WHEN json_type(json_extract(metadata, '$."${jsonPathKey}"')) = 'array' THEN
256
+ json_array_length(json_extract(metadata, '$."${jsonPathKey}"')) = $${paramIndex}
257
+ ELSE FALSE
258
+ END
259
+ )`,
260
+ needsValue: true
261
+ };
262
+ },
263
+ // /**
264
+ // * Regex Operators
265
+ // * Supports case insensitive and multiline
266
+ // */
267
+ // $regex: (key: string): FilterOperator => ({
268
+ // sql: `json_extract(metadata, '$."${toJsonPathKey(key)}"') = ?`,
269
+ // needsValue: true,
270
+ // transformValue: (value: any) => {
271
+ // const pattern = typeof value === 'object' ? value.$regex : value;
272
+ // const options = typeof value === 'object' ? value.$options || '' : '';
273
+ // let sql = `json_extract(metadata, '$."${toJsonPathKey(key)}"')`;
274
+ // // Handle multiline
275
+ // // if (options.includes('m')) {
276
+ // // sql = `REPLACE(${sql}, CHAR(10), '\n')`;
277
+ // // }
278
+ // // let finalPattern = pattern;
279
+ // // if (options) {
280
+ // // finalPattern = `(\\?${options})${pattern}`;
281
+ // // }
282
+ // // // Handle case insensitivity
283
+ // // if (options.includes('i')) {
284
+ // // sql = `LOWER(${sql}) REGEXP LOWER(?)`;
285
+ // // } else {
286
+ // // sql = `${sql} REGEXP ?`;
287
+ // // }
288
+ // if (options.includes('m')) {
289
+ // sql = `EXISTS (
290
+ // SELECT 1
291
+ // FROM json_each(
292
+ // json_array(
293
+ // ${sql},
294
+ // REPLACE(${sql}, CHAR(10), CHAR(13))
295
+ // )
296
+ // ) as lines
297
+ // WHERE lines.value REGEXP ?
298
+ // )`;
299
+ // } else {
300
+ // sql = `${sql} REGEXP ?`;
301
+ // }
302
+ // // Handle case insensitivity
303
+ // if (options.includes('i')) {
304
+ // sql = sql.replace('REGEXP ?', 'REGEXP LOWER(?)');
305
+ // sql = sql.replace('value REGEXP', 'LOWER(value) REGEXP');
306
+ // }
307
+ // // Handle extended - allows whitespace and comments in pattern
308
+ // if (options.includes('x')) {
309
+ // // Remove whitespace and comments from pattern
310
+ // const cleanPattern = pattern.replace(/\s+|#.*$/gm, '');
311
+ // return {
312
+ // sql,
313
+ // values: [cleanPattern],
314
+ // };
315
+ // }
316
+ // return {
317
+ // sql,
318
+ // values: [pattern],
319
+ // };
320
+ // },
321
+ // }),
322
+ $contains: (key, value) => {
323
+ const jsonPathKey = parseJsonPathKey(key);
324
+ let sql;
325
+ if (Array.isArray(value)) {
326
+ sql = `(
327
+ SELECT ${validateJsonArray(jsonPathKey)}
328
+ AND EXISTS (
329
+ SELECT 1
330
+ FROM json_each(json_extract(metadata, '$."${jsonPathKey}"')) as m
331
+ WHERE m.value IN (SELECT value FROM json_each(?))
332
+ )
333
+ )`;
334
+ } else if (typeof value === "string") {
335
+ sql = `lower(json_extract(metadata, '$."${jsonPathKey}"')) LIKE '%' || lower(?) || '%' ESCAPE '\\'`;
336
+ } else {
337
+ sql = `json_extract(metadata, '$."${jsonPathKey}"') = ?`;
338
+ }
339
+ return {
340
+ sql,
341
+ needsValue: true,
342
+ transformValue: () => {
343
+ if (Array.isArray(value)) {
344
+ return [JSON.stringify(value)];
345
+ }
346
+ if (typeof value === "object" && value !== null) {
347
+ return [JSON.stringify(value)];
348
+ }
349
+ if (typeof value === "string") {
350
+ return [escapeLikePattern(value)];
351
+ }
352
+ return [value];
353
+ }
354
+ };
355
+ }
356
+ /**
357
+ * $objectContains: True JSON containment for advanced use (deep sub-object match).
358
+ * Usage: { field: { $objectContains: { ...subobject } } }
359
+ */
360
+ // $objectContains: (key: string) => ({
361
+ // sql: '', // Will be overridden by transformValue
362
+ // needsValue: true,
363
+ // transformValue: (value: any) => ({
364
+ // sql: `json_type(json_extract(metadata, '$."${toJsonPathKey(key)}"')) = 'object'
365
+ // AND json_patch(json_extract(metadata, '$."${toJsonPathKey(key)}"'), ?) = json_extract(metadata, '$."${toJsonPathKey(key)}"')`,
366
+ // values: [JSON.stringify(value)],
367
+ // }),
368
+ // }),
369
+ };
370
+ function isFilterResult(obj) {
371
+ return obj && typeof obj === "object" && typeof obj.sql === "string" && Array.isArray(obj.values);
372
+ }
373
+ var parseJsonPathKey = (key) => {
374
+ const parsedKey = parseFieldKey(key);
375
+ return parsedKey.replace(/\./g, '"."');
376
+ };
377
+ function escapeLikePattern(str) {
378
+ return str.replace(/([%_\\])/g, "\\$1");
379
+ }
380
+ function buildFilterQuery(filter) {
381
+ if (!filter) {
382
+ return { sql: "", values: [] };
383
+ }
384
+ const values = [];
385
+ const conditions = Object.entries(filter).map(([key, value]) => {
386
+ const condition = buildCondition(key, value);
387
+ values.push(...condition.values);
388
+ return condition.sql;
389
+ }).join(" AND ");
390
+ return {
391
+ sql: conditions ? `WHERE ${conditions}` : "",
392
+ values
393
+ };
394
+ }
395
+ function buildCondition(key, value, parentPath) {
396
+ if (["$and", "$or", "$not", "$nor"].includes(key)) {
397
+ return handleLogicalOperator(key, value);
398
+ }
399
+ if (!value || typeof value !== "object") {
400
+ return {
401
+ sql: `json_extract(metadata, '$."${key.replace(/\./g, '"."')}"') = ?`,
402
+ values: [value]
403
+ };
404
+ }
405
+ return handleOperator(key, value);
406
+ }
407
+ function handleLogicalOperator(key, value, parentPath) {
408
+ if (!value || value.length === 0) {
409
+ switch (key) {
410
+ case "$and":
411
+ case "$nor":
412
+ return { sql: "true", values: [] };
413
+ case "$or":
414
+ return { sql: "false", values: [] };
415
+ case "$not":
416
+ throw new Error("$not operator cannot be empty");
417
+ default:
418
+ return { sql: "true", values: [] };
419
+ }
420
+ }
421
+ if (key === "$not") {
422
+ const entries = Object.entries(value);
423
+ const conditions2 = entries.map(([fieldKey, fieldValue]) => buildCondition(fieldKey, fieldValue));
424
+ return {
425
+ sql: `NOT (${conditions2.map((c) => c.sql).join(" AND ")})`,
426
+ values: conditions2.flatMap((c) => c.values)
427
+ };
428
+ }
429
+ const values = [];
430
+ const joinOperator = key === "$or" || key === "$nor" ? "OR" : "AND";
431
+ const conditions = Array.isArray(value) ? value.map((f) => {
432
+ const entries = Object.entries(f);
433
+ return entries.map(([k, v]) => buildCondition(k, v));
434
+ }) : [buildCondition(key, value)];
435
+ const joined = conditions.flat().map((c) => {
436
+ values.push(...c.values);
437
+ return c.sql;
438
+ }).join(` ${joinOperator} `);
439
+ return {
440
+ sql: key === "$nor" ? `NOT (${joined})` : `(${joined})`,
441
+ values
442
+ };
443
+ }
444
+ function handleOperator(key, value) {
445
+ if (typeof value === "object" && !Array.isArray(value)) {
446
+ const entries = Object.entries(value);
447
+ const results = entries.map(
448
+ ([operator2, operatorValue2]) => operator2 === "$not" ? {
449
+ sql: `NOT (${Object.entries(operatorValue2).map(([op, val]) => processOperator(key, op, val).sql).join(" AND ")})`,
450
+ values: Object.entries(operatorValue2).flatMap(
451
+ ([op, val]) => processOperator(key, op, val).values
452
+ )
453
+ } : processOperator(key, operator2, operatorValue2)
454
+ );
455
+ return {
456
+ sql: `(${results.map((r) => r.sql).join(" AND ")})`,
457
+ values: results.flatMap((r) => r.values)
458
+ };
459
+ }
460
+ const [[operator, operatorValue] = []] = Object.entries(value);
461
+ return processOperator(key, operator, operatorValue);
462
+ }
463
+ var processOperator = (key, operator, operatorValue) => {
464
+ if (!operator.startsWith("$") || !FILTER_OPERATORS[operator]) {
465
+ throw new Error(`Invalid operator: ${operator}`);
466
+ }
467
+ const operatorFn = FILTER_OPERATORS[operator];
468
+ const operatorResult = operatorFn(key, operatorValue);
469
+ if (!operatorResult.needsValue) {
470
+ return { sql: operatorResult.sql, values: [] };
471
+ }
472
+ const transformed = operatorResult.transformValue ? operatorResult.transformValue() : operatorValue;
473
+ if (isFilterResult(transformed)) {
474
+ return transformed;
475
+ }
476
+ return {
477
+ sql: operatorResult.sql,
478
+ values: Array.isArray(transformed) ? transformed : [transformed]
479
+ };
480
+ };
481
+
482
+ // src/vector/index.ts
483
+ var LibSQLVector = class extends MastraVector {
484
+ turso;
485
+ constructor({
486
+ connectionUrl,
487
+ authToken,
488
+ syncUrl,
489
+ syncInterval
490
+ }) {
491
+ super();
492
+ this.turso = createClient({
493
+ url: connectionUrl,
494
+ syncUrl,
495
+ authToken,
496
+ syncInterval
497
+ });
498
+ if (connectionUrl.includes(`file:`) || connectionUrl.includes(`:memory:`)) {
499
+ void this.turso.execute({
500
+ sql: "PRAGMA journal_mode=WAL;",
501
+ args: {}
502
+ });
503
+ }
504
+ }
505
+ transformFilter(filter) {
506
+ const translator = new LibSQLFilterTranslator();
507
+ return translator.translate(filter);
508
+ }
509
+ async query(...args) {
510
+ const params = this.normalizeArgs("query", args, ["minScore"]);
511
+ try {
512
+ const { indexName, queryVector, topK = 10, filter, includeVector = false, minScore = 0 } = params;
513
+ if (!Number.isInteger(topK) || topK <= 0) {
514
+ throw new Error("topK must be a positive integer");
515
+ }
516
+ if (!Array.isArray(queryVector) || !queryVector.every((x) => typeof x === "number" && Number.isFinite(x))) {
517
+ throw new Error("queryVector must be an array of finite numbers");
518
+ }
519
+ const parsedIndexName = parseSqlIdentifier(indexName, "index name");
520
+ const vectorStr = `[${queryVector.join(",")}]`;
521
+ const translatedFilter = this.transformFilter(filter);
522
+ const { sql: filterQuery, values: filterValues } = buildFilterQuery(translatedFilter);
523
+ filterValues.push(minScore);
524
+ filterValues.push(topK);
525
+ const query = `
526
+ WITH vector_scores AS (
527
+ SELECT
528
+ vector_id as id,
529
+ (1-vector_distance_cos(embedding, '${vectorStr}')) as score,
530
+ metadata
531
+ ${includeVector ? ", vector_extract(embedding) as embedding" : ""}
532
+ FROM ${parsedIndexName}
533
+ ${filterQuery}
534
+ )
535
+ SELECT *
536
+ FROM vector_scores
537
+ WHERE score > ?
538
+ ORDER BY score DESC
539
+ LIMIT ?`;
540
+ const result = await this.turso.execute({
541
+ sql: query,
542
+ args: filterValues
543
+ });
544
+ return result.rows.map(({ id, score, metadata, embedding }) => ({
545
+ id,
546
+ score,
547
+ metadata: JSON.parse(metadata ?? "{}"),
548
+ ...includeVector && embedding && { vector: JSON.parse(embedding) }
549
+ }));
550
+ } finally {
551
+ }
552
+ }
553
+ async upsert(...args) {
554
+ const params = this.normalizeArgs("upsert", args);
555
+ const { indexName, vectors, metadata, ids } = params;
556
+ const tx = await this.turso.transaction("write");
557
+ try {
558
+ const parsedIndexName = parseSqlIdentifier(indexName, "index name");
559
+ const vectorIds = ids || vectors.map(() => crypto.randomUUID());
560
+ for (let i = 0; i < vectors.length; i++) {
561
+ const query = `
562
+ INSERT INTO ${parsedIndexName} (vector_id, embedding, metadata)
563
+ VALUES (?, vector32(?), ?)
564
+ ON CONFLICT(vector_id) DO UPDATE SET
565
+ embedding = vector32(?),
566
+ metadata = ?
567
+ `;
568
+ await tx.execute({
569
+ sql: query,
570
+ // @ts-ignore
571
+ args: [
572
+ vectorIds[i],
573
+ JSON.stringify(vectors[i]),
574
+ JSON.stringify(metadata?.[i] || {}),
575
+ JSON.stringify(vectors[i]),
576
+ JSON.stringify(metadata?.[i] || {})
577
+ ]
578
+ });
579
+ }
580
+ await tx.commit();
581
+ return vectorIds;
582
+ } catch (error) {
583
+ await tx.rollback();
584
+ if (error instanceof Error && error.message?.includes("dimensions are different")) {
585
+ const match = error.message.match(/dimensions are different: (\d+) != (\d+)/);
586
+ if (match) {
587
+ const [, actual, expected] = match;
588
+ throw new Error(
589
+ `Vector dimension mismatch: Index "${indexName}" expects ${expected} dimensions but got ${actual} dimensions. Either use a matching embedding model or delete and recreate the index with the new dimension.`
590
+ );
591
+ }
592
+ }
593
+ throw error;
594
+ }
595
+ }
596
+ async createIndex(...args) {
597
+ const params = this.normalizeArgs("createIndex", args);
598
+ const { indexName, dimension } = params;
599
+ try {
600
+ if (!Number.isInteger(dimension) || dimension <= 0) {
601
+ throw new Error("Dimension must be a positive integer");
602
+ }
603
+ const parsedIndexName = parseSqlIdentifier(indexName, "index name");
604
+ await this.turso.execute({
605
+ sql: `
606
+ CREATE TABLE IF NOT EXISTS ${parsedIndexName} (
607
+ id SERIAL PRIMARY KEY,
608
+ vector_id TEXT UNIQUE NOT NULL,
609
+ embedding F32_BLOB(${dimension}),
610
+ metadata TEXT DEFAULT '{}'
611
+ );
612
+ `,
613
+ args: []
614
+ });
615
+ await this.turso.execute({
616
+ sql: `
617
+ CREATE INDEX IF NOT EXISTS ${parsedIndexName}_vector_idx
618
+ ON ${parsedIndexName} (libsql_vector_idx(embedding))
619
+ `,
620
+ args: []
621
+ });
622
+ } catch (error) {
623
+ console.error("Failed to create vector table:", error);
624
+ throw error;
625
+ } finally {
626
+ }
627
+ }
628
+ async deleteIndex(...args) {
629
+ const params = this.normalizeArgs("deleteIndex", args);
630
+ const { indexName } = params;
631
+ try {
632
+ const parsedIndexName = parseSqlIdentifier(indexName, "index name");
633
+ await this.turso.execute({
634
+ sql: `DROP TABLE IF EXISTS ${parsedIndexName}`,
635
+ args: []
636
+ });
637
+ } catch (error) {
638
+ console.error("Failed to delete vector table:", error);
639
+ throw new Error(`Failed to delete vector table: ${error.message}`);
640
+ } finally {
641
+ }
642
+ }
643
+ async listIndexes() {
644
+ try {
645
+ const vectorTablesQuery = `
646
+ SELECT name FROM sqlite_master
647
+ WHERE type='table'
648
+ AND sql LIKE '%F32_BLOB%';
649
+ `;
650
+ const result = await this.turso.execute({
651
+ sql: vectorTablesQuery,
652
+ args: []
653
+ });
654
+ return result.rows.map((row) => row.name);
655
+ } catch (error) {
656
+ throw new Error(`Failed to list vector tables: ${error.message}`);
657
+ }
658
+ }
659
+ /**
660
+ * Retrieves statistics about a vector index.
661
+ *
662
+ * @param params - The parameters for describing an index
663
+ * @param params.indexName - The name of the index to describe
664
+ * @returns A promise that resolves to the index statistics including dimension, count and metric
665
+ */
666
+ async describeIndex(...args) {
667
+ const params = this.normalizeArgs("describeIndex", args);
668
+ const { indexName } = params;
669
+ try {
670
+ const parsedIndexName = parseSqlIdentifier(indexName, "index name");
671
+ const tableInfoQuery = `
672
+ SELECT sql
673
+ FROM sqlite_master
674
+ WHERE type='table'
675
+ AND name = ?;
676
+ `;
677
+ const tableInfo = await this.turso.execute({
678
+ sql: tableInfoQuery,
679
+ args: [parsedIndexName]
680
+ });
681
+ if (!tableInfo.rows[0]?.sql) {
682
+ throw new Error(`Table ${parsedIndexName} not found`);
683
+ }
684
+ const dimension = parseInt(tableInfo.rows[0].sql.match(/F32_BLOB\((\d+)\)/)?.[1] || "0");
685
+ const countQuery = `
686
+ SELECT COUNT(*) as count
687
+ FROM ${parsedIndexName};
688
+ `;
689
+ const countResult = await this.turso.execute({
690
+ sql: countQuery,
691
+ args: []
692
+ });
693
+ const metric = "cosine";
694
+ return {
695
+ dimension,
696
+ count: countResult?.rows?.[0]?.count ?? 0,
697
+ metric
698
+ };
699
+ } catch (e) {
700
+ throw new Error(`Failed to describe vector table: ${e.message}`);
701
+ }
702
+ }
703
+ /**
704
+ * @deprecated Use {@link updateVector} instead. This method will be removed on May 20th, 2025.
705
+ *
706
+ * Updates a vector by its ID with the provided vector and/or metadata.
707
+ * @param indexName - The name of the index containing the vector.
708
+ * @param id - The ID of the vector to update.
709
+ * @param update - An object containing the vector and/or metadata to update.
710
+ * @param update.vector - An optional array of numbers representing the new vector.
711
+ * @param update.metadata - An optional record containing the new metadata.
712
+ * @returns A promise that resolves when the update is complete.
713
+ * @throws Will throw an error if no updates are provided or if the update operation fails.
714
+ */
715
+ async updateIndexById(indexName, id, update) {
716
+ this.logger.warn(
717
+ `Deprecation Warning: updateIndexById() is deprecated.
718
+ Please use updateVector() instead.
719
+ updateIndexById() will be removed on May 20th, 2025.`
720
+ );
721
+ await this.updateVector({ indexName, id, update });
722
+ }
723
+ /**
724
+ * Updates a vector by its ID with the provided vector and/or metadata.
725
+ *
726
+ * @param indexName - The name of the index containing the vector.
727
+ * @param id - The ID of the vector to update.
728
+ * @param update - An object containing the vector and/or metadata to update.
729
+ * @param update.vector - An optional array of numbers representing the new vector.
730
+ * @param update.metadata - An optional record containing the new metadata.
731
+ * @returns A promise that resolves when the update is complete.
732
+ * @throws Will throw an error if no updates are provided or if the update operation fails.
733
+ */
734
+ async updateVector(...args) {
735
+ const params = this.normalizeArgs("updateVector", args);
736
+ const { indexName, id, update } = params;
737
+ try {
738
+ const parsedIndexName = parseSqlIdentifier(indexName, "index name");
739
+ const updates = [];
740
+ const args2 = [];
741
+ if (update.vector) {
742
+ updates.push("embedding = vector32(?)");
743
+ args2.push(JSON.stringify(update.vector));
744
+ }
745
+ if (update.metadata) {
746
+ updates.push("metadata = ?");
747
+ args2.push(JSON.stringify(update.metadata));
748
+ }
749
+ if (updates.length === 0) {
750
+ throw new Error("No updates provided");
751
+ }
752
+ args2.push(id);
753
+ const query = `
754
+ UPDATE ${parsedIndexName}
755
+ SET ${updates.join(", ")}
756
+ WHERE vector_id = ?;
757
+ `;
758
+ await this.turso.execute({
759
+ sql: query,
760
+ args: args2
761
+ });
762
+ } catch (error) {
763
+ throw new Error(`Failed to update vector by id: ${id} for index: ${indexName}: ${error.message}`);
764
+ }
765
+ }
766
+ /**
767
+ * @deprecated Use {@link deleteVector} instead. This method will be removed on May 20th, 2025.
768
+ *
769
+ * Deletes a vector by its ID.
770
+ * @param indexName - The name of the index containing the vector.
771
+ * @param id - The ID of the vector to delete.
772
+ * @returns A promise that resolves when the deletion is complete.
773
+ * @throws Will throw an error if the deletion operation fails.
774
+ */
775
+ async deleteIndexById(indexName, id) {
776
+ this.logger.warn(
777
+ `Deprecation Warning: deleteIndexById() is deprecated.
778
+ Please use deleteVector() instead.
779
+ deleteIndexById() will be removed on May 20th, 2025.`
780
+ );
781
+ await this.deleteVector({ indexName, id });
782
+ }
783
+ /**
784
+ * Deletes a vector by its ID.
785
+ * @param indexName - The name of the index containing the vector.
786
+ * @param id - The ID of the vector to delete.
787
+ * @returns A promise that resolves when the deletion is complete.
788
+ * @throws Will throw an error if the deletion operation fails.
789
+ */
790
+ async deleteVector(...args) {
791
+ const params = this.normalizeArgs("deleteVector", args);
792
+ const { indexName, id } = params;
793
+ try {
794
+ const parsedIndexName = parseSqlIdentifier(indexName, "index name");
795
+ await this.turso.execute({
796
+ sql: `DELETE FROM ${parsedIndexName} WHERE vector_id = ?`,
797
+ args: [id]
798
+ });
799
+ } catch (error) {
800
+ throw new Error(`Failed to delete vector by id: ${id} for index: ${indexName}: ${error.message}`);
801
+ }
802
+ }
803
+ async truncateIndex(...args) {
804
+ const params = this.normalizeArgs("truncateIndex", args);
805
+ const { indexName } = params;
806
+ await this.turso.execute({
807
+ sql: `DELETE FROM ${parseSqlIdentifier(indexName, "index name")}`,
808
+ args: []
809
+ });
810
+ }
811
+ };
812
+ function safelyParseJSON(jsonString) {
813
+ try {
814
+ return JSON.parse(jsonString);
815
+ } catch {
816
+ return {};
817
+ }
818
+ }
819
+ var LibSQLStore = class extends MastraStorage {
820
+ client;
821
+ constructor(config) {
822
+ super({ name: `LibSQLStore` });
823
+ if (config.url.endsWith(":memory:")) {
824
+ this.shouldCacheInit = false;
825
+ }
826
+ this.client = createClient(config);
827
+ }
828
+ getCreateTableSQL(tableName, schema) {
829
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
830
+ const columns = Object.entries(schema).map(([name, col]) => {
831
+ const parsedColumnName = parseSqlIdentifier(name, "column name");
832
+ let type = col.type.toUpperCase();
833
+ if (type === "TEXT") type = "TEXT";
834
+ if (type === "TIMESTAMP") type = "TEXT";
835
+ const nullable = col.nullable ? "" : "NOT NULL";
836
+ const primaryKey = col.primaryKey ? "PRIMARY KEY" : "";
837
+ return `${parsedColumnName} ${type} ${nullable} ${primaryKey}`.trim();
838
+ });
839
+ if (tableName === TABLE_WORKFLOW_SNAPSHOT) {
840
+ const stmnt = `CREATE TABLE IF NOT EXISTS ${parsedTableName} (
841
+ ${columns.join(",\n")},
842
+ PRIMARY KEY (workflow_name, run_id)
843
+ )`;
844
+ return stmnt;
845
+ }
846
+ return `CREATE TABLE IF NOT EXISTS ${parsedTableName} (${columns.join(", ")})`;
847
+ }
848
+ async createTable({
849
+ tableName,
850
+ schema
851
+ }) {
852
+ try {
853
+ this.logger.debug(`Creating database table`, { tableName, operation: "schema init" });
854
+ const sql = this.getCreateTableSQL(tableName, schema);
855
+ await this.client.execute(sql);
856
+ } catch (error) {
857
+ this.logger.error(`Error creating table ${tableName}: ${error}`);
858
+ throw error;
859
+ }
860
+ }
861
+ async clearTable({ tableName }) {
862
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
863
+ try {
864
+ await this.client.execute(`DELETE FROM ${parsedTableName}`);
865
+ } catch (e) {
866
+ if (e instanceof Error) {
867
+ this.logger.error(e.message);
868
+ }
869
+ }
870
+ }
871
+ prepareStatement({ tableName, record }) {
872
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
873
+ const columns = Object.keys(record).map((col) => parseSqlIdentifier(col, "column name"));
874
+ const values = Object.values(record).map((v) => {
875
+ if (typeof v === `undefined`) {
876
+ return null;
877
+ }
878
+ if (v instanceof Date) {
879
+ return v.toISOString();
880
+ }
881
+ return typeof v === "object" ? JSON.stringify(v) : v;
882
+ });
883
+ const placeholders = values.map(() => "?").join(", ");
884
+ return {
885
+ sql: `INSERT OR REPLACE INTO ${parsedTableName} (${columns.join(", ")}) VALUES (${placeholders})`,
886
+ args: values
887
+ };
888
+ }
889
+ async insert({ tableName, record }) {
890
+ try {
891
+ await this.client.execute(
892
+ this.prepareStatement({
893
+ tableName,
894
+ record
895
+ })
896
+ );
897
+ } catch (error) {
898
+ this.logger.error(`Error upserting into table ${tableName}: ${error}`);
899
+ throw error;
900
+ }
901
+ }
902
+ async batchInsert({ tableName, records }) {
903
+ if (records.length === 0) return;
904
+ try {
905
+ const batchStatements = records.map((r) => this.prepareStatement({ tableName, record: r }));
906
+ await this.client.batch(batchStatements, "write");
907
+ } catch (error) {
908
+ this.logger.error(`Error upserting into table ${tableName}: ${error}`);
909
+ throw error;
910
+ }
911
+ }
912
+ async load({ tableName, keys }) {
913
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
914
+ const parsedKeys = Object.keys(keys).map((key) => parseSqlIdentifier(key, "column name"));
915
+ const conditions = parsedKeys.map((key) => `${key} = ?`).join(" AND ");
916
+ const values = Object.values(keys);
917
+ const result = await this.client.execute({
918
+ sql: `SELECT * FROM ${parsedTableName} WHERE ${conditions} ORDER BY createdAt DESC LIMIT 1`,
919
+ args: values
920
+ });
921
+ if (!result.rows || result.rows.length === 0) {
922
+ return null;
923
+ }
924
+ const row = result.rows[0];
925
+ const parsed = Object.fromEntries(
926
+ Object.entries(row || {}).map(([k, v]) => {
927
+ try {
928
+ return [k, typeof v === "string" ? v.startsWith("{") || v.startsWith("[") ? JSON.parse(v) : v : v];
929
+ } catch {
930
+ return [k, v];
931
+ }
932
+ })
933
+ );
934
+ return parsed;
935
+ }
936
+ async getThreadById({ threadId }) {
937
+ const result = await this.load({
938
+ tableName: TABLE_THREADS,
939
+ keys: { id: threadId }
940
+ });
941
+ if (!result) {
942
+ return null;
943
+ }
944
+ return {
945
+ ...result,
946
+ metadata: typeof result.metadata === "string" ? JSON.parse(result.metadata) : result.metadata
947
+ };
948
+ }
949
+ async getThreadsByResourceId({ resourceId }) {
950
+ const result = await this.client.execute({
951
+ sql: `SELECT * FROM ${TABLE_THREADS} WHERE resourceId = ?`,
952
+ args: [resourceId]
953
+ });
954
+ if (!result.rows) {
955
+ return [];
956
+ }
957
+ return result.rows.map((thread) => ({
958
+ id: thread.id,
959
+ resourceId: thread.resourceId,
960
+ title: thread.title,
961
+ createdAt: thread.createdAt,
962
+ updatedAt: thread.updatedAt,
963
+ metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata
964
+ }));
965
+ }
966
+ async saveThread({ thread }) {
967
+ await this.insert({
968
+ tableName: TABLE_THREADS,
969
+ record: {
970
+ ...thread,
971
+ metadata: JSON.stringify(thread.metadata)
972
+ }
973
+ });
974
+ return thread;
975
+ }
976
+ async updateThread({
977
+ id,
978
+ title,
979
+ metadata
980
+ }) {
981
+ const thread = await this.getThreadById({ threadId: id });
982
+ if (!thread) {
983
+ throw new Error(`Thread ${id} not found`);
984
+ }
985
+ const updatedThread = {
986
+ ...thread,
987
+ title,
988
+ metadata: {
989
+ ...thread.metadata,
990
+ ...metadata
991
+ }
992
+ };
993
+ await this.client.execute({
994
+ sql: `UPDATE ${TABLE_THREADS} SET title = ?, metadata = ? WHERE id = ?`,
995
+ args: [title, JSON.stringify(updatedThread.metadata), id]
996
+ });
997
+ return updatedThread;
998
+ }
999
+ async deleteThread({ threadId }) {
1000
+ await this.client.execute({
1001
+ sql: `DELETE FROM ${TABLE_THREADS} WHERE id = ?`,
1002
+ args: [threadId]
1003
+ });
1004
+ }
1005
+ parseRow(row) {
1006
+ let content = row.content;
1007
+ try {
1008
+ content = JSON.parse(row.content);
1009
+ } catch {
1010
+ }
1011
+ return {
1012
+ id: row.id,
1013
+ content,
1014
+ role: row.role,
1015
+ type: row.type,
1016
+ createdAt: new Date(row.createdAt),
1017
+ threadId: row.thread_id
1018
+ };
1019
+ }
1020
+ async getMessages({ threadId, selectBy }) {
1021
+ try {
1022
+ const messages = [];
1023
+ const limit = typeof selectBy?.last === `number` ? selectBy.last : 40;
1024
+ if (selectBy?.include?.length) {
1025
+ const includeIds = selectBy.include.map((i) => i.id);
1026
+ const maxPrev = Math.max(...selectBy.include.map((i) => i.withPreviousMessages || 0));
1027
+ const maxNext = Math.max(...selectBy.include.map((i) => i.withNextMessages || 0));
1028
+ const includeResult = await this.client.execute({
1029
+ sql: `
1030
+ WITH numbered_messages AS (
1031
+ SELECT
1032
+ id,
1033
+ content,
1034
+ role,
1035
+ type,
1036
+ "createdAt",
1037
+ thread_id,
1038
+ ROW_NUMBER() OVER (ORDER BY "createdAt" ASC) as row_num
1039
+ FROM "${TABLE_MESSAGES}"
1040
+ WHERE thread_id = ?
1041
+ ),
1042
+ target_positions AS (
1043
+ SELECT row_num as target_pos
1044
+ FROM numbered_messages
1045
+ WHERE id IN (${includeIds.map(() => "?").join(", ")})
1046
+ )
1047
+ SELECT DISTINCT m.*
1048
+ FROM numbered_messages m
1049
+ CROSS JOIN target_positions t
1050
+ WHERE m.row_num BETWEEN (t.target_pos - ?) AND (t.target_pos + ?)
1051
+ ORDER BY m."createdAt" ASC
1052
+ `,
1053
+ args: [threadId, ...includeIds, maxPrev, maxNext]
1054
+ });
1055
+ if (includeResult.rows) {
1056
+ messages.push(...includeResult.rows.map((row) => this.parseRow(row)));
1057
+ }
1058
+ }
1059
+ const excludeIds = messages.map((m) => m.id);
1060
+ const remainingSql = `
1061
+ SELECT
1062
+ id,
1063
+ content,
1064
+ role,
1065
+ type,
1066
+ "createdAt",
1067
+ thread_id
1068
+ FROM "${TABLE_MESSAGES}"
1069
+ WHERE thread_id = ?
1070
+ ${excludeIds.length ? `AND id NOT IN (${excludeIds.map(() => "?").join(", ")})` : ""}
1071
+ ORDER BY "createdAt" DESC
1072
+ LIMIT ?
1073
+ `;
1074
+ const remainingArgs = [threadId, ...excludeIds.length ? excludeIds : [], limit];
1075
+ const remainingResult = await this.client.execute({
1076
+ sql: remainingSql,
1077
+ args: remainingArgs
1078
+ });
1079
+ if (remainingResult.rows) {
1080
+ messages.push(...remainingResult.rows.map((row) => this.parseRow(row)));
1081
+ }
1082
+ messages.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
1083
+ return messages;
1084
+ } catch (error) {
1085
+ this.logger.error("Error getting messages:", error);
1086
+ throw error;
1087
+ }
1088
+ }
1089
+ async saveMessages({ messages }) {
1090
+ if (messages.length === 0) return messages;
1091
+ try {
1092
+ const threadId = messages[0]?.threadId;
1093
+ if (!threadId) {
1094
+ throw new Error("Thread ID is required");
1095
+ }
1096
+ const batchStatements = messages.map((message) => {
1097
+ const time = message.createdAt || /* @__PURE__ */ new Date();
1098
+ return {
1099
+ sql: `INSERT INTO ${TABLE_MESSAGES} (id, thread_id, content, role, type, createdAt)
1100
+ VALUES (?, ?, ?, ?, ?, ?)`,
1101
+ args: [
1102
+ message.id,
1103
+ threadId,
1104
+ typeof message.content === "object" ? JSON.stringify(message.content) : message.content,
1105
+ message.role,
1106
+ message.type,
1107
+ time instanceof Date ? time.toISOString() : time
1108
+ ]
1109
+ };
1110
+ });
1111
+ await this.client.batch(batchStatements, "write");
1112
+ return messages;
1113
+ } catch (error) {
1114
+ this.logger.error("Failed to save messages in database: " + error?.message);
1115
+ throw error;
1116
+ }
1117
+ }
1118
+ transformEvalRow(row) {
1119
+ const resultValue = JSON.parse(row.result);
1120
+ const testInfoValue = row.test_info ? JSON.parse(row.test_info) : void 0;
1121
+ if (!resultValue || typeof resultValue !== "object" || !("score" in resultValue)) {
1122
+ throw new Error(`Invalid MetricResult format: ${JSON.stringify(resultValue)}`);
1123
+ }
1124
+ return {
1125
+ input: row.input,
1126
+ output: row.output,
1127
+ result: resultValue,
1128
+ agentName: row.agent_name,
1129
+ metricName: row.metric_name,
1130
+ instructions: row.instructions,
1131
+ testInfo: testInfoValue,
1132
+ globalRunId: row.global_run_id,
1133
+ runId: row.run_id,
1134
+ createdAt: row.created_at
1135
+ };
1136
+ }
1137
+ async getEvalsByAgentName(agentName, type) {
1138
+ try {
1139
+ const baseQuery = `SELECT * FROM ${TABLE_EVALS} WHERE agent_name = ?`;
1140
+ const typeCondition = type === "test" ? " AND test_info IS NOT NULL AND test_info->>'testPath' IS NOT NULL" : type === "live" ? " AND (test_info IS NULL OR test_info->>'testPath' IS NULL)" : "";
1141
+ const result = await this.client.execute({
1142
+ sql: `${baseQuery}${typeCondition} ORDER BY created_at DESC`,
1143
+ args: [agentName]
1144
+ });
1145
+ return result.rows?.map((row) => this.transformEvalRow(row)) ?? [];
1146
+ } catch (error) {
1147
+ if (error instanceof Error && error.message.includes("no such table")) {
1148
+ return [];
1149
+ }
1150
+ this.logger.error("Failed to get evals for the specified agent: " + error?.message);
1151
+ throw error;
1152
+ }
1153
+ }
1154
+ // TODO: add types
1155
+ async getTraces({
1156
+ name,
1157
+ scope,
1158
+ page,
1159
+ perPage,
1160
+ attributes,
1161
+ filters,
1162
+ fromDate,
1163
+ toDate
1164
+ } = {
1165
+ page: 0,
1166
+ perPage: 100
1167
+ }) {
1168
+ const limit = perPage;
1169
+ const offset = page * perPage;
1170
+ const args = [];
1171
+ const conditions = [];
1172
+ if (name) {
1173
+ conditions.push("name LIKE CONCAT(?, '%')");
1174
+ }
1175
+ if (scope) {
1176
+ conditions.push("scope = ?");
1177
+ }
1178
+ if (attributes) {
1179
+ Object.keys(attributes).forEach((key) => {
1180
+ conditions.push(`attributes->>'$.${key}' = ?`);
1181
+ });
1182
+ }
1183
+ if (filters) {
1184
+ Object.entries(filters).forEach(([key, _value]) => {
1185
+ conditions.push(`${key} = ?`);
1186
+ });
1187
+ }
1188
+ if (fromDate) {
1189
+ conditions.push("createdAt >= ?");
1190
+ }
1191
+ if (toDate) {
1192
+ conditions.push("createdAt <= ?");
1193
+ }
1194
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
1195
+ if (name) {
1196
+ args.push(name);
1197
+ }
1198
+ if (scope) {
1199
+ args.push(scope);
1200
+ }
1201
+ if (attributes) {
1202
+ for (const [, value] of Object.entries(attributes)) {
1203
+ args.push(value);
1204
+ }
1205
+ }
1206
+ if (filters) {
1207
+ for (const [, value] of Object.entries(filters)) {
1208
+ args.push(value);
1209
+ }
1210
+ }
1211
+ if (fromDate) {
1212
+ args.push(fromDate.toISOString());
1213
+ }
1214
+ if (toDate) {
1215
+ args.push(toDate.toISOString());
1216
+ }
1217
+ args.push(limit, offset);
1218
+ const result = await this.client.execute({
1219
+ sql: `SELECT * FROM ${TABLE_TRACES} ${whereClause} ORDER BY "startTime" DESC LIMIT ? OFFSET ?`,
1220
+ args
1221
+ });
1222
+ if (!result.rows) {
1223
+ return [];
1224
+ }
1225
+ return result.rows.map((row) => ({
1226
+ id: row.id,
1227
+ parentSpanId: row.parentSpanId,
1228
+ traceId: row.traceId,
1229
+ name: row.name,
1230
+ scope: row.scope,
1231
+ kind: row.kind,
1232
+ status: safelyParseJSON(row.status),
1233
+ events: safelyParseJSON(row.events),
1234
+ links: safelyParseJSON(row.links),
1235
+ attributes: safelyParseJSON(row.attributes),
1236
+ startTime: row.startTime,
1237
+ endTime: row.endTime,
1238
+ other: safelyParseJSON(row.other),
1239
+ createdAt: row.createdAt
1240
+ }));
1241
+ }
1242
+ async getWorkflowRuns({
1243
+ workflowName,
1244
+ fromDate,
1245
+ toDate,
1246
+ limit,
1247
+ offset,
1248
+ resourceId
1249
+ } = {}) {
1250
+ try {
1251
+ const conditions = [];
1252
+ const args = [];
1253
+ if (workflowName) {
1254
+ conditions.push("workflow_name = ?");
1255
+ args.push(workflowName);
1256
+ }
1257
+ if (fromDate) {
1258
+ conditions.push("createdAt >= ?");
1259
+ args.push(fromDate.toISOString());
1260
+ }
1261
+ if (toDate) {
1262
+ conditions.push("createdAt <= ?");
1263
+ args.push(toDate.toISOString());
1264
+ }
1265
+ if (resourceId) {
1266
+ const hasResourceId = await this.hasColumn(TABLE_WORKFLOW_SNAPSHOT, "resourceId");
1267
+ if (hasResourceId) {
1268
+ conditions.push("resourceId = ?");
1269
+ args.push(resourceId);
1270
+ } else {
1271
+ console.warn(`[${TABLE_WORKFLOW_SNAPSHOT}] resourceId column not found. Skipping resourceId filter.`);
1272
+ }
1273
+ }
1274
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
1275
+ let total = 0;
1276
+ if (limit !== void 0 && offset !== void 0) {
1277
+ const countResult = await this.client.execute({
1278
+ sql: `SELECT COUNT(*) as count FROM ${TABLE_WORKFLOW_SNAPSHOT} ${whereClause}`,
1279
+ args
1280
+ });
1281
+ total = Number(countResult.rows?.[0]?.count ?? 0);
1282
+ }
1283
+ const result = await this.client.execute({
1284
+ sql: `SELECT * FROM ${TABLE_WORKFLOW_SNAPSHOT} ${whereClause} ORDER BY createdAt DESC${limit !== void 0 && offset !== void 0 ? ` LIMIT ? OFFSET ?` : ""}`,
1285
+ args: limit !== void 0 && offset !== void 0 ? [...args, limit, offset] : args
1286
+ });
1287
+ const runs = (result.rows || []).map((row) => this.parseWorkflowRun(row));
1288
+ return { runs, total: total || runs.length };
1289
+ } catch (error) {
1290
+ console.error("Error getting workflow runs:", error);
1291
+ throw error;
1292
+ }
1293
+ }
1294
+ async getWorkflowRunById({
1295
+ runId,
1296
+ workflowName
1297
+ }) {
1298
+ const conditions = [];
1299
+ const args = [];
1300
+ if (runId) {
1301
+ conditions.push("run_id = ?");
1302
+ args.push(runId);
1303
+ }
1304
+ if (workflowName) {
1305
+ conditions.push("workflow_name = ?");
1306
+ args.push(workflowName);
1307
+ }
1308
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
1309
+ const result = await this.client.execute({
1310
+ sql: `SELECT * FROM ${TABLE_WORKFLOW_SNAPSHOT} ${whereClause}`,
1311
+ args
1312
+ });
1313
+ if (!result.rows?.[0]) {
1314
+ return null;
1315
+ }
1316
+ return this.parseWorkflowRun(result.rows[0]);
1317
+ }
1318
+ async hasColumn(table, column) {
1319
+ const result = await this.client.execute({
1320
+ sql: `PRAGMA table_info(${table})`
1321
+ });
1322
+ return (await result.rows)?.some((row) => row.name === column);
1323
+ }
1324
+ parseWorkflowRun(row) {
1325
+ let parsedSnapshot = row.snapshot;
1326
+ if (typeof parsedSnapshot === "string") {
1327
+ try {
1328
+ parsedSnapshot = JSON.parse(row.snapshot);
1329
+ } catch (e) {
1330
+ console.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`);
1331
+ }
1332
+ }
1333
+ return {
1334
+ workflowName: row.workflow_name,
1335
+ runId: row.run_id,
1336
+ snapshot: parsedSnapshot,
1337
+ resourceId: row.resourceId,
1338
+ createdAt: new Date(row.created_at),
1339
+ updatedAt: new Date(row.updated_at)
1340
+ };
1341
+ }
1342
+ };
1343
+
1344
+ // src/vector/prompt.ts
1345
+ var LIBSQL_PROMPT = `When querying LibSQL Vector, you can ONLY use the operators listed below. Any other operators will be rejected.
1346
+ Important: Don't explain how to construct the filter - use the specified operators and fields to search the content and return relevant results.
1347
+ If a user tries to give an explicit operator that is not supported, reject the filter entirely and let them know that the operator is not supported.
1348
+
1349
+ Basic Comparison Operators:
1350
+ - $eq: Exact match (default when using field: value)
1351
+ Example: { "category": "electronics" }
1352
+ - $ne: Not equal
1353
+ Example: { "category": { "$ne": "electronics" } }
1354
+ - $gt: Greater than
1355
+ Example: { "price": { "$gt": 100 } }
1356
+ - $gte: Greater than or equal
1357
+ Example: { "price": { "$gte": 100 } }
1358
+ - $lt: Less than
1359
+ Example: { "price": { "$lt": 100 } }
1360
+ - $lte: Less than or equal
1361
+ Example: { "price": { "$lte": 100 } }
1362
+
1363
+ Array Operators:
1364
+ - $in: Match any value in array
1365
+ Example: { "category": { "$in": ["electronics", "books"] } }
1366
+ - $nin: Does not match any value in array
1367
+ Example: { "category": { "$nin": ["electronics", "books"] } }
1368
+ - $all: Match all values in array
1369
+ Example: { "tags": { "$all": ["premium", "sale"] } }
1370
+ - $elemMatch: Match array elements that meet all specified conditions
1371
+ Example: { "items": { "$elemMatch": { "price": { "$gt": 100 } } } }
1372
+ - $contains: Check if array contains value
1373
+ Example: { "tags": { "$contains": "premium" } }
1374
+
1375
+ Logical Operators:
1376
+ - $and: Logical AND (implicit when using multiple conditions)
1377
+ Example: { "$and": [{ "price": { "$gt": 100 } }, { "category": "electronics" }] }
1378
+ - $or: Logical OR
1379
+ Example: { "$or": [{ "price": { "$lt": 50 } }, { "category": "books" }] }
1380
+ - $not: Logical NOT
1381
+ Example: { "$not": { "category": "electronics" } }
1382
+ - $nor: Logical NOR
1383
+ Example: { "$nor": [{ "price": { "$lt": 50 } }, { "category": "books" }] }
1384
+
1385
+ Element Operators:
1386
+ - $exists: Check if field exists
1387
+ Example: { "rating": { "$exists": true } }
1388
+
1389
+ Special Operators:
1390
+ - $size: Array length check
1391
+ Example: { "tags": { "$size": 2 } }
1392
+
1393
+ Restrictions:
1394
+ - Regex patterns are not supported
1395
+ - Direct RegExp patterns will throw an error
1396
+ - Nested fields are supported using dot notation
1397
+ - Multiple conditions on the same field are supported with both implicit and explicit $and
1398
+ - Array operations work on array fields only
1399
+ - Basic operators handle array values as JSON strings
1400
+ - Empty arrays in conditions are handled gracefully
1401
+ - Only logical operators ($and, $or, $not, $nor) can be used at the top level
1402
+ - All other operators must be used within a field condition
1403
+ Valid: { "field": { "$gt": 100 } }
1404
+ Valid: { "$and": [...] }
1405
+ Invalid: { "$gt": 100 }
1406
+ Invalid: { "$contains": "value" }
1407
+ - Logical operators must contain field conditions, not direct operators
1408
+ Valid: { "$and": [{ "field": { "$gt": 100 } }] }
1409
+ Invalid: { "$and": [{ "$gt": 100 }] }
1410
+ - $not operator:
1411
+ - Must be an object
1412
+ - Cannot be empty
1413
+ - Can be used at field level or top level
1414
+ - Valid: { "$not": { "field": "value" } }
1415
+ - Valid: { "field": { "$not": { "$eq": "value" } } }
1416
+ - Other logical operators ($and, $or, $nor):
1417
+ - Can only be used at top level or nested within other logical operators
1418
+ - Can not be used on a field level, or be nested inside a field
1419
+ - Can not be used inside an operator
1420
+ - Valid: { "$and": [{ "field": { "$gt": 100 } }] }
1421
+ - Valid: { "$or": [{ "$and": [{ "field": { "$gt": 100 } }] }] }
1422
+ - Invalid: { "field": { "$and": [{ "$gt": 100 }] } }
1423
+ - Invalid: { "field": { "$or": [{ "$gt": 100 }] } }
1424
+ - Invalid: { "field": { "$gt": { "$and": [{...}] } } }
1425
+ - $elemMatch requires an object with conditions
1426
+ Valid: { "array": { "$elemMatch": { "field": "value" } } }
1427
+ Invalid: { "array": { "$elemMatch": "value" } }
1428
+
1429
+ Example Complex Query:
1430
+ {
1431
+ "$and": [
1432
+ { "category": { "$in": ["electronics", "computers"] } },
1433
+ { "price": { "$gte": 100, "$lte": 1000 } },
1434
+ { "tags": { "$all": ["premium", "sale"] } },
1435
+ { "items": { "$elemMatch": { "price": { "$gt": 50 }, "inStock": true } } },
1436
+ { "$or": [
1437
+ { "stock": { "$gt": 0 } },
1438
+ { "preorder": true }
1439
+ ]}
1440
+ ]
1441
+ }`;
1442
+
1443
+ export { LibSQLStore as DefaultStorage, LIBSQL_PROMPT, LibSQLStore, LibSQLVector };