@aigne/agent-library 1.10.0 → 1.12.0

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.
Files changed (64) hide show
  1. package/CHANGELOG.md +21 -2
  2. package/lib/cjs/data-mapper/agents/mapper.d.ts +14 -0
  3. package/lib/cjs/data-mapper/agents/mapper.js +63 -0
  4. package/lib/cjs/data-mapper/agents/reviewer.d.ts +19 -0
  5. package/lib/cjs/data-mapper/agents/reviewer.js +47 -0
  6. package/lib/cjs/data-mapper/index.d.ts +18 -0
  7. package/lib/cjs/data-mapper/index.js +29 -0
  8. package/lib/cjs/data-mapper/prompts.d.ts +1 -0
  9. package/lib/cjs/data-mapper/prompts.js +94 -0
  10. package/lib/cjs/data-mapper/tools.d.ts +17 -0
  11. package/lib/cjs/data-mapper/tools.js +177 -0
  12. package/lib/cjs/default-memory/default-memory-storage/index.d.ts +23 -0
  13. package/lib/cjs/default-memory/default-memory-storage/index.js +65 -0
  14. package/lib/cjs/default-memory/default-memory-storage/migrate.d.ts +3 -0
  15. package/lib/cjs/default-memory/default-memory-storage/migrate.js +32 -0
  16. package/lib/cjs/default-memory/default-memory-storage/migrations/001-init.d.ts +5 -0
  17. package/lib/cjs/default-memory/default-memory-storage/migrations/001-init.js +16 -0
  18. package/lib/cjs/default-memory/default-memory-storage/models/memory.d.ts +102 -0
  19. package/lib/cjs/default-memory/default-memory-storage/models/memory.js +21 -0
  20. package/lib/cjs/default-memory/index.d.ts +10 -0
  21. package/lib/cjs/default-memory/index.js +60 -0
  22. package/lib/cjs/default-memory/storage.d.ts +12 -0
  23. package/lib/cjs/default-memory/storage.js +6 -0
  24. package/lib/cjs/fs-memory/index.js +1 -1
  25. package/lib/cjs/utils/fs.d.ts +2 -0
  26. package/lib/cjs/utils/fs.js +25 -0
  27. package/lib/dts/data-mapper/agents/mapper.d.ts +14 -0
  28. package/lib/dts/data-mapper/agents/reviewer.d.ts +19 -0
  29. package/lib/dts/data-mapper/index.d.ts +18 -0
  30. package/lib/dts/data-mapper/prompts.d.ts +1 -0
  31. package/lib/dts/data-mapper/tools.d.ts +17 -0
  32. package/lib/dts/default-memory/default-memory-storage/index.d.ts +23 -0
  33. package/lib/dts/default-memory/default-memory-storage/migrate.d.ts +3 -0
  34. package/lib/dts/default-memory/default-memory-storage/migrations/001-init.d.ts +5 -0
  35. package/lib/dts/default-memory/default-memory-storage/models/memory.d.ts +102 -0
  36. package/lib/dts/default-memory/index.d.ts +10 -0
  37. package/lib/dts/default-memory/storage.d.ts +12 -0
  38. package/lib/dts/utils/fs.d.ts +2 -0
  39. package/lib/esm/data-mapper/agents/mapper.d.ts +14 -0
  40. package/lib/esm/data-mapper/agents/mapper.js +61 -0
  41. package/lib/esm/data-mapper/agents/reviewer.d.ts +19 -0
  42. package/lib/esm/data-mapper/agents/reviewer.js +45 -0
  43. package/lib/esm/data-mapper/index.d.ts +18 -0
  44. package/lib/esm/data-mapper/index.js +21 -0
  45. package/lib/esm/data-mapper/prompts.d.ts +1 -0
  46. package/lib/esm/data-mapper/prompts.js +91 -0
  47. package/lib/esm/data-mapper/tools.d.ts +17 -0
  48. package/lib/esm/data-mapper/tools.js +167 -0
  49. package/lib/esm/default-memory/default-memory-storage/index.d.ts +23 -0
  50. package/lib/esm/default-memory/default-memory-storage/index.js +61 -0
  51. package/lib/esm/default-memory/default-memory-storage/migrate.d.ts +3 -0
  52. package/lib/esm/default-memory/default-memory-storage/migrate.js +26 -0
  53. package/lib/esm/default-memory/default-memory-storage/migrations/001-init.d.ts +5 -0
  54. package/lib/esm/default-memory/default-memory-storage/migrations/001-init.js +14 -0
  55. package/lib/esm/default-memory/default-memory-storage/models/memory.d.ts +102 -0
  56. package/lib/esm/default-memory/default-memory-storage/models/memory.js +18 -0
  57. package/lib/esm/default-memory/index.d.ts +10 -0
  58. package/lib/esm/default-memory/index.js +53 -0
  59. package/lib/esm/default-memory/storage.d.ts +12 -0
  60. package/lib/esm/default-memory/storage.js +2 -0
  61. package/lib/esm/fs-memory/index.js +1 -1
  62. package/lib/esm/utils/fs.d.ts +2 -0
  63. package/lib/esm/utils/fs.js +21 -0
  64. package/package.json +10 -2
@@ -0,0 +1,45 @@
1
+ import { FunctionAgent, UserOutputTopic } from "@aigne/core";
2
+ import { z } from "zod";
3
+ import { applyJsonataWithValidation } from "../tools.js";
4
+ const reviewer = FunctionAgent.from({
5
+ name: "check_mapping",
6
+ description: "Check the mapping result",
7
+ subscribeTopic: ["review_request"],
8
+ publishTopic: (output) => (output.success ? UserOutputTopic : "mapping_request"),
9
+ inputSchema: z.object({
10
+ sourceData: z.string(),
11
+ jsonata: z.string(),
12
+ responseSchema: z.string(),
13
+ }),
14
+ process: async ({ sourceData, jsonata, responseSchema, }) => {
15
+ let parsedSourceData = null;
16
+ let parsedResponseSchema = null;
17
+ try {
18
+ parsedSourceData = sourceData ? JSON.parse(sourceData) : null;
19
+ parsedResponseSchema = responseSchema ? JSON.parse(responseSchema) : null;
20
+ }
21
+ catch (parseError) {
22
+ // input data is not valid JSON, return success
23
+ return {
24
+ success: true,
25
+ data: null,
26
+ feedback: `JSON parsing failed: ${parseError.message}`,
27
+ };
28
+ }
29
+ const transformation = await applyJsonataWithValidation(parsedSourceData, jsonata, parsedResponseSchema);
30
+ // if transformation is successful, return success
31
+ if (transformation.success) {
32
+ return {
33
+ success: true,
34
+ data: transformation.data,
35
+ };
36
+ }
37
+ return {
38
+ success: transformation.success,
39
+ data: transformation.data,
40
+ feedback: transformation.error,
41
+ };
42
+ },
43
+ includeInputInOutput: true,
44
+ });
45
+ export default reviewer;
@@ -0,0 +1,18 @@
1
+ import { type ChatModel } from "@aigne/core";
2
+ export interface TransformInput {
3
+ responseSchema: string;
4
+ responseSampleData?: string;
5
+ sourceData?: string;
6
+ sourceSchema?: string;
7
+ instruction?: string;
8
+ [key: string]: unknown;
9
+ }
10
+ export declare function generateMapping({ input, model, }: {
11
+ input: TransformInput;
12
+ model: ChatModel;
13
+ }): Promise<{
14
+ jsonata: string;
15
+ confidence: number;
16
+ confidenceReasoning: string;
17
+ } | null>;
18
+ export { applyJsonata } from "./tools.js";
@@ -0,0 +1,21 @@
1
+ import { AIGNE, UserInputTopic, UserOutputTopic } from "@aigne/core";
2
+ import mapper from "./agents/mapper.js";
3
+ import reviewer from "./agents/reviewer.js";
4
+ import { getSchemaFromData } from "./tools.js";
5
+ export async function generateMapping({ input, model, }) {
6
+ if (!model)
7
+ throw new Error("model is required to run data mapper");
8
+ // if sourceSchema is not provided, generate it from sourceData
9
+ if (!input.sourceSchema && input.sourceData) {
10
+ input.sourceSchema = JSON.stringify(getSchemaFromData(JSON.parse(input.sourceData)));
11
+ }
12
+ const aigne = new AIGNE({ model, agents: [mapper, reviewer] });
13
+ aigne.publish(UserInputTopic, input);
14
+ const { message } = await aigne.subscribe(UserOutputTopic);
15
+ return {
16
+ jsonata: message.jsonata || "",
17
+ confidence: message.confidence || 0,
18
+ confidenceReasoning: message.confidenceReasoning || "",
19
+ };
20
+ }
21
+ export { applyJsonata } from "./tools.js";
@@ -0,0 +1 @@
1
+ export declare const PROMPT_MAPPING = "You are an AI that generates JSONata mapping expressions to transform source data structures into target structures.\n\nGuidelines for creating JSONata mappings:\n\n1. Source References:\n - Use exact field paths from the source data, e.g. $.merchant_category\n - For accessing fields with names containing spaces, use backticks, e.g. $.`merchant category`\n - Jsonata will automatically extract all the fields from the current context. E.g. if you need all variants from all products, you can use $.products.variants. No need to do nested map reduce operations.\n - $. The variable with no name refers to the context value at any point in the input JSON hierarchy. E.g. if the current context is products.price, then $.currency is products.price.currency\n - %. The parent of the current context value. E.g. if the current context is products.variants.size and you want variant name, use %.name\n\n - When multiple source fields could map to a target, use a maximum of 3 fallbacks:\n GOOD: source1 ? source1 : source2 ? source2 : source3 ? source3 : 'default'\n BAD: source1 ? source1 : source1 ? source1 : source1 (repeated fields)\n\n2. Expression Rules:\n - Avoid unnecessary array/string operations\n - Each mapping should be clear and concise\n - Use proper JSONata syntax for coalesce operations\n - Do not use ~> to execute functions. Use the functions directly with the correct arguments or use $map(arr, $function) to apply a function to each element of an array.\n\n3. Array Handling:\n - For mapping to an array of objects, use the following patterns:\n a) When in array scope, use $.{} to map each object:\n Correct: [$.{\"id\": id, \"name\": name}]\n Incorrect: [{\"id\": $.id}]\n b) When outside array scope, include the source path:\n Correct: [$.items.{\"id\": id, \"name\": name}]\n Incorrect: [{\"id\": $.items.id}]\n c) For nested arrays, chain the array operators:\n Correct: [products.variants.{\"size\": size, \"color\": color}]\n Incorrect: [products.[{\"size\": variants.size}]]\n d) You need to use the square brackets [] to map to an array of objects, otherwise it might return an object and fail the validation.\n Correct: variants: [variants.{\"size\": size, \"color\": color}]\n Incorrect: variants: variants.{\"size\": variants.size}\n - For array elements, use JSONata array operators like [0] for first element, [-1] for last element\n - Square bracket notation [] can be used with predicates, e.g. items[type='book']\n\n4. Field Selection Priority:\n - Prefer variant-specific fields over general fields (e.g., sizeVariants.description over sizes)\n - Choose the most specific/detailed field available (e.g., type=\"shelf\" over category=\"furniture\")\n\n5. Filters:\n - Pay special attention to filter statements in the instruction and the schema description. Add them to the generated jsonata expression.\n Example: Get me all products with SKU 0406654608 or products: {\"type\": \"array\", description: \"only products with SKU 0406654608\"}\n Generated jsonata expression: Account.Order.Product[SKU = \"0406654608\"].{\"Description\": Description}\n - For filtering with arrays, you can use the \"in\" operator. E.g. library.books[\"Steven King\" in authors]\n\n6. Data Integrity:\n - ONLY use fields that exist in the source data structure\n - If no matching source field exists, leave the field undefined\n - Never invent or assume the existence of fields not present in the source data\n\n7. Function Calls:\n - You may use the following functions if prompted:\n $string(arg) - Converts argument to string\n $length(str) - Returns string length\n $substring(str, start[, length]) - Extracts substring\n $substringBefore(str, chars) - Gets substring before specified chars\n $substringAfter(str, chars) - Gets substring after specified chars\n $uppercase(str) - Converts to uppercase\n $lowercase(str) - Converts to lowercase\n $trim(str) - Removes whitespace from both ends\n $pad(str, width[, char]) - Pads string to specified width\n $contains(str, substring) - Tests if string contains substring\n $toMillis(timestamp [, picture]) - Converts ISO 8601 timestamp to milliseconds. E.g. $toMillis(\"2017-11-07T15:07:54.972Z\") => 1510067274972\n $toDate(str | number) - Converts any timestamp string to valid ISO 8601 date string. E.g. $toDate(\"Oct 15, 2024 12:00:00 AM UTC\") => \"2024-10-15T00:00:00.000Z\", $toDate(1728873600000) => \"2024-10-15T00:00:00.000Z\"\n $dateMax(arr) - Returns the maximum date of an array of dates. E.g. $dateMax([\"2017-11-07T15:07:54.972Z\", \"Oct 15, 2012 12:00:00 AM UTC\"]) returns \"2017-11-07T15:07:54.972Z\".\n $dateMin(arr) - Returns the minimum date of an array of dates. E.g. $dateMin($.variants.created_at) returns the minimum created_at date of all variants.\n $dateDiff(date1, date2, unit: \"seconds\" | \"minutes\" | \"hours\" | \"days\") - Returns the difference between two dates in the specified unit. E.g. $dateDiff($.order.created_at, $.order.updated_at, \"days\") returns the number of days between the order created_at and updated_at.\n $now([picture [, timezone]]) - Returns current date and time in ISO 8601 format. E.g. $now() => \"2017-05-15T15:12:59.152Z\"\n $split(str[, separator][, limit]) - Splits string into array\n $join(array[, separator]) - Joins array elements into string\n $match(str, pattern[, limit]) - Returns array of regex matches\n $replace(str, pattern, replacement) - Replaces all occurrences of pattern. E.g. $replace(\"abracadabra\", /a.*?a/, \"*\") returns \"ab*ad*bra\". $replace(\"John Smith\", \"John\", \"Marc\") returns Marc Smith.\n $number(arg) - Converts an argument to a number.\n $min(arr) - Returns minimum number of a number array. E.g. $min($map($.variants.price, $number)) returns the minimum price of all variants.\n $max(arr) - Returns maximum number of a number array. E.g. $max($map($.variants.price, $number)) returns the maximum price of all variants.\n $count(array) - Returns array length\n $sort(array[, function]) - Sorts array\n $distinct(array) - Removes duplicates\n $map(array, function) - Applies function to each element\n $filter(array, function) - Filters array based on predicate\n\n- Error handling:\n - If you get an error like \"is not of a type(s) string/number/object\", try to convert the source field, but also consider that the original field or one of its parent might be null. In this case, add a default value.\n - If the error is something like \"instance is not of a type(s) object\", make sure you REALLY create the target schema with the correct type.\n - If the error is something like \"instance is not of a type(s) array or array/null\". In this case, wrap the source selector in an array to ensure it always returns an array. E.g. \"result\": [$.items]\n - if an object is optional but its fields required, you can add a test and default to {}, but do not set the inner fields to default null.\n\nRemember: The goal is to create valid JSONata expressions that accurately transform the source data structure into the required target structure.";
@@ -0,0 +1,91 @@
1
+ export const PROMPT_MAPPING = `You are an AI that generates JSONata mapping expressions to transform source data structures into target structures.
2
+
3
+ Guidelines for creating JSONata mappings:
4
+
5
+ 1. Source References:
6
+ - Use exact field paths from the source data, e.g. $.merchant_category
7
+ - For accessing fields with names containing spaces, use backticks, e.g. $.\`merchant category\`
8
+ - Jsonata will automatically extract all the fields from the current context. E.g. if you need all variants from all products, you can use $.products.variants. No need to do nested map reduce operations.
9
+ - $. The variable with no name refers to the context value at any point in the input JSON hierarchy. E.g. if the current context is products.price, then $.currency is products.price.currency
10
+ - %. The parent of the current context value. E.g. if the current context is products.variants.size and you want variant name, use %.name
11
+
12
+ - When multiple source fields could map to a target, use a maximum of 3 fallbacks:
13
+ GOOD: source1 ? source1 : source2 ? source2 : source3 ? source3 : 'default'
14
+ BAD: source1 ? source1 : source1 ? source1 : source1 (repeated fields)
15
+
16
+ 2. Expression Rules:
17
+ - Avoid unnecessary array/string operations
18
+ - Each mapping should be clear and concise
19
+ - Use proper JSONata syntax for coalesce operations
20
+ - Do not use ~> to execute functions. Use the functions directly with the correct arguments or use $map(arr, $function) to apply a function to each element of an array.
21
+
22
+ 3. Array Handling:
23
+ - For mapping to an array of objects, use the following patterns:
24
+ a) When in array scope, use $.{} to map each object:
25
+ Correct: [$.{"id": id, "name": name}]
26
+ Incorrect: [{"id": $.id}]
27
+ b) When outside array scope, include the source path:
28
+ Correct: [$.items.{"id": id, "name": name}]
29
+ Incorrect: [{"id": $.items.id}]
30
+ c) For nested arrays, chain the array operators:
31
+ Correct: [products.variants.{"size": size, "color": color}]
32
+ Incorrect: [products.[{"size": variants.size}]]
33
+ d) You need to use the square brackets [] to map to an array of objects, otherwise it might return an object and fail the validation.
34
+ Correct: variants: [variants.{"size": size, "color": color}]
35
+ Incorrect: variants: variants.{"size": variants.size}
36
+ - For array elements, use JSONata array operators like [0] for first element, [-1] for last element
37
+ - Square bracket notation [] can be used with predicates, e.g. items[type='book']
38
+
39
+ 4. Field Selection Priority:
40
+ - Prefer variant-specific fields over general fields (e.g., sizeVariants.description over sizes)
41
+ - Choose the most specific/detailed field available (e.g., type="shelf" over category="furniture")
42
+
43
+ 5. Filters:
44
+ - Pay special attention to filter statements in the instruction and the schema description. Add them to the generated jsonata expression.
45
+ Example: Get me all products with SKU 0406654608 or products: {"type": "array", description: "only products with SKU 0406654608"}
46
+ Generated jsonata expression: Account.Order.Product[SKU = "0406654608"].{"Description": Description}
47
+ - For filtering with arrays, you can use the "in" operator. E.g. library.books["Steven King" in authors]
48
+
49
+ 6. Data Integrity:
50
+ - ONLY use fields that exist in the source data structure
51
+ - If no matching source field exists, leave the field undefined
52
+ - Never invent or assume the existence of fields not present in the source data
53
+
54
+ 7. Function Calls:
55
+ - You may use the following functions if prompted:
56
+ $string(arg) - Converts argument to string
57
+ $length(str) - Returns string length
58
+ $substring(str, start[, length]) - Extracts substring
59
+ $substringBefore(str, chars) - Gets substring before specified chars
60
+ $substringAfter(str, chars) - Gets substring after specified chars
61
+ $uppercase(str) - Converts to uppercase
62
+ $lowercase(str) - Converts to lowercase
63
+ $trim(str) - Removes whitespace from both ends
64
+ $pad(str, width[, char]) - Pads string to specified width
65
+ $contains(str, substring) - Tests if string contains substring
66
+ $toMillis(timestamp [, picture]) - Converts ISO 8601 timestamp to milliseconds. E.g. $toMillis("2017-11-07T15:07:54.972Z") => 1510067274972
67
+ $toDate(str | number) - Converts any timestamp string to valid ISO 8601 date string. E.g. $toDate("Oct 15, 2024 12:00:00 AM UTC") => "2024-10-15T00:00:00.000Z", $toDate(1728873600000) => "2024-10-15T00:00:00.000Z"
68
+ $dateMax(arr) - Returns the maximum date of an array of dates. E.g. $dateMax(["2017-11-07T15:07:54.972Z", "Oct 15, 2012 12:00:00 AM UTC"]) returns "2017-11-07T15:07:54.972Z".
69
+ $dateMin(arr) - Returns the minimum date of an array of dates. E.g. $dateMin($.variants.created_at) returns the minimum created_at date of all variants.
70
+ $dateDiff(date1, date2, unit: "seconds" | "minutes" | "hours" | "days") - Returns the difference between two dates in the specified unit. E.g. $dateDiff($.order.created_at, $.order.updated_at, "days") returns the number of days between the order created_at and updated_at.
71
+ $now([picture [, timezone]]) - Returns current date and time in ISO 8601 format. E.g. $now() => "2017-05-15T15:12:59.152Z"
72
+ $split(str[, separator][, limit]) - Splits string into array
73
+ $join(array[, separator]) - Joins array elements into string
74
+ $match(str, pattern[, limit]) - Returns array of regex matches
75
+ $replace(str, pattern, replacement) - Replaces all occurrences of pattern. E.g. $replace("abracadabra", /a.*?a/, "*") returns "ab*ad*bra". $replace("John Smith", "John", "Marc") returns Marc Smith.
76
+ $number(arg) - Converts an argument to a number.
77
+ $min(arr) - Returns minimum number of a number array. E.g. $min($map($.variants.price, $number)) returns the minimum price of all variants.
78
+ $max(arr) - Returns maximum number of a number array. E.g. $max($map($.variants.price, $number)) returns the maximum price of all variants.
79
+ $count(array) - Returns array length
80
+ $sort(array[, function]) - Sorts array
81
+ $distinct(array) - Removes duplicates
82
+ $map(array, function) - Applies function to each element
83
+ $filter(array, function) - Filters array based on predicate
84
+
85
+ - Error handling:
86
+ - If you get an error like "is not of a type(s) string/number/object", try to convert the source field, but also consider that the original field or one of its parent might be null. In this case, add a default value.
87
+ - If the error is something like "instance is not of a type(s) object", make sure you REALLY create the target schema with the correct type.
88
+ - If the error is something like "instance is not of a type(s) array or array/null". In this case, wrap the source selector in an array to ensure it always returns an array. E.g. "result": [$.items]
89
+ - if an object is optional but its fields required, you can add a test and default to {}, but do not set the inner fields to default null.
90
+
91
+ Remember: The goal is to create valid JSONata expressions that accurately transform the source data structure into the required target structure.`;
@@ -0,0 +1,17 @@
1
+ import jsonata from "jsonata";
2
+ import type { Schema } from "jsonschema";
3
+ export interface TransformResult {
4
+ success: boolean;
5
+ data?: unknown;
6
+ error?: string;
7
+ }
8
+ /**
9
+ * Extract JSON Schema from data
10
+ * @param data Any data
11
+ * @returns JSON Schema or null
12
+ */
13
+ export declare function getSchemaFromData(data: unknown): unknown;
14
+ export declare function applyJsonataWithValidation(data: unknown, expr: string, schema: unknown): Promise<TransformResult>;
15
+ export declare function applyJsonata(data: unknown, expr: string): Promise<unknown>;
16
+ export declare function extendJsonata(expr: string): jsonata.Expression;
17
+ export declare function addNullableToOptional(schema: Schema): Schema;
@@ -0,0 +1,167 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import jsonata from "jsonata";
3
+ import { Validator } from "jsonschema";
4
+ import toJsonSchema from "to-json-schema";
5
+ /**
6
+ * Extract JSON Schema from data
7
+ * @param data Any data
8
+ * @returns JSON Schema or null
9
+ */
10
+ export function getSchemaFromData(data) {
11
+ if (!data)
12
+ return null;
13
+ return toJsonSchema(data);
14
+ }
15
+ export async function applyJsonataWithValidation(data, expr, schema) {
16
+ try {
17
+ const result = await applyJsonata(data, expr);
18
+ if (result === null ||
19
+ result === undefined ||
20
+ (Array.isArray(result) && result.length === 0) ||
21
+ (typeof result === "object" && Object.keys(result).length === 0)) {
22
+ return { success: false, error: "Result is empty" };
23
+ }
24
+ const validator = new Validator();
25
+ const optionalSchema = addNullableToOptional(schema);
26
+ const validation = validator.validate(result, optionalSchema);
27
+ if (!validation.valid) {
28
+ return {
29
+ success: false,
30
+ error: validation.errors
31
+ .map((e) => `${e.stack}. Source: ${e.instance ? JSON.stringify(e.instance) : "undefined"}`)
32
+ .join("\n")
33
+ .slice(0, 5000),
34
+ };
35
+ }
36
+ return { success: true, data: result };
37
+ }
38
+ catch (error) {
39
+ return { success: false, error: `Validation failed: ${error.message}` };
40
+ }
41
+ }
42
+ export async function applyJsonata(data, expr) {
43
+ try {
44
+ const expression = extendJsonata(expr);
45
+ const result = await expression.evaluate(data);
46
+ return result;
47
+ }
48
+ catch (error) {
49
+ throw new Error(`JSONata evaluation failed for expression "${expr}": ${error.message}`);
50
+ }
51
+ }
52
+ export function extendJsonata(expr) {
53
+ const expression = jsonata(expr);
54
+ expression.registerFunction("max", (arr) => {
55
+ if (Array.isArray(arr)) {
56
+ return Math.max(...arr.map(Number).filter((n) => !Number.isNaN(n)));
57
+ }
58
+ return arr;
59
+ });
60
+ expression.registerFunction("min", (arr) => {
61
+ if (Array.isArray(arr)) {
62
+ return Math.min(...arr.map(Number).filter((n) => !Number.isNaN(n)));
63
+ }
64
+ return arr;
65
+ });
66
+ expression.registerFunction("number", (value) => Number.parseFloat(value));
67
+ expression.registerFunction("substring", (str, start, end) => String(str).substring(start, end));
68
+ expression.registerFunction("replace", (obj, pattern, replacement) => {
69
+ if (Array.isArray(obj)) {
70
+ return obj.map((item) => String(item).replace(pattern, replacement));
71
+ }
72
+ if (typeof obj === "object") {
73
+ return Object.fromEntries(Object.entries(obj || {}).map(([key, value]) => [
74
+ key,
75
+ String(value).replace(pattern, replacement),
76
+ ]));
77
+ }
78
+ return String(obj).replace(pattern, replacement);
79
+ });
80
+ expression.registerFunction("toDate", (date) => {
81
+ try {
82
+ // Handle numeric timestamps (milliseconds or seconds)
83
+ if (typeof date === "number" || /^\d+$/.test(date)) {
84
+ const timestamp = typeof date === "number" ? date : Number.parseInt(date, 10);
85
+ // If timestamp is in seconds (typically 10 digits), convert to milliseconds
86
+ const millisTimestamp = timestamp < 10000000000 ? timestamp * 1000 : timestamp;
87
+ return new Date(millisTimestamp).toISOString();
88
+ }
89
+ // Handle date strings in MM/DD/YYYY format
90
+ const match = String(date).match(/^(\d{2})\/(\d{2})\/(\d{4})(?:\s+(\d{2}):(\d{2}):(\d{2}))?$/);
91
+ if (match) {
92
+ // eslint-disable-next-line @typescript-eslint/naming-convention
93
+ const [_, month, day, year, hours = "00", minutes = "00", seconds = "00"] = match;
94
+ const isoDate = `${year}-${month}-${day}T${hours}:${minutes}:${seconds}.000Z`;
95
+ return new Date(isoDate).toISOString();
96
+ }
97
+ // Default case: try standard Date parsing
98
+ return new Date(date).toISOString();
99
+ }
100
+ catch (e) {
101
+ throw new Error(`Invalid date: ${e.message}`);
102
+ }
103
+ });
104
+ expression.registerFunction("dateMax", (dates) => dates.reduce((max, curr) => (new Date(max) > new Date(curr) ? max : curr)));
105
+ expression.registerFunction("dateMin", (dates) => dates.reduce((min, curr) => (new Date(min) < new Date(curr) ? min : curr)));
106
+ expression.registerFunction("dateDiff", (date1, date2, unit = "days") => {
107
+ const d1 = new Date(date1);
108
+ const d2 = new Date(date2);
109
+ const diff = Math.abs(d1.getTime() - d2.getTime());
110
+ switch (unit.toLowerCase()) {
111
+ case "seconds":
112
+ return Math.floor(diff / 1000);
113
+ case "minutes":
114
+ return Math.floor(diff / (1000 * 60));
115
+ case "hours":
116
+ return Math.floor(diff / (1000 * 60 * 60));
117
+ case "days":
118
+ return Math.floor(diff / (1000 * 60 * 60 * 24));
119
+ default:
120
+ return diff; // milliseconds
121
+ }
122
+ });
123
+ return expression;
124
+ }
125
+ export function addNullableToOptional(schema) {
126
+ if (!schema || typeof schema !== "object")
127
+ return schema;
128
+ const newSchema = { ...schema };
129
+ if (schema.type === "object" && schema.properties) {
130
+ const required = new Set(Array.isArray(schema.required) ? schema.required : []);
131
+ newSchema.properties = Object.entries(schema.properties).reduce((acc, [key, value]) => ({
132
+ // biome-ignore lint/performance/noAccumulatingSpread: <explanation>
133
+ ...acc,
134
+ [key]: !required.has(key) ? makeNullable(value) : addNullableToOptional(value),
135
+ }), {});
136
+ }
137
+ if (schema.type === "array" && schema.items) {
138
+ newSchema.items = addNullableToOptional(schema.items);
139
+ }
140
+ return newSchema;
141
+ }
142
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
143
+ function makeNullable(schema) {
144
+ if (!schema || typeof schema !== "object")
145
+ return schema;
146
+ const newSchema = { ...schema };
147
+ if (Array.isArray(schema.type)) {
148
+ if (!schema.type.includes("null")) {
149
+ newSchema.type = [...schema.type, "null"];
150
+ }
151
+ }
152
+ else if (schema.type) {
153
+ newSchema.type = [schema.type, "null"];
154
+ }
155
+ // Recursively process nested properties
156
+ if (schema.properties) {
157
+ newSchema.properties = Object.entries(schema.properties).reduce((acc, [key, value]) => ({
158
+ // biome-ignore lint/performance/noAccumulatingSpread: <explanation>
159
+ ...acc,
160
+ [key]: makeNullable(value),
161
+ }), {});
162
+ }
163
+ if (schema.items) {
164
+ newSchema.items = makeNullable(schema.items);
165
+ }
166
+ return newSchema;
167
+ }
@@ -0,0 +1,23 @@
1
+ import type { AgentInvokeOptions, Context, Memory } from "@aigne/core";
2
+ import type { PromiseOrValue } from "@aigne/core/utils/type-utils.js";
3
+ import { MemoryStorage } from "../storage.js";
4
+ export interface DefaultMemoryStorageOptions {
5
+ url?: string;
6
+ getSessionId?: (context: Context) => PromiseOrValue<string>;
7
+ }
8
+ export declare class DefaultMemoryStorage extends MemoryStorage {
9
+ options?: DefaultMemoryStorageOptions | undefined;
10
+ constructor(options?: DefaultMemoryStorageOptions | undefined);
11
+ private _db;
12
+ private initSqlite;
13
+ get db(): Promise<import("drizzle-orm/libsql").LibSQLDatabase<Record<string, never>>>;
14
+ private convertMemory;
15
+ search(query: {
16
+ limit?: number;
17
+ }, { context }: AgentInvokeOptions): Promise<{
18
+ result: Memory[];
19
+ }>;
20
+ create(memory: Pick<Memory, "content">, { context }: AgentInvokeOptions): Promise<{
21
+ result: Memory;
22
+ }>;
23
+ }
@@ -0,0 +1,61 @@
1
+ import { initDatabase } from "@aigne/sqlite";
2
+ import { desc, eq, isNull } from "drizzle-orm";
3
+ import { MemoryStorage } from "../storage.js";
4
+ import { migrate } from "./migrate.js";
5
+ import { Memories } from "./models/memory.js";
6
+ const DEFAULT_MAX_MEMORY_COUNT = 10;
7
+ export class DefaultMemoryStorage extends MemoryStorage {
8
+ options;
9
+ constructor(options) {
10
+ super();
11
+ this.options = options;
12
+ }
13
+ _db;
14
+ async initSqlite() {
15
+ const db = await initDatabase({ url: this.options?.url });
16
+ await migrate(db);
17
+ return db;
18
+ }
19
+ get db() {
20
+ this._db ??= this.initSqlite();
21
+ return this._db;
22
+ }
23
+ convertMemory(m) {
24
+ return {
25
+ id: m.id,
26
+ sessionId: m.sessionId,
27
+ content: m.content,
28
+ createdAt: m.createdAt.toISOString(),
29
+ };
30
+ }
31
+ async search(query, { context }) {
32
+ const { limit = DEFAULT_MAX_MEMORY_COUNT } = query;
33
+ const sessionId = (await this.options?.getSessionId?.(context)) ?? null;
34
+ const db = await this.db;
35
+ const memories = await db
36
+ .select()
37
+ .from(Memories)
38
+ .where(sessionId ? eq(Memories.sessionId, sessionId) : isNull(Memories.sessionId))
39
+ .orderBy(desc(Memories.id))
40
+ .limit(limit)
41
+ .execute();
42
+ return {
43
+ result: memories.reverse().map(this.convertMemory),
44
+ };
45
+ }
46
+ async create(memory, { context }) {
47
+ const sessionId = (await this.options?.getSessionId?.(context)) ?? null;
48
+ const db = await this.db;
49
+ const [result] = await db
50
+ .insert(Memories)
51
+ .values({
52
+ ...memory,
53
+ sessionId,
54
+ })
55
+ .returning()
56
+ .execute();
57
+ if (!result)
58
+ throw new Error("Failed to create memory");
59
+ return { result: this.convertMemory(result) };
60
+ }
61
+ }
@@ -0,0 +1,3 @@
1
+ import type { LibSQLDatabase } from "drizzle-orm/libsql";
2
+ import type { SqliteRemoteDatabase } from "drizzle-orm/sqlite-proxy";
3
+ export declare function migrate(db: LibSQLDatabase | SqliteRemoteDatabase): Promise<void>;
@@ -0,0 +1,26 @@
1
+ import { sql } from "drizzle-orm/sql";
2
+ import init from "./migrations/001-init.js";
3
+ export async function migrate(db) {
4
+ const migrations = [init];
5
+ const migrationsTable = "__drizzle_migrations";
6
+ const migrationTableCreate = sql `
7
+ CREATE TABLE IF NOT EXISTS ${sql.identifier(migrationsTable)} (
8
+ id SERIAL PRIMARY KEY,
9
+ hash text NOT NULL
10
+ )
11
+ `;
12
+ await db.run(migrationTableCreate).execute();
13
+ const dbMigrations = await db
14
+ .values(sql `SELECT id, hash FROM ${sql.identifier(migrationsTable)} ORDER BY id DESC LIMIT 1`)
15
+ .execute();
16
+ const lastDbMigration = dbMigrations[0];
17
+ const queriesToRun = [];
18
+ for (const migration of migrations) {
19
+ if (!lastDbMigration || lastDbMigration[1] < migration.hash) {
20
+ queriesToRun.push(...migration.sql, `INSERT INTO \`${migrationsTable}\` ("hash") VALUES('${migration.hash}')`);
21
+ }
22
+ }
23
+ for (const query of queriesToRun) {
24
+ await db.run(query).execute();
25
+ }
26
+ }
@@ -0,0 +1,5 @@
1
+ declare const _default: {
2
+ hash: string;
3
+ sql: string[];
4
+ };
5
+ export default _default;
@@ -0,0 +1,14 @@
1
+ export default {
2
+ hash: "001-init",
3
+ sql: [
4
+ `\
5
+ CREATE TABLE "Memories" (
6
+ "id" TEXT NOT NULL PRIMARY KEY,
7
+ "createdAt" DATETIME NOT NULL,
8
+ "updatedAt" DATETIME NOT NULL,
9
+ "sessionId" TEXT,
10
+ "content" JSON NOT NULL
11
+ )
12
+ `,
13
+ ],
14
+ };
@@ -0,0 +1,102 @@
1
+ export declare const Memories: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
2
+ name: "Memories";
3
+ schema: undefined;
4
+ columns: {
5
+ id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
6
+ name: "id";
7
+ tableName: "Memories";
8
+ dataType: "string";
9
+ columnType: "SQLiteText";
10
+ data: string;
11
+ driverParam: string;
12
+ notNull: true;
13
+ hasDefault: true;
14
+ isPrimaryKey: true;
15
+ isAutoincrement: false;
16
+ hasRuntimeDefault: true;
17
+ enumValues: [string, ...string[]];
18
+ baseColumn: never;
19
+ identity: undefined;
20
+ generated: undefined;
21
+ }, {}, {
22
+ length: number | undefined;
23
+ }>;
24
+ createdAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
25
+ name: "createdAt";
26
+ tableName: "Memories";
27
+ dataType: "custom";
28
+ columnType: "SQLiteCustomColumn";
29
+ data: Date;
30
+ driverParam: string;
31
+ notNull: true;
32
+ hasDefault: true;
33
+ isPrimaryKey: false;
34
+ isAutoincrement: false;
35
+ hasRuntimeDefault: true;
36
+ enumValues: undefined;
37
+ baseColumn: never;
38
+ identity: undefined;
39
+ generated: undefined;
40
+ }, {}, {
41
+ sqliteColumnBuilderBrand: "SQLiteCustomColumnBuilderBrand";
42
+ }>;
43
+ updatedAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
44
+ name: "updatedAt";
45
+ tableName: "Memories";
46
+ dataType: "custom";
47
+ columnType: "SQLiteCustomColumn";
48
+ data: Date;
49
+ driverParam: string;
50
+ notNull: true;
51
+ hasDefault: true;
52
+ isPrimaryKey: false;
53
+ isAutoincrement: false;
54
+ hasRuntimeDefault: true;
55
+ enumValues: undefined;
56
+ baseColumn: never;
57
+ identity: undefined;
58
+ generated: undefined;
59
+ }, {}, {
60
+ sqliteColumnBuilderBrand: "SQLiteCustomColumnBuilderBrand";
61
+ }>;
62
+ sessionId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
63
+ name: "sessionId";
64
+ tableName: "Memories";
65
+ dataType: "string";
66
+ columnType: "SQLiteText";
67
+ data: string;
68
+ driverParam: string;
69
+ notNull: false;
70
+ hasDefault: false;
71
+ isPrimaryKey: false;
72
+ isAutoincrement: false;
73
+ hasRuntimeDefault: false;
74
+ enumValues: [string, ...string[]];
75
+ baseColumn: never;
76
+ identity: undefined;
77
+ generated: undefined;
78
+ }, {}, {
79
+ length: number | undefined;
80
+ }>;
81
+ content: import("drizzle-orm/sqlite-core").SQLiteColumn<{
82
+ name: "content";
83
+ tableName: "Memories";
84
+ dataType: "custom";
85
+ columnType: "SQLiteCustomColumn";
86
+ data: unknown;
87
+ driverParam: string;
88
+ notNull: true;
89
+ hasDefault: false;
90
+ isPrimaryKey: false;
91
+ isAutoincrement: false;
92
+ hasRuntimeDefault: false;
93
+ enumValues: undefined;
94
+ baseColumn: never;
95
+ identity: undefined;
96
+ generated: undefined;
97
+ }, {}, {
98
+ sqliteColumnBuilderBrand: "SQLiteCustomColumnBuilderBrand";
99
+ }>;
100
+ };
101
+ dialect: "sqlite";
102
+ }>;
@@ -0,0 +1,18 @@
1
+ import { datetime, json } from "@aigne/sqlite/type.js";
2
+ import { sqliteTable, text } from "drizzle-orm/sqlite-core";
3
+ import { v7 } from "uuid";
4
+ export const Memories = sqliteTable("Memories", {
5
+ id: text("id")
6
+ .notNull()
7
+ .primaryKey()
8
+ .$defaultFn(() => v7()),
9
+ createdAt: datetime("createdAt")
10
+ .notNull()
11
+ .$defaultFn(() => new Date()),
12
+ updatedAt: datetime("updatedAt")
13
+ .notNull()
14
+ .$defaultFn(() => new Date())
15
+ .$onUpdateFn(() => new Date()),
16
+ sessionId: text("sessionId"),
17
+ content: json("content").notNull(),
18
+ });
@@ -0,0 +1,10 @@
1
+ import { MemoryAgent, type MemoryAgentOptions } from "@aigne/core";
2
+ import { type DefaultMemoryStorageOptions } from "./default-memory-storage/index.js";
3
+ import { MemoryStorage } from "./storage.js";
4
+ export interface DefaultMemoryOptions extends Partial<MemoryAgentOptions> {
5
+ storage?: MemoryStorage | DefaultMemoryStorageOptions;
6
+ }
7
+ export declare class DefaultMemory extends MemoryAgent {
8
+ constructor(options?: DefaultMemoryOptions);
9
+ storage: MemoryStorage;
10
+ }