@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.
@@ -0,0 +1,530 @@
1
+ import type { InValue } from '@libsql/client';
2
+ import { parseFieldKey } from '@mastra/core/utils';
3
+ import type {
4
+ BasicOperator,
5
+ NumericOperator,
6
+ ArrayOperator,
7
+ ElementOperator,
8
+ LogicalOperator,
9
+ VectorFilter,
10
+ } from '@mastra/core/vector/filter';
11
+
12
+ type OperatorType =
13
+ | BasicOperator
14
+ | NumericOperator
15
+ | ArrayOperator
16
+ | ElementOperator
17
+ | LogicalOperator
18
+ | '$contains'
19
+ | '$size';
20
+
21
+ type FilterOperator = {
22
+ sql: string;
23
+ needsValue: boolean;
24
+ transformValue?: () => any;
25
+ };
26
+
27
+ type OperatorFn = (key: string, value?: any) => FilterOperator;
28
+
29
+ // Helper functions to create operators
30
+ const createBasicOperator = (symbol: string) => {
31
+ return (key: string, value: any): FilterOperator => {
32
+ const jsonPathKey = parseJsonPathKey(key);
33
+ return {
34
+ sql: `CASE
35
+ WHEN ? IS NULL THEN json_extract(metadata, '$."${jsonPathKey}"') IS ${symbol === '=' ? '' : 'NOT'} NULL
36
+ ELSE json_extract(metadata, '$."${jsonPathKey}"') ${symbol} ?
37
+ END`,
38
+ needsValue: true,
39
+ transformValue: () => {
40
+ // Return the values directly, not in an object
41
+ return [value, value];
42
+ },
43
+ };
44
+ };
45
+ };
46
+ const createNumericOperator = (symbol: string) => {
47
+ return (key: string): FilterOperator => {
48
+ const jsonPathKey = parseJsonPathKey(key);
49
+ return {
50
+ sql: `CAST(json_extract(metadata, '$."${jsonPathKey}"') AS NUMERIC) ${symbol} ?`,
51
+ needsValue: true,
52
+ };
53
+ };
54
+ };
55
+
56
+ const validateJsonArray = (key: string) =>
57
+ `json_valid(json_extract(metadata, '$."${key}"'))
58
+ AND json_type(json_extract(metadata, '$."${key}"')) = 'array'`;
59
+
60
+ const pattern = /json_extract\(metadata, '\$\."[^"]*"(\."[^"]*")*'\)/g;
61
+
62
+ function buildElemMatchConditions(value: any) {
63
+ const conditions = Object.entries(value).map(([field, fieldValue]) => {
64
+ if (field.startsWith('$')) {
65
+ // Direct operators on array elements ($in, $gt, etc)
66
+ const { sql, values } = buildCondition('elem.value', { [field]: fieldValue }, '');
67
+ // Replace the metadata path with elem.value
68
+ const elemSql = sql.replace(pattern, 'elem.value');
69
+ return { sql: elemSql, values };
70
+ } else if (typeof fieldValue === 'object' && !Array.isArray(fieldValue)) {
71
+ // Nested field with operators (count: { $gt: 20 })
72
+ const { sql, values } = buildCondition(field, fieldValue, '');
73
+ // Replace the field path with elem.value path
74
+ const elemSql = sql.replace(pattern, `json_extract(elem.value, '$."${field}"')`);
75
+ return { sql: elemSql, values };
76
+ } else {
77
+ const parsedFieldKey = parseFieldKey(field);
78
+ // Simple field equality (warehouse: 'A')
79
+ return {
80
+ sql: `json_extract(elem.value, '$."${parsedFieldKey}"') = ?`,
81
+ values: [fieldValue],
82
+ };
83
+ }
84
+ });
85
+
86
+ return conditions;
87
+ }
88
+
89
+ // Define all filter operators
90
+ const FILTER_OPERATORS: Record<OperatorType, OperatorFn> = {
91
+ $eq: createBasicOperator('='),
92
+ $ne: createBasicOperator('!='),
93
+ $gt: createNumericOperator('>'),
94
+ $gte: createNumericOperator('>='),
95
+ $lt: createNumericOperator('<'),
96
+ $lte: createNumericOperator('<='),
97
+
98
+ // Array Operators
99
+ $in: (key: string, value: any) => {
100
+ const jsonPathKey = parseJsonPathKey(key);
101
+ const arr = Array.isArray(value) ? value : [value];
102
+ if (arr.length === 0) {
103
+ return { sql: '1 = 0', needsValue: true, transformValue: () => [] };
104
+ }
105
+ const paramPlaceholders = arr.map(() => '?').join(',');
106
+ return {
107
+ sql: `(
108
+ CASE
109
+ WHEN ${validateJsonArray(jsonPathKey)} THEN
110
+ EXISTS (
111
+ SELECT 1 FROM json_each(json_extract(metadata, '$."${jsonPathKey}"')) as elem
112
+ WHERE elem.value IN (SELECT value FROM json_each(?))
113
+ )
114
+ ELSE json_extract(metadata, '$."${jsonPathKey}"') IN (${paramPlaceholders})
115
+ END
116
+ )`,
117
+ needsValue: true,
118
+ transformValue: () => [JSON.stringify(arr), ...arr],
119
+ };
120
+ },
121
+
122
+ $nin: (key: string, value: any) => {
123
+ const jsonPathKey = parseJsonPathKey(key);
124
+ const arr = Array.isArray(value) ? value : [value];
125
+ if (arr.length === 0) {
126
+ return { sql: '1 = 1', needsValue: true, transformValue: () => [] };
127
+ }
128
+ const paramPlaceholders = arr.map(() => '?').join(',');
129
+ return {
130
+ sql: `(
131
+ CASE
132
+ WHEN ${validateJsonArray(jsonPathKey)} THEN
133
+ NOT EXISTS (
134
+ SELECT 1 FROM json_each(json_extract(metadata, '$."${jsonPathKey}"')) as elem
135
+ WHERE elem.value IN (SELECT value FROM json_each(?))
136
+ )
137
+ ELSE json_extract(metadata, '$."${jsonPathKey}"') NOT IN (${paramPlaceholders})
138
+ END
139
+ )`,
140
+ needsValue: true,
141
+ transformValue: () => [JSON.stringify(arr), ...arr],
142
+ };
143
+ },
144
+ $all: (key: string, value: any) => {
145
+ const jsonPathKey = parseJsonPathKey(key);
146
+ let sql: string;
147
+ const arrayValue = Array.isArray(value) ? value : [value];
148
+
149
+ if (arrayValue.length === 0) {
150
+ // If the array is empty, always return false (no matches)
151
+ sql = '1 = 0';
152
+ } else {
153
+ sql = `(
154
+ CASE
155
+ WHEN ${validateJsonArray(jsonPathKey)} THEN
156
+ NOT EXISTS (
157
+ SELECT value
158
+ FROM json_each(?)
159
+ WHERE value NOT IN (
160
+ SELECT value
161
+ FROM json_each(json_extract(metadata, '$."${jsonPathKey}"'))
162
+ )
163
+ )
164
+ ELSE FALSE
165
+ END
166
+ )`;
167
+ }
168
+
169
+ return {
170
+ sql,
171
+ needsValue: true,
172
+ transformValue: () => {
173
+ if (arrayValue.length === 0) {
174
+ return [];
175
+ }
176
+ return [JSON.stringify(arrayValue)];
177
+ },
178
+ };
179
+ },
180
+ $elemMatch: (key: string, value: any) => {
181
+ const jsonPathKey = parseJsonPathKey(key);
182
+ if (typeof value !== 'object' || Array.isArray(value)) {
183
+ throw new Error('$elemMatch requires an object with conditions');
184
+ }
185
+
186
+ // For nested object conditions
187
+ const conditions = buildElemMatchConditions(value);
188
+
189
+ return {
190
+ sql: `(
191
+ CASE
192
+ WHEN ${validateJsonArray(jsonPathKey)} THEN
193
+ EXISTS (
194
+ SELECT 1
195
+ FROM json_each(json_extract(metadata, '$."${jsonPathKey}"')) as elem
196
+ WHERE ${conditions.map(c => c.sql).join(' AND ')}
197
+ )
198
+ ELSE FALSE
199
+ END
200
+ )`,
201
+ needsValue: true,
202
+ transformValue: () => conditions.flatMap(c => c.values),
203
+ };
204
+ },
205
+
206
+ // Element Operators
207
+ $exists: (key: string) => {
208
+ const jsonPathKey = parseJsonPathKey(key);
209
+ return {
210
+ sql: `json_extract(metadata, '$."${jsonPathKey}"') IS NOT NULL`,
211
+ needsValue: false,
212
+ };
213
+ },
214
+
215
+ // Logical Operators
216
+ $and: (key: string) => ({
217
+ sql: `(${key})`,
218
+ needsValue: false,
219
+ }),
220
+ $or: (key: string) => ({
221
+ sql: `(${key})`,
222
+ needsValue: false,
223
+ }),
224
+ $not: key => ({ sql: `NOT (${key})`, needsValue: false }),
225
+ $nor: (key: string) => ({
226
+ sql: `NOT (${key})`,
227
+ needsValue: false,
228
+ }),
229
+ $size: (key: string, paramIndex: number) => {
230
+ const jsonPathKey = parseJsonPathKey(key);
231
+ return {
232
+ sql: `(
233
+ CASE
234
+ WHEN json_type(json_extract(metadata, '$."${jsonPathKey}"')) = 'array' THEN
235
+ json_array_length(json_extract(metadata, '$."${jsonPathKey}"')) = $${paramIndex}
236
+ ELSE FALSE
237
+ END
238
+ )`,
239
+ needsValue: true,
240
+ };
241
+ },
242
+ // /**
243
+ // * Regex Operators
244
+ // * Supports case insensitive and multiline
245
+ // */
246
+ // $regex: (key: string): FilterOperator => ({
247
+ // sql: `json_extract(metadata, '$."${toJsonPathKey(key)}"') = ?`,
248
+ // needsValue: true,
249
+ // transformValue: (value: any) => {
250
+ // const pattern = typeof value === 'object' ? value.$regex : value;
251
+ // const options = typeof value === 'object' ? value.$options || '' : '';
252
+ // let sql = `json_extract(metadata, '$."${toJsonPathKey(key)}"')`;
253
+
254
+ // // Handle multiline
255
+ // // if (options.includes('m')) {
256
+ // // sql = `REPLACE(${sql}, CHAR(10), '\n')`;
257
+ // // }
258
+
259
+ // // let finalPattern = pattern;
260
+ // // if (options) {
261
+ // // finalPattern = `(\\?${options})${pattern}`;
262
+ // // }
263
+
264
+ // // // Handle case insensitivity
265
+ // // if (options.includes('i')) {
266
+ // // sql = `LOWER(${sql}) REGEXP LOWER(?)`;
267
+ // // } else {
268
+ // // sql = `${sql} REGEXP ?`;
269
+ // // }
270
+
271
+ // if (options.includes('m')) {
272
+ // sql = `EXISTS (
273
+ // SELECT 1
274
+ // FROM json_each(
275
+ // json_array(
276
+ // ${sql},
277
+ // REPLACE(${sql}, CHAR(10), CHAR(13))
278
+ // )
279
+ // ) as lines
280
+ // WHERE lines.value REGEXP ?
281
+ // )`;
282
+ // } else {
283
+ // sql = `${sql} REGEXP ?`;
284
+ // }
285
+
286
+ // // Handle case insensitivity
287
+ // if (options.includes('i')) {
288
+ // sql = sql.replace('REGEXP ?', 'REGEXP LOWER(?)');
289
+ // sql = sql.replace('value REGEXP', 'LOWER(value) REGEXP');
290
+ // }
291
+
292
+ // // Handle extended - allows whitespace and comments in pattern
293
+ // if (options.includes('x')) {
294
+ // // Remove whitespace and comments from pattern
295
+ // const cleanPattern = pattern.replace(/\s+|#.*$/gm, '');
296
+ // return {
297
+ // sql,
298
+ // values: [cleanPattern],
299
+ // };
300
+ // }
301
+
302
+ // return {
303
+ // sql,
304
+ // values: [pattern],
305
+ // };
306
+ // },
307
+ // }),
308
+ $contains: (key: string, value: any) => {
309
+ const jsonPathKey = parseJsonPathKey(key);
310
+ let sql;
311
+ if (Array.isArray(value)) {
312
+ sql = `(
313
+ SELECT ${validateJsonArray(jsonPathKey)}
314
+ AND EXISTS (
315
+ SELECT 1
316
+ FROM json_each(json_extract(metadata, '$."${jsonPathKey}"')) as m
317
+ WHERE m.value IN (SELECT value FROM json_each(?))
318
+ )
319
+ )`;
320
+ } else if (typeof value === 'string') {
321
+ sql = `lower(json_extract(metadata, '$."${jsonPathKey}"')) LIKE '%' || lower(?) || '%' ESCAPE '\\'`;
322
+ } else {
323
+ sql = `json_extract(metadata, '$."${jsonPathKey}"') = ?`;
324
+ }
325
+ return {
326
+ sql,
327
+ needsValue: true,
328
+ transformValue: () => {
329
+ if (Array.isArray(value)) {
330
+ return [JSON.stringify(value)];
331
+ }
332
+ if (typeof value === 'object' && value !== null) {
333
+ return [JSON.stringify(value)];
334
+ }
335
+ if (typeof value === 'string') {
336
+ return [escapeLikePattern(value)];
337
+ }
338
+ return [value];
339
+ },
340
+ };
341
+ },
342
+ /**
343
+ * $objectContains: True JSON containment for advanced use (deep sub-object match).
344
+ * Usage: { field: { $objectContains: { ...subobject } } }
345
+ */
346
+ // $objectContains: (key: string) => ({
347
+ // sql: '', // Will be overridden by transformValue
348
+ // needsValue: true,
349
+ // transformValue: (value: any) => ({
350
+ // sql: `json_type(json_extract(metadata, '$."${toJsonPathKey(key)}"')) = 'object'
351
+ // AND json_patch(json_extract(metadata, '$."${toJsonPathKey(key)}"'), ?) = json_extract(metadata, '$."${toJsonPathKey(key)}"')`,
352
+ // values: [JSON.stringify(value)],
353
+ // }),
354
+ // }),
355
+ };
356
+
357
+ interface FilterResult {
358
+ sql: string;
359
+ values: InValue[];
360
+ }
361
+
362
+ function isFilterResult(obj: any): obj is FilterResult {
363
+ return obj && typeof obj === 'object' && typeof obj.sql === 'string' && Array.isArray(obj.values);
364
+ }
365
+
366
+ const parseJsonPathKey = (key: string) => {
367
+ const parsedKey = parseFieldKey(key);
368
+ return parsedKey.replace(/\./g, '"."');
369
+ };
370
+
371
+ function escapeLikePattern(str: string): string {
372
+ return str.replace(/([%_\\])/g, '\\$1');
373
+ }
374
+
375
+ export function buildFilterQuery(filter: VectorFilter): FilterResult {
376
+ if (!filter) {
377
+ return { sql: '', values: [] };
378
+ }
379
+
380
+ const values: InValue[] = [];
381
+ const conditions = Object.entries(filter)
382
+ .map(([key, value]) => {
383
+ const condition = buildCondition(key, value, '');
384
+ values.push(...condition.values);
385
+ return condition.sql;
386
+ })
387
+ .join(' AND ');
388
+
389
+ return {
390
+ sql: conditions ? `WHERE ${conditions}` : '',
391
+ values,
392
+ };
393
+ }
394
+
395
+ function buildCondition(key: string, value: any, parentPath: string): FilterResult {
396
+ // Handle logical operators ($and/$or)
397
+ if (['$and', '$or', '$not', '$nor'].includes(key)) {
398
+ return handleLogicalOperator(key as '$and' | '$or' | '$not' | '$nor', value, parentPath);
399
+ }
400
+
401
+ // If condition is not a FilterCondition object, assume it's an equality check
402
+ if (!value || typeof value !== 'object') {
403
+ return {
404
+ sql: `json_extract(metadata, '$."${key.replace(/\./g, '"."')}"') = ?`,
405
+ values: [value],
406
+ };
407
+ }
408
+
409
+ //TODO: Add regex support
410
+ // if ('$regex' in value) {
411
+ // return handleRegexOperator(key, value);
412
+ // }
413
+
414
+ // Handle operator conditions
415
+ return handleOperator(key, value);
416
+ }
417
+
418
+ // function handleRegexOperator(key: string, value: any): FilterResult {
419
+ // const operatorFn = FILTER_OPERATORS['$regex']!;
420
+ // const operatorResult = operatorFn(key, value);
421
+ // const transformed = operatorResult.transformValue ? operatorResult.transformValue(value) : value;
422
+
423
+ // return {
424
+ // sql: transformed.sql,
425
+ // values: transformed.values,
426
+ // };
427
+ // }
428
+
429
+ function handleLogicalOperator(
430
+ key: '$and' | '$or' | '$not' | '$nor',
431
+ value: VectorFilter[] | VectorFilter,
432
+ parentPath: string,
433
+ ): FilterResult {
434
+ // Handle empty conditions
435
+ if (!value || value.length === 0) {
436
+ switch (key) {
437
+ case '$and':
438
+ case '$nor':
439
+ return { sql: 'true', values: [] };
440
+ case '$or':
441
+ return { sql: 'false', values: [] };
442
+ case '$not':
443
+ throw new Error('$not operator cannot be empty');
444
+ default:
445
+ return { sql: 'true', values: [] };
446
+ }
447
+ }
448
+
449
+ if (key === '$not') {
450
+ // For top-level $not
451
+ const entries = Object.entries(value);
452
+ const conditions = entries.map(([fieldKey, fieldValue]) => buildCondition(fieldKey, fieldValue, key));
453
+ return {
454
+ sql: `NOT (${conditions.map(c => c.sql).join(' AND ')})`,
455
+ values: conditions.flatMap(c => c.values),
456
+ };
457
+ }
458
+
459
+ const values: InValue[] = [];
460
+ const joinOperator = key === '$or' || key === '$nor' ? 'OR' : 'AND';
461
+ const conditions = Array.isArray(value)
462
+ ? value.map(f => {
463
+ const entries = Object.entries(f);
464
+ return entries.map(([k, v]) => buildCondition(k, v, key));
465
+ })
466
+ : [buildCondition(key, value, parentPath)];
467
+
468
+ const joined = conditions
469
+ .flat()
470
+ .map(c => {
471
+ values.push(...c.values);
472
+ return c.sql;
473
+ })
474
+ .join(` ${joinOperator} `);
475
+
476
+ return {
477
+ sql: key === '$nor' ? `NOT (${joined})` : `(${joined})`,
478
+ values,
479
+ };
480
+ }
481
+
482
+ function handleOperator(key: string, value: any): FilterResult {
483
+ if (typeof value === 'object' && !Array.isArray(value)) {
484
+ const entries = Object.entries(value);
485
+ const results = entries.map(([operator, operatorValue]) =>
486
+ operator === '$not'
487
+ ? {
488
+ sql: `NOT (${Object.entries(operatorValue as Record<string, any>)
489
+ .map(([op, val]) => processOperator(key, op as OperatorType, val).sql)
490
+ .join(' AND ')})`,
491
+ values: Object.entries(operatorValue as Record<string, any>).flatMap(
492
+ ([op, val]) => processOperator(key, op as OperatorType, val).values,
493
+ ),
494
+ }
495
+ : processOperator(key, operator as OperatorType, operatorValue),
496
+ );
497
+
498
+ return {
499
+ sql: `(${results.map(r => r.sql).join(' AND ')})`,
500
+ values: results.flatMap(r => r.values),
501
+ };
502
+ }
503
+
504
+ // Handle single operator
505
+ const [[operator, operatorValue] = []] = Object.entries(value);
506
+ return processOperator(key, operator as OperatorType, operatorValue);
507
+ }
508
+
509
+ const processOperator = (key: string, operator: OperatorType, operatorValue: any): FilterResult => {
510
+ if (!operator.startsWith('$') || !FILTER_OPERATORS[operator]) {
511
+ throw new Error(`Invalid operator: ${operator}`);
512
+ }
513
+ const operatorFn = FILTER_OPERATORS[operator]!;
514
+ const operatorResult = operatorFn(key, operatorValue);
515
+
516
+ if (!operatorResult.needsValue) {
517
+ return { sql: operatorResult.sql, values: [] };
518
+ }
519
+
520
+ const transformed = operatorResult.transformValue ? operatorResult.transformValue() : operatorValue;
521
+
522
+ if (isFilterResult(transformed)) {
523
+ return transformed;
524
+ }
525
+
526
+ return {
527
+ sql: operatorResult.sql,
528
+ values: Array.isArray(transformed) ? transformed : [transformed],
529
+ };
530
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": "../../tsconfig.node.json",
3
+ "include": ["src/**/*"],
4
+ "exclude": ["node_modules", "**/*.test.ts"]
5
+ }
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ environment: 'node',
6
+ include: ['src/**/*.test.ts'],
7
+ coverage: {
8
+ reporter: ['text', 'json', 'html'],
9
+ },
10
+ },
11
+ });