From 64041e192651d2fe919962fe6153e63e57077f76 Mon Sep 17 00:00:00 2001 From: Matelz <49626198+Matelz@users.noreply.github.com> Date: Tue, 6 May 2025 23:22:23 -0300 Subject: [PATCH 01/12] feature: added basic functions for testing purposes --- .env | 3 +- deno.json | 7 +- deno.lock | 213 +++++++++++++++++++++++++++++++++++++++++++++++- src/lc/model.ts | 81 ++++++++++++++++++ 4 files changed, 300 insertions(+), 4 deletions(-) create mode 100644 src/lc/model.ts diff --git a/.env b/.env index 82e6a29..de39eda 100644 --- a/.env +++ b/.env @@ -1 +1,2 @@ -DATABASE_URL=postgresql://user:password@localhost/dbname \ No newline at end of file +DATABASE_URL=postgresql://user:password@localhost/dbname +MISTRAL_API_KEY=Yn7p3GEHvjZ1HV0nLKjw5hNaUYfJn5Oi \ No newline at end of file diff --git a/deno.json b/deno.json index e15b4b3..dccbf4f 100644 --- a/deno.json +++ b/deno.json @@ -3,8 +3,13 @@ "dev": "deno run --allow-net ./src/main.ts" }, "imports": { + "@langchain/core": "npm:@langchain/core@^0.3.53", + "@langchain/langgraph": "npm:@langchain/langgraph@^0.2.68", "@types/express": "npm:@types/express@^5.0.1", "drizzle-kit": "npm:drizzle-kit@^0.31.0", - "express": "npm:express@^5.1.0" + "express": "npm:express@^5.1.0", + "@langchain/mistralai": "npm:@langchain/mistralai@^0.2.0", + "uuid": "npm:uuid@^11.1.0", + "zod": "npm:zod@^3.24.4" } } \ No newline at end of file diff --git a/deno.lock b/deno.lock index 183c574..0a24110 100644 --- a/deno.lock +++ b/deno.lock @@ -1,11 +1,19 @@ { "version": "4", "specifiers": { + "npm:@langchain/core@~0.3.53": "0.3.53_zod@3.24.3", + "npm:@langchain/langgraph@~0.2.68": "0.2.68_@langchain+core@0.3.53__zod@3.24.3", + "npm:@langchain/mistralai@0.2": "0.2.0_@langchain+core@0.3.53__zod@3.24.3_zod@3.24.3", "npm:@types/express@^5.0.1": "5.0.1", "npm:drizzle-kit@0.31": "0.31.0_esbuild@0.25.3", - "npm:express@^5.1.0": "5.1.0" + "npm:express@^5.1.0": "5.1.0", + "npm:uuid@^11.1.0": "11.1.0", + "npm:zod@^3.24.4": "3.24.4" }, "npm": { + "@cfworker/json-schema@4.1.1": { + "integrity": "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==" + }, "@drizzle-team/brocli@0.10.2": { "integrity": "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==" }, @@ -164,6 +172,67 @@ "@esbuild/win32-x64@0.25.3": { "integrity": "sha512-ICgUR+kPimx0vvRzf+N/7L7tVSQeE3BYY+NhHRHXS1kBuPO7z2+7ea2HbhDyZdTephgvNvKrlDDKUexuCVBVvg==" }, + "@langchain/core@0.3.53_zod@3.24.3": { + "integrity": "sha512-rHlBcEG5PNaWxlVhPTLiZ0WRCr/URNEUynhgZTZ8QbTJhQ1vEMibdr2YL9LYKHSXNyAp/b5j3itcu3epB8FD7Q==", + "dependencies": [ + "@cfworker/json-schema", + "ansi-styles@5.2.0", + "camelcase", + "decamelize", + "js-tiktoken", + "langsmith", + "mustache", + "p-queue", + "p-retry", + "uuid@10.0.0", + "zod-to-json-schema", + "zod@3.24.3" + ] + }, + "@langchain/langgraph-checkpoint@0.0.17_@langchain+core@0.3.53__zod@3.24.3": { + "integrity": "sha512-6b3CuVVYx+7x0uWLG+7YXz9j2iBa+tn2AXvkLxzEvaAsLE6Sij++8PPbS2BZzC+S/FPJdWsz6I5bsrqL0BYrCA==", + "dependencies": [ + "@langchain/core", + "uuid@10.0.0" + ] + }, + "@langchain/langgraph-sdk@0.0.74_@langchain+core@0.3.53__zod@3.24.3": { + "integrity": "sha512-IUN0m4BYkGWdviFd4EaWDcQgxNq8z+1LIwXajCSt9B+Cb/pz0ZNpIPdu5hAIsf6a0RWu5yRUhzL1L40t7vu3Zg==", + "dependencies": [ + "@langchain/core", + "@types/json-schema", + "p-queue", + "p-retry", + "uuid@9.0.1" + ] + }, + "@langchain/langgraph@0.2.68_@langchain+core@0.3.53__zod@3.24.3": { + "integrity": "sha512-wxdGmOeRUQutCWfdKwFb+92RMdZZ8jvXkG7PD3ZcVLiuOXhC9LSe5xcc1UbWHcV29fV542IDHMfvf2OVdv9dZQ==", + "dependencies": [ + "@langchain/core", + "@langchain/langgraph-checkpoint", + "@langchain/langgraph-sdk", + "uuid@10.0.0", + "zod@3.24.3" + ] + }, + "@langchain/mistralai@0.2.0_@langchain+core@0.3.53__zod@3.24.3_zod@3.24.3": { + "integrity": "sha512-VdfbKZopAuSXf/vlXbriGWLK3c7j5s47DoB3S31xpprY2BMSKZZiX9vE9TsgxMfAPuIDPIYcfgU7p1upvTYt8g==", + "dependencies": [ + "@langchain/core", + "@mistralai/mistralai", + "uuid@10.0.0", + "zod-to-json-schema", + "zod@3.24.3" + ] + }, + "@mistralai/mistralai@1.6.0_zod@3.24.3": { + "integrity": "sha512-PQwGV3+n7FbE7Dp3Vnd8DAa3ffx6WuVV966Gfmf4QvzwcO3Mvxpz0SnJ/PjaZcsCwApBCZpNyQzvarAKEQLKeQ==", + "dependencies": [ + "zod-to-json-schema", + "zod@3.24.3" + ] + }, "@types/body-parser@1.19.5": { "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", "dependencies": [ @@ -197,6 +266,9 @@ "@types/http-errors@2.0.4": { "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==" }, + "@types/json-schema@7.0.15": { + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" + }, "@types/mime@1.3.5": { "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" }, @@ -212,6 +284,9 @@ "@types/range-parser@1.2.7": { "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" }, + "@types/retry@0.12.0": { + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" + }, "@types/send@0.17.4": { "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", "dependencies": [ @@ -227,6 +302,9 @@ "@types/send" ] }, + "@types/uuid@10.0.0": { + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==" + }, "accepts@2.0.0": { "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "dependencies": [ @@ -234,6 +312,18 @@ "negotiator" ] }, + "ansi-styles@4.3.0": { + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": [ + "color-convert" + ] + }, + "ansi-styles@5.2.0": { + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==" + }, + "base64-js@1.5.1": { + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, "body-parser@2.2.0": { "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", "dependencies": [ @@ -268,6 +358,31 @@ "get-intrinsic" ] }, + "camelcase@6.3.0": { + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==" + }, + "chalk@4.1.2": { + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": [ + "ansi-styles@4.3.0", + "supports-color" + ] + }, + "color-convert@2.0.1": { + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": [ + "color-name" + ] + }, + "color-name@1.1.4": { + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "console-table-printer@2.12.1": { + "integrity": "sha512-wKGOQRRvdnd89pCeH96e2Fn4wkbenSP6LMHfjfyNLMbGuHEFbMqQNuxXqd0oXG9caIOQ1FTvc5Uijp9/4jujnQ==", + "dependencies": [ + "simple-wcswidth" + ] + }, "content-disposition@1.0.0": { "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", "dependencies": [ @@ -289,6 +404,9 @@ "ms" ] }, + "decamelize@1.2.0": { + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==" + }, "depd@2.0.0": { "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" }, @@ -397,6 +515,9 @@ "etag@1.8.1": { "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" }, + "eventemitter3@4.0.7": { + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, "express@5.1.0": { "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", "dependencies": [ @@ -480,6 +601,9 @@ "gopd@1.2.0": { "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" }, + "has-flag@4.0.0": { + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, "has-symbols@1.1.0": { "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" }, @@ -514,6 +638,24 @@ "is-promise@4.0.0": { "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" }, + "js-tiktoken@1.0.20": { + "integrity": "sha512-Xlaqhhs8VfCd6Sh7a1cFkZHQbYTLCwVJJWiHVxBYzLPxW0XsoxBy1hitmjkdIjD3Aon5BXLHFwU5O8WUx6HH+A==", + "dependencies": [ + "base64-js" + ] + }, + "langsmith@0.3.25": { + "integrity": "sha512-KuJu89VY3DmCdFvlVxQG4owQl546Z6pQc6TbhsyP77MkVJgZr8yvevZvvcXDWIpT2o2s52c9Aww2XVOH6GmHxQ==", + "dependencies": [ + "@types/uuid", + "chalk", + "console-table-printer", + "p-queue", + "p-retry", + "semver", + "uuid@10.0.0" + ] + }, "math-intrinsics@1.1.0": { "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" }, @@ -535,6 +677,9 @@ "ms@2.1.3": { "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "mustache@4.2.0": { + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==" + }, "negotiator@1.0.0": { "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==" }, @@ -553,6 +698,29 @@ "wrappy" ] }, + "p-finally@1.0.0": { + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==" + }, + "p-queue@6.6.2": { + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "dependencies": [ + "eventemitter3", + "p-timeout" + ] + }, + "p-retry@4.6.2": { + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dependencies": [ + "@types/retry", + "retry" + ] + }, + "p-timeout@3.2.0": { + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "dependencies": [ + "p-finally" + ] + }, "parseurl@1.3.3": { "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, @@ -587,6 +755,9 @@ "resolve-pkg-maps@1.0.0": { "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==" }, + "retry@0.13.1": { + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==" + }, "router@2.2.0": { "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", "dependencies": [ @@ -603,6 +774,9 @@ "safer-buffer@2.1.2": { "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "semver@7.7.1": { + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==" + }, "send@1.2.0": { "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", "dependencies": [ @@ -667,6 +841,9 @@ "side-channel-weakmap" ] }, + "simple-wcswidth@1.0.1": { + "integrity": "sha512-xMO/8eNREtaROt7tJvWJqHBDTMFN4eiQ5I4JRMuilwfnFcV5W9u7RUkueNkdw0jPqGMX36iCywelS5yilTuOxg==" + }, "source-map-support@0.5.21": { "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dependencies": [ @@ -680,6 +857,12 @@ "statuses@2.0.1": { "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" }, + "supports-color@7.2.0": { + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": [ + "has-flag" + ] + }, "toidentifier@1.0.1": { "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" }, @@ -697,18 +880,44 @@ "unpipe@1.0.0": { "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" }, + "uuid@10.0.0": { + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==" + }, + "uuid@11.1.0": { + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==" + }, + "uuid@9.0.1": { + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==" + }, "vary@1.1.2": { "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" }, "wrappy@1.0.2": { "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "zod-to-json-schema@3.24.5_zod@3.24.3": { + "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", + "dependencies": [ + "zod@3.24.3" + ] + }, + "zod@3.24.3": { + "integrity": "sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==" + }, + "zod@3.24.4": { + "integrity": "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==" } }, "workspace": { "dependencies": [ + "npm:@langchain/core@~0.3.53", + "npm:@langchain/langgraph@~0.2.68", + "npm:@langchain/mistralai@0.2", "npm:@types/express@^5.0.1", "npm:drizzle-kit@0.31", - "npm:express@^5.1.0" + "npm:express@^5.1.0", + "npm:uuid@^11.1.0", + "npm:zod@^3.24.4" ] } } diff --git a/src/lc/model.ts b/src/lc/model.ts new file mode 100644 index 0000000..7d0cb0c --- /dev/null +++ b/src/lc/model.ts @@ -0,0 +1,81 @@ +// deno-lint-ignore-file +import { HumanMessage } from "@langchain/core/messages"; +import { tool } from "@langchain/core/tools"; +import { ChatMistralAI } from "@langchain/mistralai"; +import { z } from "zod"; + +const llm = new ChatMistralAI({ + model: "mistral-small-latest", + temperature: 0 +}); + +interface RestaurantMenu { + name: string; + menu: Array<{ item: string; price: number }>; +} + +const restaurantMenus: RestaurantMenu[] = [ + { + name: "Pizza Place", + menu: [ + { item: "Margherita Pizza", price: 10 }, + { item: "Pepperoni Pizza", price: 12 }, + { item: "Veggie Pizza", price: 11 }, + ], + }, + { + name: "Burger Joint", + menu: [ + { item: "Cheeseburger", price: 8 }, + { item: "Veggie Burger", price: 7 }, + { item: "Double Burger", price: 10 }, + ], + }, +] + +const getRestaurantMenu = tool( + async ({ restaurant_name }) => { + const restaurant = restaurantMenus.find( + (menu) => menu.name.toLowerCase() === restaurant_name.toLowerCase() + ); + if (!restaurant) { + throw new Error(`Restaurant ${restaurant_name} not found.`); + } + return `Menu for ${restaurant.name}: ${restaurant.menu.map(item => `${item.item} - $${item.price}`).join(", ")}`; + } + , + { + name: "get_restaurant_menu", + schema: z.object({ + restaurant_name: z.string(), + }), + description: "Get the menu of a restaurant.", + } +); + +const tools = [getRestaurantMenu]; + +const llmWithTools = llm.bindTools(tools); + +const messages = [new HumanMessage("What is the menu of Pizza Place?")]; + +const aiMessage = await llmWithTools.invoke(messages); + +console.log(aiMessage); + +messages.push(aiMessage); + +const toolsByName = { + get_restaurant_menu: getRestaurantMenu, + }; + + for (const toolCall of aiMessage.tool_calls) { + const selectedTool = toolsByName[toolCall.name]; + const toolMessage = await selectedTool.invoke(toolCall); + messages.push(toolMessage); + } + + console.log(messages); + + const final = await llmWithTools.invoke(messages); + console.log(final); \ No newline at end of file From dc78c6e907ad500656faface9a4373e7bc6ac44b Mon Sep 17 00:00:00 2001 From: "Mateus S. Pereira" Date: Wed, 7 May 2025 13:57:33 -0300 Subject: [PATCH 02/12] feat: added SocketIO and integrated with frontend --- deno.json | 3 + deno.lock | 402 ++++++++++++++++++++++++++++++++++++++---------- src/lc/model.ts | 87 ++--------- src/main.ts | 25 ++- 4 files changed, 358 insertions(+), 159 deletions(-) diff --git a/deno.json b/deno.json index dccbf4f..a32e1b0 100644 --- a/deno.json +++ b/deno.json @@ -6,9 +6,12 @@ "@langchain/core": "npm:@langchain/core@^0.3.53", "@langchain/langgraph": "npm:@langchain/langgraph@^0.2.68", "@types/express": "npm:@types/express@^5.0.1", + "cors": "npm:cors@^2.8.5", "drizzle-kit": "npm:drizzle-kit@^0.31.0", "express": "npm:express@^5.1.0", "@langchain/mistralai": "npm:@langchain/mistralai@^0.2.0", + "socket.io": "npm:socket.io@^4.8.1", + "socket.io-client": "npm:socket.io-client@^4.8.1", "uuid": "npm:uuid@^11.1.0", "zod": "npm:zod@^3.24.4" } diff --git a/deno.lock b/deno.lock index 0a24110..987351f 100644 --- a/deno.lock +++ b/deno.lock @@ -1,12 +1,17 @@ { - "version": "4", + "version": "5", "specifiers": { "npm:@langchain/core@~0.3.53": "0.3.53_zod@3.24.3", "npm:@langchain/langgraph@~0.2.68": "0.2.68_@langchain+core@0.3.53__zod@3.24.3", "npm:@langchain/mistralai@0.2": "0.2.0_@langchain+core@0.3.53__zod@3.24.3_zod@3.24.3", "npm:@types/express@^5.0.1": "5.0.1", + "npm:cors@*": "2.8.5", + "npm:cors@^2.8.5": "2.8.5", "npm:drizzle-kit@0.31": "0.31.0_esbuild@0.25.3", "npm:express@^5.1.0": "5.1.0", + "npm:socket.io-client@^4.8.1": "4.8.1", + "npm:socket.io@*": "4.8.1", + "npm:socket.io@^4.8.1": "4.8.1", "npm:uuid@^11.1.0": "11.1.0", "npm:zod@^3.24.4": "3.24.4" }, @@ -22,155 +27,251 @@ "dependencies": [ "esbuild@0.18.20", "source-map-support" - ] + ], + "deprecated": true }, "@esbuild-kit/esm-loader@2.6.5": { "integrity": "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==", "dependencies": [ "@esbuild-kit/core-utils", "get-tsconfig" - ] + ], + "deprecated": true }, "@esbuild/aix-ppc64@0.25.3": { - "integrity": "sha512-W8bFfPA8DowP8l//sxjJLSLkD8iEjMc7cBVyP+u4cEv9sM7mdUCkgsj+t0n/BWPFtv7WWCN5Yzj0N6FJNUUqBQ==" + "integrity": "sha512-W8bFfPA8DowP8l//sxjJLSLkD8iEjMc7cBVyP+u4cEv9sM7mdUCkgsj+t0n/BWPFtv7WWCN5Yzj0N6FJNUUqBQ==", + "os": ["aix"], + "cpu": ["ppc64"] }, "@esbuild/android-arm64@0.18.20": { - "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==" + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "os": ["android"], + "cpu": ["arm64"] }, "@esbuild/android-arm64@0.25.3": { - "integrity": "sha512-XelR6MzjlZuBM4f5z2IQHK6LkK34Cvv6Rj2EntER3lwCBFdg6h2lKbtRjpTTsdEjD/WSe1q8UyPBXP1x3i/wYQ==" + "integrity": "sha512-XelR6MzjlZuBM4f5z2IQHK6LkK34Cvv6Rj2EntER3lwCBFdg6h2lKbtRjpTTsdEjD/WSe1q8UyPBXP1x3i/wYQ==", + "os": ["android"], + "cpu": ["arm64"] }, "@esbuild/android-arm@0.18.20": { - "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==" + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "os": ["android"], + "cpu": ["arm"] }, "@esbuild/android-arm@0.25.3": { - "integrity": "sha512-PuwVXbnP87Tcff5I9ngV0lmiSu40xw1At6i3GsU77U7cjDDB4s0X2cyFuBiDa1SBk9DnvWwnGvVaGBqoFWPb7A==" + "integrity": "sha512-PuwVXbnP87Tcff5I9ngV0lmiSu40xw1At6i3GsU77U7cjDDB4s0X2cyFuBiDa1SBk9DnvWwnGvVaGBqoFWPb7A==", + "os": ["android"], + "cpu": ["arm"] }, "@esbuild/android-x64@0.18.20": { - "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==" + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "os": ["android"], + "cpu": ["x64"] }, "@esbuild/android-x64@0.25.3": { - "integrity": "sha512-ogtTpYHT/g1GWS/zKM0cc/tIebFjm1F9Aw1boQ2Y0eUQ+J89d0jFY//s9ei9jVIlkYi8AfOjiixcLJSGNSOAdQ==" + "integrity": "sha512-ogtTpYHT/g1GWS/zKM0cc/tIebFjm1F9Aw1boQ2Y0eUQ+J89d0jFY//s9ei9jVIlkYi8AfOjiixcLJSGNSOAdQ==", + "os": ["android"], + "cpu": ["x64"] }, "@esbuild/darwin-arm64@0.18.20": { - "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==" + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "os": ["darwin"], + "cpu": ["arm64"] }, "@esbuild/darwin-arm64@0.25.3": { - "integrity": "sha512-eESK5yfPNTqpAmDfFWNsOhmIOaQA59tAcF/EfYvo5/QWQCzXn5iUSOnqt3ra3UdzBv073ykTtmeLJZGt3HhA+w==" + "integrity": "sha512-eESK5yfPNTqpAmDfFWNsOhmIOaQA59tAcF/EfYvo5/QWQCzXn5iUSOnqt3ra3UdzBv073ykTtmeLJZGt3HhA+w==", + "os": ["darwin"], + "cpu": ["arm64"] }, "@esbuild/darwin-x64@0.18.20": { - "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==" + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "os": ["darwin"], + "cpu": ["x64"] }, "@esbuild/darwin-x64@0.25.3": { - "integrity": "sha512-Kd8glo7sIZtwOLcPbW0yLpKmBNWMANZhrC1r6K++uDR2zyzb6AeOYtI6udbtabmQpFaxJ8uduXMAo1gs5ozz8A==" + "integrity": "sha512-Kd8glo7sIZtwOLcPbW0yLpKmBNWMANZhrC1r6K++uDR2zyzb6AeOYtI6udbtabmQpFaxJ8uduXMAo1gs5ozz8A==", + "os": ["darwin"], + "cpu": ["x64"] }, "@esbuild/freebsd-arm64@0.18.20": { - "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==" + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "os": ["freebsd"], + "cpu": ["arm64"] }, "@esbuild/freebsd-arm64@0.25.3": { - "integrity": "sha512-EJiyS70BYybOBpJth3M0KLOus0n+RRMKTYzhYhFeMwp7e/RaajXvP+BWlmEXNk6uk+KAu46j/kaQzr6au+JcIw==" + "integrity": "sha512-EJiyS70BYybOBpJth3M0KLOus0n+RRMKTYzhYhFeMwp7e/RaajXvP+BWlmEXNk6uk+KAu46j/kaQzr6au+JcIw==", + "os": ["freebsd"], + "cpu": ["arm64"] }, "@esbuild/freebsd-x64@0.18.20": { - "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==" + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "os": ["freebsd"], + "cpu": ["x64"] }, "@esbuild/freebsd-x64@0.25.3": { - "integrity": "sha512-Q+wSjaLpGxYf7zC0kL0nDlhsfuFkoN+EXrx2KSB33RhinWzejOd6AvgmP5JbkgXKmjhmpfgKZq24pneodYqE8Q==" + "integrity": "sha512-Q+wSjaLpGxYf7zC0kL0nDlhsfuFkoN+EXrx2KSB33RhinWzejOd6AvgmP5JbkgXKmjhmpfgKZq24pneodYqE8Q==", + "os": ["freebsd"], + "cpu": ["x64"] }, "@esbuild/linux-arm64@0.18.20": { - "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==" + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "os": ["linux"], + "cpu": ["arm64"] }, "@esbuild/linux-arm64@0.25.3": { - "integrity": "sha512-xCUgnNYhRD5bb1C1nqrDV1PfkwgbswTTBRbAd8aH5PhYzikdf/ddtsYyMXFfGSsb/6t6QaPSzxtbfAZr9uox4A==" + "integrity": "sha512-xCUgnNYhRD5bb1C1nqrDV1PfkwgbswTTBRbAd8aH5PhYzikdf/ddtsYyMXFfGSsb/6t6QaPSzxtbfAZr9uox4A==", + "os": ["linux"], + "cpu": ["arm64"] }, "@esbuild/linux-arm@0.18.20": { - "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==" + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "os": ["linux"], + "cpu": ["arm"] }, "@esbuild/linux-arm@0.25.3": { - "integrity": "sha512-dUOVmAUzuHy2ZOKIHIKHCm58HKzFqd+puLaS424h6I85GlSDRZIA5ycBixb3mFgM0Jdh+ZOSB6KptX30DD8YOQ==" + "integrity": "sha512-dUOVmAUzuHy2ZOKIHIKHCm58HKzFqd+puLaS424h6I85GlSDRZIA5ycBixb3mFgM0Jdh+ZOSB6KptX30DD8YOQ==", + "os": ["linux"], + "cpu": ["arm"] }, "@esbuild/linux-ia32@0.18.20": { - "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==" + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "os": ["linux"], + "cpu": ["ia32"] }, "@esbuild/linux-ia32@0.25.3": { - "integrity": "sha512-yplPOpczHOO4jTYKmuYuANI3WhvIPSVANGcNUeMlxH4twz/TeXuzEP41tGKNGWJjuMhotpGabeFYGAOU2ummBw==" + "integrity": "sha512-yplPOpczHOO4jTYKmuYuANI3WhvIPSVANGcNUeMlxH4twz/TeXuzEP41tGKNGWJjuMhotpGabeFYGAOU2ummBw==", + "os": ["linux"], + "cpu": ["ia32"] }, "@esbuild/linux-loong64@0.18.20": { - "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==" + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "os": ["linux"], + "cpu": ["loong64"] }, "@esbuild/linux-loong64@0.25.3": { - "integrity": "sha512-P4BLP5/fjyihmXCELRGrLd793q/lBtKMQl8ARGpDxgzgIKJDRJ/u4r1A/HgpBpKpKZelGct2PGI4T+axcedf6g==" + "integrity": "sha512-P4BLP5/fjyihmXCELRGrLd793q/lBtKMQl8ARGpDxgzgIKJDRJ/u4r1A/HgpBpKpKZelGct2PGI4T+axcedf6g==", + "os": ["linux"], + "cpu": ["loong64"] }, "@esbuild/linux-mips64el@0.18.20": { - "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==" + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "os": ["linux"], + "cpu": ["mips64el"] }, "@esbuild/linux-mips64el@0.25.3": { - "integrity": "sha512-eRAOV2ODpu6P5divMEMa26RRqb2yUoYsuQQOuFUexUoQndm4MdpXXDBbUoKIc0iPa4aCO7gIhtnYomkn2x+bag==" + "integrity": "sha512-eRAOV2ODpu6P5divMEMa26RRqb2yUoYsuQQOuFUexUoQndm4MdpXXDBbUoKIc0iPa4aCO7gIhtnYomkn2x+bag==", + "os": ["linux"], + "cpu": ["mips64el"] }, "@esbuild/linux-ppc64@0.18.20": { - "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==" + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "os": ["linux"], + "cpu": ["ppc64"] }, "@esbuild/linux-ppc64@0.25.3": { - "integrity": "sha512-ZC4jV2p7VbzTlnl8nZKLcBkfzIf4Yad1SJM4ZMKYnJqZFD4rTI+pBG65u8ev4jk3/MPwY9DvGn50wi3uhdaghg==" + "integrity": "sha512-ZC4jV2p7VbzTlnl8nZKLcBkfzIf4Yad1SJM4ZMKYnJqZFD4rTI+pBG65u8ev4jk3/MPwY9DvGn50wi3uhdaghg==", + "os": ["linux"], + "cpu": ["ppc64"] }, "@esbuild/linux-riscv64@0.18.20": { - "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==" + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "os": ["linux"], + "cpu": ["riscv64"] }, "@esbuild/linux-riscv64@0.25.3": { - "integrity": "sha512-LDDODcFzNtECTrUUbVCs6j9/bDVqy7DDRsuIXJg6so+mFksgwG7ZVnTruYi5V+z3eE5y+BJZw7VvUadkbfg7QA==" + "integrity": "sha512-LDDODcFzNtECTrUUbVCs6j9/bDVqy7DDRsuIXJg6so+mFksgwG7ZVnTruYi5V+z3eE5y+BJZw7VvUadkbfg7QA==", + "os": ["linux"], + "cpu": ["riscv64"] }, "@esbuild/linux-s390x@0.18.20": { - "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==" + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "os": ["linux"], + "cpu": ["s390x"] }, "@esbuild/linux-s390x@0.25.3": { - "integrity": "sha512-s+w/NOY2k0yC2p9SLen+ymflgcpRkvwwa02fqmAwhBRI3SC12uiS10edHHXlVWwfAagYSY5UpmT/zISXPMW3tQ==" + "integrity": "sha512-s+w/NOY2k0yC2p9SLen+ymflgcpRkvwwa02fqmAwhBRI3SC12uiS10edHHXlVWwfAagYSY5UpmT/zISXPMW3tQ==", + "os": ["linux"], + "cpu": ["s390x"] }, "@esbuild/linux-x64@0.18.20": { - "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==" + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "os": ["linux"], + "cpu": ["x64"] }, "@esbuild/linux-x64@0.25.3": { - "integrity": "sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA==" + "integrity": "sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA==", + "os": ["linux"], + "cpu": ["x64"] }, "@esbuild/netbsd-arm64@0.25.3": { - "integrity": "sha512-1QaLtOWq0mzK6tzzp0jRN3eccmN3hezey7mhLnzC6oNlJoUJz4nym5ZD7mDnS/LZQgkrhEbEiTn515lPeLpgWA==" + "integrity": "sha512-1QaLtOWq0mzK6tzzp0jRN3eccmN3hezey7mhLnzC6oNlJoUJz4nym5ZD7mDnS/LZQgkrhEbEiTn515lPeLpgWA==", + "os": ["netbsd"], + "cpu": ["arm64"] }, "@esbuild/netbsd-x64@0.18.20": { - "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==" + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "os": ["netbsd"], + "cpu": ["x64"] }, "@esbuild/netbsd-x64@0.25.3": { - "integrity": "sha512-i5Hm68HXHdgv8wkrt+10Bc50zM0/eonPb/a/OFVfB6Qvpiirco5gBA5bz7S2SHuU+Y4LWn/zehzNX14Sp4r27g==" + "integrity": "sha512-i5Hm68HXHdgv8wkrt+10Bc50zM0/eonPb/a/OFVfB6Qvpiirco5gBA5bz7S2SHuU+Y4LWn/zehzNX14Sp4r27g==", + "os": ["netbsd"], + "cpu": ["x64"] }, "@esbuild/openbsd-arm64@0.25.3": { - "integrity": "sha512-zGAVApJEYTbOC6H/3QBr2mq3upG/LBEXr85/pTtKiv2IXcgKV0RT0QA/hSXZqSvLEpXeIxah7LczB4lkiYhTAQ==" + "integrity": "sha512-zGAVApJEYTbOC6H/3QBr2mq3upG/LBEXr85/pTtKiv2IXcgKV0RT0QA/hSXZqSvLEpXeIxah7LczB4lkiYhTAQ==", + "os": ["openbsd"], + "cpu": ["arm64"] }, "@esbuild/openbsd-x64@0.18.20": { - "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==" + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "os": ["openbsd"], + "cpu": ["x64"] }, "@esbuild/openbsd-x64@0.25.3": { - "integrity": "sha512-fpqctI45NnCIDKBH5AXQBsD0NDPbEFczK98hk/aa6HJxbl+UtLkJV2+Bvy5hLSLk3LHmqt0NTkKNso1A9y1a4w==" + "integrity": "sha512-fpqctI45NnCIDKBH5AXQBsD0NDPbEFczK98hk/aa6HJxbl+UtLkJV2+Bvy5hLSLk3LHmqt0NTkKNso1A9y1a4w==", + "os": ["openbsd"], + "cpu": ["x64"] }, "@esbuild/sunos-x64@0.18.20": { - "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==" + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "os": ["sunos"], + "cpu": ["x64"] }, "@esbuild/sunos-x64@0.25.3": { - "integrity": "sha512-ROJhm7d8bk9dMCUZjkS8fgzsPAZEjtRJqCAmVgB0gMrvG7hfmPmz9k1rwO4jSiblFjYmNvbECL9uhaPzONMfgA==" + "integrity": "sha512-ROJhm7d8bk9dMCUZjkS8fgzsPAZEjtRJqCAmVgB0gMrvG7hfmPmz9k1rwO4jSiblFjYmNvbECL9uhaPzONMfgA==", + "os": ["sunos"], + "cpu": ["x64"] }, "@esbuild/win32-arm64@0.18.20": { - "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==" + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "os": ["win32"], + "cpu": ["arm64"] }, "@esbuild/win32-arm64@0.25.3": { - "integrity": "sha512-YWcow8peiHpNBiIXHwaswPnAXLsLVygFwCB3A7Bh5jRkIBFWHGmNQ48AlX4xDvQNoMZlPYzjVOQDYEzWCqufMQ==" + "integrity": "sha512-YWcow8peiHpNBiIXHwaswPnAXLsLVygFwCB3A7Bh5jRkIBFWHGmNQ48AlX4xDvQNoMZlPYzjVOQDYEzWCqufMQ==", + "os": ["win32"], + "cpu": ["arm64"] }, "@esbuild/win32-ia32@0.18.20": { - "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==" + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "os": ["win32"], + "cpu": ["ia32"] }, "@esbuild/win32-ia32@0.25.3": { - "integrity": "sha512-qspTZOIGoXVS4DpNqUYUs9UxVb04khS1Degaw/MnfMe7goQ3lTfQ13Vw4qY/Nj0979BGvMRpAYbs/BAxEvU8ew==" + "integrity": "sha512-qspTZOIGoXVS4DpNqUYUs9UxVb04khS1Degaw/MnfMe7goQ3lTfQ13Vw4qY/Nj0979BGvMRpAYbs/BAxEvU8ew==", + "os": ["win32"], + "cpu": ["ia32"] }, "@esbuild/win32-x64@0.18.20": { - "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==" + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "os": ["win32"], + "cpu": ["x64"] }, "@esbuild/win32-x64@0.25.3": { - "integrity": "sha512-ICgUR+kPimx0vvRzf+N/7L7tVSQeE3BYY+NhHRHXS1kBuPO7z2+7ea2HbhDyZdTephgvNvKrlDDKUexuCVBVvg==" + "integrity": "sha512-ICgUR+kPimx0vvRzf+N/7L7tVSQeE3BYY+NhHRHXS1kBuPO7z2+7ea2HbhDyZdTephgvNvKrlDDKUexuCVBVvg==", + "os": ["win32"], + "cpu": ["x64"] }, "@langchain/core@0.3.53_zod@3.24.3": { "integrity": "sha512-rHlBcEG5PNaWxlVhPTLiZ0WRCr/URNEUynhgZTZ8QbTJhQ1vEMibdr2YL9LYKHSXNyAp/b5j3itcu3epB8FD7Q==", @@ -185,8 +286,8 @@ "p-queue", "p-retry", "uuid@10.0.0", - "zod-to-json-schema", - "zod@3.24.3" + "zod@3.24.3", + "zod-to-json-schema" ] }, "@langchain/langgraph-checkpoint@0.0.17_@langchain+core@0.3.53__zod@3.24.3": { @@ -204,6 +305,9 @@ "p-queue", "p-retry", "uuid@9.0.1" + ], + "optionalPeers": [ + "@langchain/core" ] }, "@langchain/langgraph@0.2.68_@langchain+core@0.3.53__zod@3.24.3": { @@ -222,17 +326,20 @@ "@langchain/core", "@mistralai/mistralai", "uuid@10.0.0", - "zod-to-json-schema", - "zod@3.24.3" + "zod@3.24.3", + "zod-to-json-schema" ] }, "@mistralai/mistralai@1.6.0_zod@3.24.3": { "integrity": "sha512-PQwGV3+n7FbE7Dp3Vnd8DAa3ffx6WuVV966Gfmf4QvzwcO3Mvxpz0SnJ/PjaZcsCwApBCZpNyQzvarAKEQLKeQ==", "dependencies": [ - "zod-to-json-schema", - "zod@3.24.3" + "zod@3.24.3", + "zod-to-json-schema" ] }, + "@socket.io/component-emitter@3.1.2": { + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" + }, "@types/body-parser@1.19.5": { "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", "dependencies": [ @@ -246,6 +353,12 @@ "@types/node" ] }, + "@types/cors@2.8.17": { + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dependencies": [ + "@types/node" + ] + }, "@types/express-serve-static-core@5.0.6": { "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==", "dependencies": [ @@ -305,11 +418,18 @@ "@types/uuid@10.0.0": { "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==" }, + "accepts@1.3.8": { + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": [ + "mime-types@2.1.35", + "negotiator@0.6.3" + ] + }, "accepts@2.0.0": { "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "dependencies": [ - "mime-types", - "negotiator" + "mime-types@3.0.1", + "negotiator@1.0.0" ] }, "ansi-styles@4.3.0": { @@ -324,12 +444,15 @@ "base64-js@1.5.1": { "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, + "base64id@2.0.0": { + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" + }, "body-parser@2.2.0": { "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", "dependencies": [ "bytes", "content-type", - "debug", + "debug@4.4.0", "http-errors", "iconv-lite", "on-finished", @@ -398,6 +521,19 @@ "cookie@0.7.2": { "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==" }, + "cors@2.8.5": { + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": [ + "object-assign", + "vary" + ] + }, + "debug@4.3.7": { + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": [ + "ms" + ] + }, "debug@4.4.0": { "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "dependencies": [ @@ -415,9 +551,10 @@ "dependencies": [ "@drizzle-team/brocli", "@esbuild-kit/esm-loader", - "esbuild-register", - "esbuild@0.25.3" - ] + "esbuild@0.25.3", + "esbuild-register" + ], + "bin": true }, "dunder-proto@1.0.1": { "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", @@ -433,6 +570,33 @@ "encodeurl@2.0.0": { "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" }, + "engine.io-client@6.6.3": { + "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==", + "dependencies": [ + "@socket.io/component-emitter", + "debug@4.3.7", + "engine.io-parser", + "ws", + "xmlhttprequest-ssl" + ] + }, + "engine.io-parser@5.2.3": { + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==" + }, + "engine.io@6.6.4": { + "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", + "dependencies": [ + "@types/cors", + "@types/node", + "accepts@1.3.8", + "base64id", + "cookie", + "cors", + "debug@4.3.7", + "engine.io-parser", + "ws" + ] + }, "es-define-property@1.0.1": { "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" }, @@ -448,22 +612,22 @@ "esbuild-register@3.6.0_esbuild@0.25.3": { "integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==", "dependencies": [ - "debug", + "debug@4.4.0", "esbuild@0.25.3" ] }, "esbuild@0.18.20": { "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", - "dependencies": [ - "@esbuild/android-arm64@0.18.20", + "optionalDependencies": [ "@esbuild/android-arm@0.18.20", + "@esbuild/android-arm64@0.18.20", "@esbuild/android-x64@0.18.20", "@esbuild/darwin-arm64@0.18.20", "@esbuild/darwin-x64@0.18.20", "@esbuild/freebsd-arm64@0.18.20", "@esbuild/freebsd-x64@0.18.20", - "@esbuild/linux-arm64@0.18.20", "@esbuild/linux-arm@0.18.20", + "@esbuild/linux-arm64@0.18.20", "@esbuild/linux-ia32@0.18.20", "@esbuild/linux-loong64@0.18.20", "@esbuild/linux-mips64el@0.18.20", @@ -477,21 +641,23 @@ "@esbuild/win32-arm64@0.18.20", "@esbuild/win32-ia32@0.18.20", "@esbuild/win32-x64@0.18.20" - ] + ], + "scripts": true, + "bin": true }, "esbuild@0.25.3": { "integrity": "sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q==", - "dependencies": [ + "optionalDependencies": [ "@esbuild/aix-ppc64", - "@esbuild/android-arm64@0.25.3", "@esbuild/android-arm@0.25.3", + "@esbuild/android-arm64@0.25.3", "@esbuild/android-x64@0.25.3", "@esbuild/darwin-arm64@0.25.3", "@esbuild/darwin-x64@0.25.3", "@esbuild/freebsd-arm64@0.25.3", "@esbuild/freebsd-x64@0.25.3", - "@esbuild/linux-arm64@0.25.3", "@esbuild/linux-arm@0.25.3", + "@esbuild/linux-arm64@0.25.3", "@esbuild/linux-ia32@0.25.3", "@esbuild/linux-loong64@0.25.3", "@esbuild/linux-mips64el@0.25.3", @@ -507,7 +673,9 @@ "@esbuild/win32-arm64@0.25.3", "@esbuild/win32-ia32@0.25.3", "@esbuild/win32-x64@0.25.3" - ] + ], + "scripts": true, + "bin": true }, "escape-html@1.0.3": { "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" @@ -521,13 +689,13 @@ "express@5.1.0": { "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", "dependencies": [ - "accepts", + "accepts@2.0.0", "body-parser", "content-disposition", "content-type", "cookie", "cookie-signature", - "debug", + "debug@4.4.0", "encodeurl", "escape-html", "etag", @@ -535,7 +703,7 @@ "fresh", "http-errors", "merge-descriptors", - "mime-types", + "mime-types@3.0.1", "on-finished", "once", "parseurl", @@ -553,7 +721,7 @@ "finalhandler@2.1.0": { "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", "dependencies": [ - "debug", + "debug@4.4.0", "encodeurl", "escape-html", "on-finished", @@ -665,24 +833,40 @@ "merge-descriptors@2.0.0": { "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==" }, + "mime-db@1.52.0": { + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, "mime-db@1.54.0": { "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==" }, + "mime-types@2.1.35": { + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": [ + "mime-db@1.52.0" + ] + }, "mime-types@3.0.1": { "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", "dependencies": [ - "mime-db" + "mime-db@1.54.0" ] }, "ms@2.1.3": { "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "mustache@4.2.0": { - "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==" + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "bin": true + }, + "negotiator@0.6.3": { + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" }, "negotiator@1.0.0": { "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==" }, + "object-assign@4.1.1": { + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" + }, "object-inspect@1.13.4": { "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==" }, @@ -761,7 +945,7 @@ "router@2.2.0": { "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", "dependencies": [ - "debug", + "debug@4.4.0", "depd", "is-promise", "parseurl", @@ -775,18 +959,19 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "semver@7.7.1": { - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==" + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "bin": true }, "send@1.2.0": { "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", "dependencies": [ - "debug", + "debug@4.4.0", "encodeurl", "escape-html", "etag", "fresh", "http-errors", - "mime-types", + "mime-types@3.0.1", "ms", "on-finished", "range-parser", @@ -844,6 +1029,41 @@ "simple-wcswidth@1.0.1": { "integrity": "sha512-xMO/8eNREtaROt7tJvWJqHBDTMFN4eiQ5I4JRMuilwfnFcV5W9u7RUkueNkdw0jPqGMX36iCywelS5yilTuOxg==" }, + "socket.io-adapter@2.5.5": { + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "dependencies": [ + "debug@4.3.7", + "ws" + ] + }, + "socket.io-client@4.8.1": { + "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "dependencies": [ + "@socket.io/component-emitter", + "debug@4.3.7", + "engine.io-client", + "socket.io-parser" + ] + }, + "socket.io-parser@4.2.4": { + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dependencies": [ + "@socket.io/component-emitter", + "debug@4.3.7" + ] + }, + "socket.io@4.8.1": { + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", + "dependencies": [ + "accepts@1.3.8", + "base64id", + "cors", + "debug@4.3.7", + "engine.io", + "socket.io-adapter", + "socket.io-parser" + ] + }, "source-map-support@0.5.21": { "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dependencies": [ @@ -871,7 +1091,7 @@ "dependencies": [ "content-type", "media-typer", - "mime-types" + "mime-types@3.0.1" ] }, "undici-types@6.20.0": { @@ -881,13 +1101,16 @@ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" }, "uuid@10.0.0": { - "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==" + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "bin": true }, "uuid@11.1.0": { - "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==" + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "bin": true }, "uuid@9.0.1": { - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==" + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "bin": true }, "vary@1.1.2": { "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" @@ -895,6 +1118,12 @@ "wrappy@1.0.2": { "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, + "ws@8.17.1": { + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==" + }, + "xmlhttprequest-ssl@2.1.2": { + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==" + }, "zod-to-json-schema@3.24.5_zod@3.24.3": { "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", "dependencies": [ @@ -914,8 +1143,11 @@ "npm:@langchain/langgraph@~0.2.68", "npm:@langchain/mistralai@0.2", "npm:@types/express@^5.0.1", + "npm:cors@^2.8.5", "npm:drizzle-kit@0.31", "npm:express@^5.1.0", + "npm:socket.io-client@^4.8.1", + "npm:socket.io@^4.8.1", "npm:uuid@^11.1.0", "npm:zod@^3.24.4" ] diff --git a/src/lc/model.ts b/src/lc/model.ts index 7d0cb0c..9bb3474 100644 --- a/src/lc/model.ts +++ b/src/lc/model.ts @@ -1,81 +1,22 @@ -// deno-lint-ignore-file -import { HumanMessage } from "@langchain/core/messages"; -import { tool } from "@langchain/core/tools"; +import { HumanMessage, SystemMessage } from "@langchain/core/messages"; import { ChatMistralAI } from "@langchain/mistralai"; +import { tool } from "@langchain/core/tools"; import { z } from "zod"; const llm = new ChatMistralAI({ model: "mistral-small-latest", - temperature: 0 + temperature: 0, }); -interface RestaurantMenu { - name: string; - menu: Array<{ item: string; price: number }>; +const messages = [ + new SystemMessage( + "Você é um assistente de um chatbot chamado 'PoliEats', sua função é prover informações sobre o estabelecimento como: Cardápio, horários de funcionamento, receber pedidos e prover status dos pedidos em andamento. Você responde apenas em português", + ), +]; + +export async function addMessage(message: string) { + messages.push(new HumanMessage(message)); + return await llm.invoke(messages).then((res) => { + return res.content; + }); } - -const restaurantMenus: RestaurantMenu[] = [ - { - name: "Pizza Place", - menu: [ - { item: "Margherita Pizza", price: 10 }, - { item: "Pepperoni Pizza", price: 12 }, - { item: "Veggie Pizza", price: 11 }, - ], - }, - { - name: "Burger Joint", - menu: [ - { item: "Cheeseburger", price: 8 }, - { item: "Veggie Burger", price: 7 }, - { item: "Double Burger", price: 10 }, - ], - }, -] - -const getRestaurantMenu = tool( - async ({ restaurant_name }) => { - const restaurant = restaurantMenus.find( - (menu) => menu.name.toLowerCase() === restaurant_name.toLowerCase() - ); - if (!restaurant) { - throw new Error(`Restaurant ${restaurant_name} not found.`); - } - return `Menu for ${restaurant.name}: ${restaurant.menu.map(item => `${item.item} - $${item.price}`).join(", ")}`; - } - , - { - name: "get_restaurant_menu", - schema: z.object({ - restaurant_name: z.string(), - }), - description: "Get the menu of a restaurant.", - } -); - -const tools = [getRestaurantMenu]; - -const llmWithTools = llm.bindTools(tools); - -const messages = [new HumanMessage("What is the menu of Pizza Place?")]; - -const aiMessage = await llmWithTools.invoke(messages); - -console.log(aiMessage); - -messages.push(aiMessage); - -const toolsByName = { - get_restaurant_menu: getRestaurantMenu, - }; - - for (const toolCall of aiMessage.tool_calls) { - const selectedTool = toolsByName[toolCall.name]; - const toolMessage = await selectedTool.invoke(toolCall); - messages.push(toolMessage); - } - - console.log(messages); - - const final = await llmWithTools.invoke(messages); - console.log(final); \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index d68df84..eaa94c8 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,10 +1,33 @@ import express from "express"; +import { Server } from "npm:socket.io"; +import { createServer } from "node:http"; +import cors from "npm:cors"; +import { addMessage } from "./lc/model.ts"; const app = express(); +app.use(cors({ + origin: "http://localhost:5173", +})); + +const wsServer = createServer(app); +const io = new Server(wsServer, { + cors: { + origin: "http://localhost:5173", + }, +}); + app.get("/", (_req, res) => { res.send("Welcome to the Dinosaur API!"); }); -app.listen(8000); +io.on("connection", (socket) => { + socket.on("message", async (e) => { + await addMessage(e).then((final) => { + socket.emit("message", final); + }); + }); +}); + +wsServer.listen(8000); console.log(`Server is running on http://localhost:8000`); From 4cc419d10ccb7465a0e8c4b7115cba810cec8b49 Mon Sep 17 00:00:00 2001 From: Matelz <49626198+Matelz@users.noreply.github.com> Date: Sun, 11 May 2025 12:40:45 -0300 Subject: [PATCH 03/12] feat: added mock tools --- deno.lock | 493 +++++++++++++----------------------------------- src/lc/model.ts | 48 ++++- src/lc/tools.ts | 97 ++++++++++ src/main.ts | 16 +- 4 files changed, 283 insertions(+), 371 deletions(-) create mode 100644 src/lc/tools.ts diff --git a/deno.lock b/deno.lock index 987351f..f916256 100644 --- a/deno.lock +++ b/deno.lock @@ -1,18 +1,14 @@ { - "version": "5", + "version": "4", "specifiers": { - "npm:@langchain/core@~0.3.53": "0.3.53_zod@3.24.3", - "npm:@langchain/langgraph@~0.2.68": "0.2.68_@langchain+core@0.3.53__zod@3.24.3", - "npm:@langchain/mistralai@0.2": "0.2.0_@langchain+core@0.3.53__zod@3.24.3_zod@3.24.3", - "npm:@types/express@^5.0.1": "5.0.1", + "npm:@langchain/core@*": "0.3.53_zod@3.24.4", + "npm:@langchain/core@~0.3.53": "0.3.53_zod@3.24.4", + "npm:@langchain/mistralai@0.2": "0.2.0_@langchain+core@0.3.53__zod@3.24.4_zod@3.24.4", + "npm:@types/node@*": "22.12.0", "npm:cors@*": "2.8.5", - "npm:cors@^2.8.5": "2.8.5", - "npm:drizzle-kit@0.31": "0.31.0_esbuild@0.25.3", + "npm:drizzle-kit@0.31": "0.31.1_esbuild@0.25.4", "npm:express@^5.1.0": "5.1.0", - "npm:socket.io-client@^4.8.1": "4.8.1", "npm:socket.io@*": "4.8.1", - "npm:socket.io@^4.8.1": "4.8.1", - "npm:uuid@^11.1.0": "11.1.0", "npm:zod@^3.24.4": "3.24.4" }, "npm": { @@ -27,253 +23,157 @@ "dependencies": [ "esbuild@0.18.20", "source-map-support" - ], - "deprecated": true + ] }, "@esbuild-kit/esm-loader@2.6.5": { "integrity": "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==", "dependencies": [ "@esbuild-kit/core-utils", "get-tsconfig" - ], - "deprecated": true + ] }, - "@esbuild/aix-ppc64@0.25.3": { - "integrity": "sha512-W8bFfPA8DowP8l//sxjJLSLkD8iEjMc7cBVyP+u4cEv9sM7mdUCkgsj+t0n/BWPFtv7WWCN5Yzj0N6FJNUUqBQ==", - "os": ["aix"], - "cpu": ["ppc64"] + "@esbuild/aix-ppc64@0.25.4": { + "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==" }, "@esbuild/android-arm64@0.18.20": { - "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", - "os": ["android"], - "cpu": ["arm64"] + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==" }, - "@esbuild/android-arm64@0.25.3": { - "integrity": "sha512-XelR6MzjlZuBM4f5z2IQHK6LkK34Cvv6Rj2EntER3lwCBFdg6h2lKbtRjpTTsdEjD/WSe1q8UyPBXP1x3i/wYQ==", - "os": ["android"], - "cpu": ["arm64"] + "@esbuild/android-arm64@0.25.4": { + "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==" }, "@esbuild/android-arm@0.18.20": { - "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", - "os": ["android"], - "cpu": ["arm"] + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==" }, - "@esbuild/android-arm@0.25.3": { - "integrity": "sha512-PuwVXbnP87Tcff5I9ngV0lmiSu40xw1At6i3GsU77U7cjDDB4s0X2cyFuBiDa1SBk9DnvWwnGvVaGBqoFWPb7A==", - "os": ["android"], - "cpu": ["arm"] + "@esbuild/android-arm@0.25.4": { + "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==" }, "@esbuild/android-x64@0.18.20": { - "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", - "os": ["android"], - "cpu": ["x64"] + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==" }, - "@esbuild/android-x64@0.25.3": { - "integrity": "sha512-ogtTpYHT/g1GWS/zKM0cc/tIebFjm1F9Aw1boQ2Y0eUQ+J89d0jFY//s9ei9jVIlkYi8AfOjiixcLJSGNSOAdQ==", - "os": ["android"], - "cpu": ["x64"] + "@esbuild/android-x64@0.25.4": { + "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==" }, "@esbuild/darwin-arm64@0.18.20": { - "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", - "os": ["darwin"], - "cpu": ["arm64"] + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==" }, - "@esbuild/darwin-arm64@0.25.3": { - "integrity": "sha512-eESK5yfPNTqpAmDfFWNsOhmIOaQA59tAcF/EfYvo5/QWQCzXn5iUSOnqt3ra3UdzBv073ykTtmeLJZGt3HhA+w==", - "os": ["darwin"], - "cpu": ["arm64"] + "@esbuild/darwin-arm64@0.25.4": { + "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==" }, "@esbuild/darwin-x64@0.18.20": { - "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", - "os": ["darwin"], - "cpu": ["x64"] + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==" }, - "@esbuild/darwin-x64@0.25.3": { - "integrity": "sha512-Kd8glo7sIZtwOLcPbW0yLpKmBNWMANZhrC1r6K++uDR2zyzb6AeOYtI6udbtabmQpFaxJ8uduXMAo1gs5ozz8A==", - "os": ["darwin"], - "cpu": ["x64"] + "@esbuild/darwin-x64@0.25.4": { + "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==" }, "@esbuild/freebsd-arm64@0.18.20": { - "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", - "os": ["freebsd"], - "cpu": ["arm64"] + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==" }, - "@esbuild/freebsd-arm64@0.25.3": { - "integrity": "sha512-EJiyS70BYybOBpJth3M0KLOus0n+RRMKTYzhYhFeMwp7e/RaajXvP+BWlmEXNk6uk+KAu46j/kaQzr6au+JcIw==", - "os": ["freebsd"], - "cpu": ["arm64"] + "@esbuild/freebsd-arm64@0.25.4": { + "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==" }, "@esbuild/freebsd-x64@0.18.20": { - "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", - "os": ["freebsd"], - "cpu": ["x64"] + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==" }, - "@esbuild/freebsd-x64@0.25.3": { - "integrity": "sha512-Q+wSjaLpGxYf7zC0kL0nDlhsfuFkoN+EXrx2KSB33RhinWzejOd6AvgmP5JbkgXKmjhmpfgKZq24pneodYqE8Q==", - "os": ["freebsd"], - "cpu": ["x64"] + "@esbuild/freebsd-x64@0.25.4": { + "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==" }, "@esbuild/linux-arm64@0.18.20": { - "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", - "os": ["linux"], - "cpu": ["arm64"] + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==" }, - "@esbuild/linux-arm64@0.25.3": { - "integrity": "sha512-xCUgnNYhRD5bb1C1nqrDV1PfkwgbswTTBRbAd8aH5PhYzikdf/ddtsYyMXFfGSsb/6t6QaPSzxtbfAZr9uox4A==", - "os": ["linux"], - "cpu": ["arm64"] + "@esbuild/linux-arm64@0.25.4": { + "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==" }, "@esbuild/linux-arm@0.18.20": { - "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", - "os": ["linux"], - "cpu": ["arm"] + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==" }, - "@esbuild/linux-arm@0.25.3": { - "integrity": "sha512-dUOVmAUzuHy2ZOKIHIKHCm58HKzFqd+puLaS424h6I85GlSDRZIA5ycBixb3mFgM0Jdh+ZOSB6KptX30DD8YOQ==", - "os": ["linux"], - "cpu": ["arm"] + "@esbuild/linux-arm@0.25.4": { + "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==" }, "@esbuild/linux-ia32@0.18.20": { - "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", - "os": ["linux"], - "cpu": ["ia32"] + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==" }, - "@esbuild/linux-ia32@0.25.3": { - "integrity": "sha512-yplPOpczHOO4jTYKmuYuANI3WhvIPSVANGcNUeMlxH4twz/TeXuzEP41tGKNGWJjuMhotpGabeFYGAOU2ummBw==", - "os": ["linux"], - "cpu": ["ia32"] + "@esbuild/linux-ia32@0.25.4": { + "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==" }, "@esbuild/linux-loong64@0.18.20": { - "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", - "os": ["linux"], - "cpu": ["loong64"] + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==" }, - "@esbuild/linux-loong64@0.25.3": { - "integrity": "sha512-P4BLP5/fjyihmXCELRGrLd793q/lBtKMQl8ARGpDxgzgIKJDRJ/u4r1A/HgpBpKpKZelGct2PGI4T+axcedf6g==", - "os": ["linux"], - "cpu": ["loong64"] + "@esbuild/linux-loong64@0.25.4": { + "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==" }, "@esbuild/linux-mips64el@0.18.20": { - "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", - "os": ["linux"], - "cpu": ["mips64el"] + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==" }, - "@esbuild/linux-mips64el@0.25.3": { - "integrity": "sha512-eRAOV2ODpu6P5divMEMa26RRqb2yUoYsuQQOuFUexUoQndm4MdpXXDBbUoKIc0iPa4aCO7gIhtnYomkn2x+bag==", - "os": ["linux"], - "cpu": ["mips64el"] + "@esbuild/linux-mips64el@0.25.4": { + "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==" }, "@esbuild/linux-ppc64@0.18.20": { - "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", - "os": ["linux"], - "cpu": ["ppc64"] + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==" }, - "@esbuild/linux-ppc64@0.25.3": { - "integrity": "sha512-ZC4jV2p7VbzTlnl8nZKLcBkfzIf4Yad1SJM4ZMKYnJqZFD4rTI+pBG65u8ev4jk3/MPwY9DvGn50wi3uhdaghg==", - "os": ["linux"], - "cpu": ["ppc64"] + "@esbuild/linux-ppc64@0.25.4": { + "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==" }, "@esbuild/linux-riscv64@0.18.20": { - "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", - "os": ["linux"], - "cpu": ["riscv64"] + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==" }, - "@esbuild/linux-riscv64@0.25.3": { - "integrity": "sha512-LDDODcFzNtECTrUUbVCs6j9/bDVqy7DDRsuIXJg6so+mFksgwG7ZVnTruYi5V+z3eE5y+BJZw7VvUadkbfg7QA==", - "os": ["linux"], - "cpu": ["riscv64"] + "@esbuild/linux-riscv64@0.25.4": { + "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==" }, "@esbuild/linux-s390x@0.18.20": { - "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", - "os": ["linux"], - "cpu": ["s390x"] + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==" }, - "@esbuild/linux-s390x@0.25.3": { - "integrity": "sha512-s+w/NOY2k0yC2p9SLen+ymflgcpRkvwwa02fqmAwhBRI3SC12uiS10edHHXlVWwfAagYSY5UpmT/zISXPMW3tQ==", - "os": ["linux"], - "cpu": ["s390x"] + "@esbuild/linux-s390x@0.25.4": { + "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==" }, "@esbuild/linux-x64@0.18.20": { - "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", - "os": ["linux"], - "cpu": ["x64"] + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==" }, - "@esbuild/linux-x64@0.25.3": { - "integrity": "sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA==", - "os": ["linux"], - "cpu": ["x64"] + "@esbuild/linux-x64@0.25.4": { + "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==" }, - "@esbuild/netbsd-arm64@0.25.3": { - "integrity": "sha512-1QaLtOWq0mzK6tzzp0jRN3eccmN3hezey7mhLnzC6oNlJoUJz4nym5ZD7mDnS/LZQgkrhEbEiTn515lPeLpgWA==", - "os": ["netbsd"], - "cpu": ["arm64"] + "@esbuild/netbsd-arm64@0.25.4": { + "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==" }, "@esbuild/netbsd-x64@0.18.20": { - "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", - "os": ["netbsd"], - "cpu": ["x64"] + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==" }, - "@esbuild/netbsd-x64@0.25.3": { - "integrity": "sha512-i5Hm68HXHdgv8wkrt+10Bc50zM0/eonPb/a/OFVfB6Qvpiirco5gBA5bz7S2SHuU+Y4LWn/zehzNX14Sp4r27g==", - "os": ["netbsd"], - "cpu": ["x64"] + "@esbuild/netbsd-x64@0.25.4": { + "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==" }, - "@esbuild/openbsd-arm64@0.25.3": { - "integrity": "sha512-zGAVApJEYTbOC6H/3QBr2mq3upG/LBEXr85/pTtKiv2IXcgKV0RT0QA/hSXZqSvLEpXeIxah7LczB4lkiYhTAQ==", - "os": ["openbsd"], - "cpu": ["arm64"] + "@esbuild/openbsd-arm64@0.25.4": { + "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==" }, "@esbuild/openbsd-x64@0.18.20": { - "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", - "os": ["openbsd"], - "cpu": ["x64"] + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==" }, - "@esbuild/openbsd-x64@0.25.3": { - "integrity": "sha512-fpqctI45NnCIDKBH5AXQBsD0NDPbEFczK98hk/aa6HJxbl+UtLkJV2+Bvy5hLSLk3LHmqt0NTkKNso1A9y1a4w==", - "os": ["openbsd"], - "cpu": ["x64"] + "@esbuild/openbsd-x64@0.25.4": { + "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==" }, "@esbuild/sunos-x64@0.18.20": { - "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", - "os": ["sunos"], - "cpu": ["x64"] + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==" }, - "@esbuild/sunos-x64@0.25.3": { - "integrity": "sha512-ROJhm7d8bk9dMCUZjkS8fgzsPAZEjtRJqCAmVgB0gMrvG7hfmPmz9k1rwO4jSiblFjYmNvbECL9uhaPzONMfgA==", - "os": ["sunos"], - "cpu": ["x64"] + "@esbuild/sunos-x64@0.25.4": { + "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==" }, "@esbuild/win32-arm64@0.18.20": { - "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", - "os": ["win32"], - "cpu": ["arm64"] + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==" }, - "@esbuild/win32-arm64@0.25.3": { - "integrity": "sha512-YWcow8peiHpNBiIXHwaswPnAXLsLVygFwCB3A7Bh5jRkIBFWHGmNQ48AlX4xDvQNoMZlPYzjVOQDYEzWCqufMQ==", - "os": ["win32"], - "cpu": ["arm64"] + "@esbuild/win32-arm64@0.25.4": { + "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==" }, "@esbuild/win32-ia32@0.18.20": { - "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", - "os": ["win32"], - "cpu": ["ia32"] + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==" }, - "@esbuild/win32-ia32@0.25.3": { - "integrity": "sha512-qspTZOIGoXVS4DpNqUYUs9UxVb04khS1Degaw/MnfMe7goQ3lTfQ13Vw4qY/Nj0979BGvMRpAYbs/BAxEvU8ew==", - "os": ["win32"], - "cpu": ["ia32"] + "@esbuild/win32-ia32@0.25.4": { + "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==" }, "@esbuild/win32-x64@0.18.20": { - "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", - "os": ["win32"], - "cpu": ["x64"] + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==" }, - "@esbuild/win32-x64@0.25.3": { - "integrity": "sha512-ICgUR+kPimx0vvRzf+N/7L7tVSQeE3BYY+NhHRHXS1kBuPO7z2+7ea2HbhDyZdTephgvNvKrlDDKUexuCVBVvg==", - "os": ["win32"], - "cpu": ["x64"] + "@esbuild/win32-x64@0.25.4": { + "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==" }, - "@langchain/core@0.3.53_zod@3.24.3": { + "@langchain/core@0.3.53_zod@3.24.4": { "integrity": "sha512-rHlBcEG5PNaWxlVhPTLiZ0WRCr/URNEUynhgZTZ8QbTJhQ1vEMibdr2YL9LYKHSXNyAp/b5j3itcu3epB8FD7Q==", "dependencies": [ "@cfworker/json-schema", @@ -285,136 +185,46 @@ "mustache", "p-queue", "p-retry", - "uuid@10.0.0", - "zod@3.24.3", + "uuid", + "zod", "zod-to-json-schema" ] }, - "@langchain/langgraph-checkpoint@0.0.17_@langchain+core@0.3.53__zod@3.24.3": { - "integrity": "sha512-6b3CuVVYx+7x0uWLG+7YXz9j2iBa+tn2AXvkLxzEvaAsLE6Sij++8PPbS2BZzC+S/FPJdWsz6I5bsrqL0BYrCA==", - "dependencies": [ - "@langchain/core", - "uuid@10.0.0" - ] - }, - "@langchain/langgraph-sdk@0.0.74_@langchain+core@0.3.53__zod@3.24.3": { - "integrity": "sha512-IUN0m4BYkGWdviFd4EaWDcQgxNq8z+1LIwXajCSt9B+Cb/pz0ZNpIPdu5hAIsf6a0RWu5yRUhzL1L40t7vu3Zg==", - "dependencies": [ - "@langchain/core", - "@types/json-schema", - "p-queue", - "p-retry", - "uuid@9.0.1" - ], - "optionalPeers": [ - "@langchain/core" - ] - }, - "@langchain/langgraph@0.2.68_@langchain+core@0.3.53__zod@3.24.3": { - "integrity": "sha512-wxdGmOeRUQutCWfdKwFb+92RMdZZ8jvXkG7PD3ZcVLiuOXhC9LSe5xcc1UbWHcV29fV542IDHMfvf2OVdv9dZQ==", - "dependencies": [ - "@langchain/core", - "@langchain/langgraph-checkpoint", - "@langchain/langgraph-sdk", - "uuid@10.0.0", - "zod@3.24.3" - ] - }, - "@langchain/mistralai@0.2.0_@langchain+core@0.3.53__zod@3.24.3_zod@3.24.3": { + "@langchain/mistralai@0.2.0_@langchain+core@0.3.53__zod@3.24.4_zod@3.24.4": { "integrity": "sha512-VdfbKZopAuSXf/vlXbriGWLK3c7j5s47DoB3S31xpprY2BMSKZZiX9vE9TsgxMfAPuIDPIYcfgU7p1upvTYt8g==", "dependencies": [ "@langchain/core", "@mistralai/mistralai", - "uuid@10.0.0", - "zod@3.24.3", + "uuid", + "zod", "zod-to-json-schema" ] }, - "@mistralai/mistralai@1.6.0_zod@3.24.3": { + "@mistralai/mistralai@1.6.0_zod@3.24.4": { "integrity": "sha512-PQwGV3+n7FbE7Dp3Vnd8DAa3ffx6WuVV966Gfmf4QvzwcO3Mvxpz0SnJ/PjaZcsCwApBCZpNyQzvarAKEQLKeQ==", "dependencies": [ - "zod@3.24.3", + "zod", "zod-to-json-schema" ] }, "@socket.io/component-emitter@3.1.2": { "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" }, - "@types/body-parser@1.19.5": { - "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", - "dependencies": [ - "@types/connect", - "@types/node" - ] - }, - "@types/connect@3.4.38": { - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dependencies": [ - "@types/node" - ] - }, "@types/cors@2.8.17": { "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", "dependencies": [ "@types/node" ] }, - "@types/express-serve-static-core@5.0.6": { - "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==", - "dependencies": [ - "@types/node", - "@types/qs", - "@types/range-parser", - "@types/send" - ] - }, - "@types/express@5.0.1": { - "integrity": "sha512-UZUw8vjpWFXuDnjFTh7/5c2TWDlQqeXHi6hcN7F2XSVT5P+WmUnnbFS3KA6Jnc6IsEqI2qCVu2bK0R0J4A8ZQQ==", - "dependencies": [ - "@types/body-parser", - "@types/express-serve-static-core", - "@types/serve-static" - ] - }, - "@types/http-errors@2.0.4": { - "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==" - }, - "@types/json-schema@7.0.15": { - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" - }, - "@types/mime@1.3.5": { - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" - }, "@types/node@22.12.0": { "integrity": "sha512-Fll2FZ1riMjNmlmJOdAyY5pUbkftXslB5DgEzlIuNaiWhXd00FhWxVC/r4yV/4wBb9JfImTu+jiSvXTkJ7F/gA==", "dependencies": [ "undici-types" ] }, - "@types/qs@6.9.18": { - "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==" - }, - "@types/range-parser@1.2.7": { - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" - }, "@types/retry@0.12.0": { "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" }, - "@types/send@0.17.4": { - "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", - "dependencies": [ - "@types/mime", - "@types/node" - ] - }, - "@types/serve-static@1.15.7": { - "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", - "dependencies": [ - "@types/http-errors", - "@types/node", - "@types/send" - ] - }, "@types/uuid@10.0.0": { "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==" }, @@ -546,15 +356,14 @@ "depd@2.0.0": { "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" }, - "drizzle-kit@0.31.0_esbuild@0.25.3": { - "integrity": "sha512-pcKVT+GbfPA+bUovPIilgVOoq+onNBo/YQBG86sf3/GFHkN6lRJPm1l7dKN0IMAk57RQoIm4GUllRrasLlcaSg==", + "drizzle-kit@0.31.1_esbuild@0.25.4": { + "integrity": "sha512-PUjYKWtzOzPtdtQlTHQG3qfv4Y0XT8+Eas6UbxCmxTj7qgMf+39dDujf1BP1I+qqZtw9uzwTh8jYtkMuCq+B0Q==", "dependencies": [ "@drizzle-team/brocli", "@esbuild-kit/esm-loader", - "esbuild@0.25.3", - "esbuild-register" - ], - "bin": true + "esbuild-register", + "esbuild@0.25.4" + ] }, "dunder-proto@1.0.1": { "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", @@ -570,16 +379,6 @@ "encodeurl@2.0.0": { "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" }, - "engine.io-client@6.6.3": { - "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==", - "dependencies": [ - "@socket.io/component-emitter", - "debug@4.3.7", - "engine.io-parser", - "ws", - "xmlhttprequest-ssl" - ] - }, "engine.io-parser@5.2.3": { "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==" }, @@ -609,25 +408,25 @@ "es-errors" ] }, - "esbuild-register@3.6.0_esbuild@0.25.3": { + "esbuild-register@3.6.0_esbuild@0.25.4": { "integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==", "dependencies": [ "debug@4.4.0", - "esbuild@0.25.3" + "esbuild@0.25.4" ] }, "esbuild@0.18.20": { "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", - "optionalDependencies": [ - "@esbuild/android-arm@0.18.20", + "dependencies": [ "@esbuild/android-arm64@0.18.20", + "@esbuild/android-arm@0.18.20", "@esbuild/android-x64@0.18.20", "@esbuild/darwin-arm64@0.18.20", "@esbuild/darwin-x64@0.18.20", "@esbuild/freebsd-arm64@0.18.20", "@esbuild/freebsd-x64@0.18.20", - "@esbuild/linux-arm@0.18.20", "@esbuild/linux-arm64@0.18.20", + "@esbuild/linux-arm@0.18.20", "@esbuild/linux-ia32@0.18.20", "@esbuild/linux-loong64@0.18.20", "@esbuild/linux-mips64el@0.18.20", @@ -641,41 +440,37 @@ "@esbuild/win32-arm64@0.18.20", "@esbuild/win32-ia32@0.18.20", "@esbuild/win32-x64@0.18.20" - ], - "scripts": true, - "bin": true + ] }, - "esbuild@0.25.3": { - "integrity": "sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q==", - "optionalDependencies": [ + "esbuild@0.25.4": { + "integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==", + "dependencies": [ "@esbuild/aix-ppc64", - "@esbuild/android-arm@0.25.3", - "@esbuild/android-arm64@0.25.3", - "@esbuild/android-x64@0.25.3", - "@esbuild/darwin-arm64@0.25.3", - "@esbuild/darwin-x64@0.25.3", - "@esbuild/freebsd-arm64@0.25.3", - "@esbuild/freebsd-x64@0.25.3", - "@esbuild/linux-arm@0.25.3", - "@esbuild/linux-arm64@0.25.3", - "@esbuild/linux-ia32@0.25.3", - "@esbuild/linux-loong64@0.25.3", - "@esbuild/linux-mips64el@0.25.3", - "@esbuild/linux-ppc64@0.25.3", - "@esbuild/linux-riscv64@0.25.3", - "@esbuild/linux-s390x@0.25.3", - "@esbuild/linux-x64@0.25.3", + "@esbuild/android-arm64@0.25.4", + "@esbuild/android-arm@0.25.4", + "@esbuild/android-x64@0.25.4", + "@esbuild/darwin-arm64@0.25.4", + "@esbuild/darwin-x64@0.25.4", + "@esbuild/freebsd-arm64@0.25.4", + "@esbuild/freebsd-x64@0.25.4", + "@esbuild/linux-arm64@0.25.4", + "@esbuild/linux-arm@0.25.4", + "@esbuild/linux-ia32@0.25.4", + "@esbuild/linux-loong64@0.25.4", + "@esbuild/linux-mips64el@0.25.4", + "@esbuild/linux-ppc64@0.25.4", + "@esbuild/linux-riscv64@0.25.4", + "@esbuild/linux-s390x@0.25.4", + "@esbuild/linux-x64@0.25.4", "@esbuild/netbsd-arm64", - "@esbuild/netbsd-x64@0.25.3", + "@esbuild/netbsd-x64@0.25.4", "@esbuild/openbsd-arm64", - "@esbuild/openbsd-x64@0.25.3", - "@esbuild/sunos-x64@0.25.3", - "@esbuild/win32-arm64@0.25.3", - "@esbuild/win32-ia32@0.25.3", - "@esbuild/win32-x64@0.25.3" - ], - "scripts": true, - "bin": true + "@esbuild/openbsd-x64@0.25.4", + "@esbuild/sunos-x64@0.25.4", + "@esbuild/win32-arm64@0.25.4", + "@esbuild/win32-ia32@0.25.4", + "@esbuild/win32-x64@0.25.4" + ] }, "escape-html@1.0.3": { "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" @@ -821,7 +616,7 @@ "p-queue", "p-retry", "semver", - "uuid@10.0.0" + "uuid" ] }, "math-intrinsics@1.1.0": { @@ -855,8 +650,7 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "mustache@4.2.0": { - "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", - "bin": true + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==" }, "negotiator@0.6.3": { "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" @@ -959,8 +753,7 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "semver@7.7.1": { - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", - "bin": true + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==" }, "send@1.2.0": { "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", @@ -1036,15 +829,6 @@ "ws" ] }, - "socket.io-client@4.8.1": { - "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", - "dependencies": [ - "@socket.io/component-emitter", - "debug@4.3.7", - "engine.io-client", - "socket.io-parser" - ] - }, "socket.io-parser@4.2.4": { "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", "dependencies": [ @@ -1101,16 +885,7 @@ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" }, "uuid@10.0.0": { - "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", - "bin": true - }, - "uuid@11.1.0": { - "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", - "bin": true - }, - "uuid@9.0.1": { - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "bin": true + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==" }, "vary@1.1.2": { "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" @@ -1121,18 +896,12 @@ "ws@8.17.1": { "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==" }, - "xmlhttprequest-ssl@2.1.2": { - "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==" - }, - "zod-to-json-schema@3.24.5_zod@3.24.3": { + "zod-to-json-schema@3.24.5_zod@3.24.4": { "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", "dependencies": [ - "zod@3.24.3" + "zod" ] }, - "zod@3.24.3": { - "integrity": "sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==" - }, "zod@3.24.4": { "integrity": "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==" } diff --git a/src/lc/model.ts b/src/lc/model.ts index 9bb3474..dd23e37 100644 --- a/src/lc/model.ts +++ b/src/lc/model.ts @@ -1,22 +1,58 @@ import { HumanMessage, SystemMessage } from "@langchain/core/messages"; import { ChatMistralAI } from "@langchain/mistralai"; -import { tool } from "@langchain/core/tools"; -import { z } from "zod"; +import { DynamicStructuredTool } from "npm:@langchain/core/tools"; +import { cancelOrder, createOrder, getOrder } from "./tools.ts"; const llm = new ChatMistralAI({ model: "mistral-small-latest", temperature: 0, }); +const tools: DynamicStructuredTool[] = [getOrder, createOrder, cancelOrder]; + +const llmWithTools = llm.bindTools(tools); + const messages = [ new SystemMessage( - "Você é um assistente de um chatbot chamado 'PoliEats', sua função é prover informações sobre o estabelecimento como: Cardápio, horários de funcionamento, receber pedidos e prover status dos pedidos em andamento. Você responde apenas em português", + "Você é um assistente de um chatbot chamado 'PoliEats', sua função é prover informações sobre o estabelecimento como: Cardápio, horários de funcionamento, receber pedidos e prover status dos pedidos em andamento. Você responde apenas em português. Você tem permissão para se adequar ao perfil do usuário, utilizando gírias e expressões populares.", ), ]; +const toolsByName = tools.reduce((acc, tool) => { + acc[tool.name] = tool; + return acc; +}, {} as Record); + export async function addMessage(message: string) { + // Send the message to the LLM messages.push(new HumanMessage(message)); - return await llm.invoke(messages).then((res) => { - return res.content; - }); + + const firstResponse = await llmWithTools.invoke(messages); + + // Check if the response contains a tool call + if (firstResponse.tool_calls && firstResponse.tool_calls.length > 0) { + messages.push(firstResponse); + for (const toolCall of firstResponse.tool_calls) { + const selectedTool = toolsByName[toolCall.name]; + const toolMessage = await selectedTool.invoke(toolCall); + messages.push(toolMessage); + } + + // Add the final response to the messages + const finalResponse = await llmWithTools.invoke(messages); + messages.push(finalResponse); + + console.log(messages); + return JSON.stringify({ + type: "response", + message: finalResponse.content, + }); + } else { + console.log(messages); + messages.push(firstResponse); + return JSON.stringify({ + type: "response", + message: firstResponse.content, + }); + } } diff --git a/src/lc/tools.ts b/src/lc/tools.ts new file mode 100644 index 0000000..004c4ad --- /dev/null +++ b/src/lc/tools.ts @@ -0,0 +1,97 @@ +import { tool } from "@langchain/core/tools"; +import { z } from "zod"; + +const orders = [ + { + id: 1, + name: 'Order 1', + status: 'pending', + items: [ + { id: 1, name: 'Item 1', quantity: 2 }, + { id: 2, name: 'Item 2', quantity: 1 }, + ], + total: 20.0, + createdAt: new Date(), + updatedAt: new Date(), + } +] + +const availableItems = [ + { id: 1, name: 'Item 1', price: 10.0 }, + { id: 2, name: 'Item 2', price: 5.0 }, + { id: 3, name: 'Item 3', price: 15.0 }, +] + +export const cancelOrder = tool( + ({ orderId }: { orderId: number }) => { + const orderIndex = orders.findIndex(order => order.id === orderId); + if (orderIndex === -1) { + return `Pedido não encontrado.`; + } + + orders.splice(orderIndex, 1); + return `Pedido ${orderId} cancelado com sucesso!`; + }, + { + name: "cancelarPedido", + description: "Cancela um pedido existente.", + schema: z.object({ + orderId: z.number().describe("ID do pedido que deseja cancelar."), + }) + } +) + +export const createOrder = tool( + ({ items }: { items: string[] }) => { + // Parse the items from the input, the input is a string with the item names + const parsedItems = items.map(itemName => { + const item = availableItems.find(i => i.name.toLowerCase() === itemName.toLowerCase()); + if (item) { + return { id: item.id, name: item.name, quantity: 1, price: item.price }; + } else { + return null; + } + }).filter(item => item !== null); + + if (parsedItems.length === 0) { + return `Nenhum item encontrado.`; + } + + const newOrder = { + id: orders.length + 1, + name: `Order ${orders.length + 1}`, + status: 'pending', + items: parsedItems, + total: parsedItems.reduce((acc, item) => acc + item.price * item.quantity, 0), + createdAt: new Date(), + updatedAt: new Date(), + }; + + orders.push(newOrder); + return `Pedido criado com sucesso! ID do pedido: ${newOrder.id}`; + }, + { + name: "criarPedido", + description: "Cria um novo pedido com os itens especificados.", + schema: z.object({ + items: z.array(z.string()).describe("Nome dos itens que deseja adicionar ao pedido."), + }) + } +) + +export const getOrder = tool( + ({ orderId }: { orderId: number }) => { + const order = orders.find(order => order.id === orderId); + if (!order) { + return `Pedido não encontrado.`; + } + return `Pedido encontrado: ${JSON.stringify(order)}`; + }, + { + name: "verPedido", + description: "Verifica o status de um pedido.", + schema: z.object({ + orderId: z.number().describe("ID do pedido que deseja verificar."), + }) + } +) \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index eaa94c8..2263c7f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,19 +1,19 @@ import express from "express"; -import { Server } from "npm:socket.io"; import { createServer } from "node:http"; import cors from "npm:cors"; +import { Server } from "npm:socket.io"; import { addMessage } from "./lc/model.ts"; const app = express(); app.use(cors({ - origin: "http://localhost:5173", + origin: "*", })); const wsServer = createServer(app); const io = new Server(wsServer, { cors: { - origin: "http://localhost:5173", + origin: "*", }, }); @@ -26,7 +26,17 @@ io.on("connection", (socket) => { await addMessage(e).then((final) => { socket.emit("message", final); }); + + // setTimeout(() => { + // socket.emit("message", "Olá, tudo bem? Como posso ajudar?"); + // }, 3000); }); + + socket.emit("message", JSON.stringify({ + type: "welcome", + message: "Olá, tudo bem ?, bem vindo ao PoliEats! Sou um assistente virtual e estou aqui para te ajudar com o que você precisar. Você pode me perguntar sobre o cardápio, horários de funcionamento, fazer pedidos e consultar o status dos pedidos em andamento. Como posso te ajudar hoje? 🤗", + }) + ); }); wsServer.listen(8000); From d5e6688359f1fdab756ab6267131a164b0f975d1 Mon Sep 17 00:00:00 2001 From: Matelz <49626198+Matelz@users.noreply.github.com> Date: Sun, 11 May 2025 16:56:03 -0300 Subject: [PATCH 04/12] feat: started implementing order confirmation --- src/lc/tools.ts | 19 +++++++++++-------- src/main.ts | 17 ++++++++++++----- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/src/lc/tools.ts b/src/lc/tools.ts index 004c4ad..30387ab 100644 --- a/src/lc/tools.ts +++ b/src/lc/tools.ts @@ -1,5 +1,6 @@ import { tool } from "@langchain/core/tools"; import { z } from "zod"; +import { io } from "../main.ts"; const orders = [ { @@ -17,9 +18,9 @@ const orders = [ ] const availableItems = [ - { id: 1, name: 'Item 1', price: 10.0 }, - { id: 2, name: 'Item 2', price: 5.0 }, - { id: 3, name: 'Item 3', price: 15.0 }, + { id: 1, name: 'Hamburguer', price: 10.0 }, + { id: 2, name: 'Pizza', price: 5.0 }, + { id: 3, name: 'Salada', price: 15.0 }, ] export const cancelOrder = tool( @@ -42,7 +43,7 @@ export const cancelOrder = tool( ) export const createOrder = tool( - ({ items }: { items: string[] }) => { + async ({ items }: { items: string[] }) => { // Parse the items from the input, the input is a string with the item names const parsedItems = items.map(itemName => { const item = availableItems.find(i => i.name.toLowerCase() === itemName.toLowerCase()); @@ -56,7 +57,7 @@ export const createOrder = tool( if (parsedItems.length === 0) { return `Nenhum item encontrado.`; } - + const newOrder = { id: orders.length + 1, name: `Order ${orders.length + 1}`, @@ -66,9 +67,11 @@ export const createOrder = tool( createdAt: new Date(), updatedAt: new Date(), }; - - orders.push(newOrder); - return `Pedido criado com sucesso! ID do pedido: ${newOrder.id}`; + + io.emit("message", JSON.stringify({ + type: "order", + order: newOrder, + })); }, { name: "criarPedido", diff --git a/src/main.ts b/src/main.ts index 2263c7f..09784bc 100644 --- a/src/main.ts +++ b/src/main.ts @@ -11,7 +11,7 @@ app.use(cors({ })); const wsServer = createServer(app); -const io = new Server(wsServer, { +export const io = new Server(wsServer, { cors: { origin: "*", }, @@ -22,14 +22,12 @@ app.get("/", (_req, res) => { }); io.on("connection", (socket) => { + const pendingConfirmations = new Map(); + socket.on("message", async (e) => { await addMessage(e).then((final) => { socket.emit("message", final); }); - - // setTimeout(() => { - // socket.emit("message", "Olá, tudo bem? Como posso ajudar?"); - // }, 3000); }); socket.emit("message", JSON.stringify({ @@ -37,6 +35,15 @@ io.on("connection", (socket) => { message: "Olá, tudo bem ?, bem vindo ao PoliEats! Sou um assistente virtual e estou aqui para te ajudar com o que você precisar. Você pode me perguntar sobre o cardápio, horários de funcionamento, fazer pedidos e consultar o status dos pedidos em andamento. Como posso te ajudar hoje? 🤗", }) ); + + socket.on("order_confirmation", (e) => { + const { orderId, confirmation } = e; + if (pendingConfirmations.has(orderId)) { + const resolve = pendingConfirmations.get(orderId); + pendingConfirmations.delete(orderId); + resolve(confirmation); + } + }) }); wsServer.listen(8000); From b365b5cd17c1878444f39e9d3423ae1ff931bac4 Mon Sep 17 00:00:00 2001 From: "Mateus S. Pereira" Date: Mon, 12 May 2025 10:23:36 -0300 Subject: [PATCH 05/12] feat: add observation field to order item --- deno.json | 34 ++++---- drizzle.config.ts | 11 +-- src/lc/model.ts | 4 +- src/lc/tools.ts | 196 ++++++++++++++++++++++++++-------------------- src/main.ts | 20 ++--- 5 files changed, 144 insertions(+), 121 deletions(-) diff --git a/deno.json b/deno.json index a32e1b0..9813d9b 100644 --- a/deno.json +++ b/deno.json @@ -1,18 +1,18 @@ { - "tasks": { - "dev": "deno run --allow-net ./src/main.ts" - }, - "imports": { - "@langchain/core": "npm:@langchain/core@^0.3.53", - "@langchain/langgraph": "npm:@langchain/langgraph@^0.2.68", - "@types/express": "npm:@types/express@^5.0.1", - "cors": "npm:cors@^2.8.5", - "drizzle-kit": "npm:drizzle-kit@^0.31.0", - "express": "npm:express@^5.1.0", - "@langchain/mistralai": "npm:@langchain/mistralai@^0.2.0", - "socket.io": "npm:socket.io@^4.8.1", - "socket.io-client": "npm:socket.io-client@^4.8.1", - "uuid": "npm:uuid@^11.1.0", - "zod": "npm:zod@^3.24.4" - } -} \ No newline at end of file + "tasks": { + "dev": "deno run --allow-net ./src/main.ts" + }, + "imports": { + "@langchain/core": "npm:@langchain/core@^0.3.53", + "@langchain/langgraph": "npm:@langchain/langgraph@^0.2.68", + "@types/express": "npm:@types/express@^5.0.1", + "cors": "npm:cors@^2.8.5", + "drizzle-kit": "npm:drizzle-kit@^0.31.0", + "express": "npm:express@^5.1.0", + "@langchain/mistralai": "npm:@langchain/mistralai@^0.2.0", + "socket.io": "npm:socket.io@^4.8.1", + "socket.io-client": "npm:socket.io-client@^4.8.1", + "uuid": "npm:uuid@^11.1.0", + "zod": "npm:zod@^3.24.4" + } +} diff --git a/drizzle.config.ts b/drizzle.config.ts index d6f7756..b041ca5 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -1,10 +1,11 @@ -import { defineConfig } from 'drizzle-kit'; +import { defineConfig } from "drizzle-kit"; export default defineConfig({ - out: './drizzle', - schema: './src/db/schema.ts', - dialect: 'postgresql', + out: "./drizzle", + schema: "./src/db/schema.ts", + dialect: "postgresql", dbCredentials: { - url: Deno.env.get('DATABASE_URL') || 'postgres://postgres:password@localhost:5432/postgres', + url: Deno.env.get("DATABASE_URL") || + "postgres://postgres:password@localhost:5432/postgres", }, }); diff --git a/src/lc/model.ts b/src/lc/model.ts index dd23e37..28f8748 100644 --- a/src/lc/model.ts +++ b/src/lc/model.ts @@ -28,7 +28,7 @@ export async function addMessage(message: string) { messages.push(new HumanMessage(message)); const firstResponse = await llmWithTools.invoke(messages); - + // Check if the response contains a tool call if (firstResponse.tool_calls && firstResponse.tool_calls.length > 0) { messages.push(firstResponse); @@ -41,7 +41,7 @@ export async function addMessage(message: string) { // Add the final response to the messages const finalResponse = await llmWithTools.invoke(messages); messages.push(finalResponse); - + console.log(messages); return JSON.stringify({ type: "response", diff --git a/src/lc/tools.ts b/src/lc/tools.ts index 30387ab..755490d 100644 --- a/src/lc/tools.ts +++ b/src/lc/tools.ts @@ -3,98 +3,128 @@ import { z } from "zod"; import { io } from "../main.ts"; const orders = [ - { - id: 1, - name: 'Order 1', - status: 'pending', - items: [ - { id: 1, name: 'Item 1', quantity: 2 }, - { id: 2, name: 'Item 2', quantity: 1 }, - ], - total: 20.0, - createdAt: new Date(), - updatedAt: new Date(), - } -] + { + id: 1, + name: "Order 1", + status: "pending", + items: [ + { id: 1, name: "Item 1", quantity: 2 }, + { id: 2, name: "Item 2", quantity: 1 }, + ], + total: 20.0, + createdAt: new Date(), + updatedAt: new Date(), + }, +]; const availableItems = [ - { id: 1, name: 'Hamburguer', price: 10.0 }, - { id: 2, name: 'Pizza', price: 5.0 }, - { id: 3, name: 'Salada', price: 15.0 }, -] + { id: 1, name: "Hamburguer", price: 10.0 }, + { id: 2, name: "Pizza", price: 5.0 }, + { id: 3, name: "Salada", price: 15.0 }, +]; export const cancelOrder = tool( - ({ orderId }: { orderId: number }) => { - const orderIndex = orders.findIndex(order => order.id === orderId); - if (orderIndex === -1) { - return `Pedido não encontrado.`; - } - - orders.splice(orderIndex, 1); - return `Pedido ${orderId} cancelado com sucesso!`; - }, - { - name: "cancelarPedido", - description: "Cancela um pedido existente.", - schema: z.object({ - orderId: z.number().describe("ID do pedido que deseja cancelar."), - }) + ({ orderId }: { orderId: number }) => { + const orderIndex = orders.findIndex((order) => order.id === orderId); + if (orderIndex === -1) { + return `Pedido não encontrado.`; } -) -export const createOrder = tool( - async ({ items }: { items: string[] }) => { - // Parse the items from the input, the input is a string with the item names - const parsedItems = items.map(itemName => { - const item = availableItems.find(i => i.name.toLowerCase() === itemName.toLowerCase()); - if (item) { - return { id: item.id, name: item.name, quantity: 1, price: item.price }; - } else { - return null; - } - }).filter(item => item !== null); + orders.splice(orderIndex, 1); + return `Pedido ${orderId} cancelado com sucesso!`; + }, + { + name: "cancelarPedido", + description: "Cancela um pedido existente.", + schema: z.object({ + orderId: z.number().describe("ID do pedido que deseja cancelar."), + }), + }, +); - if (parsedItems.length === 0) { - return `Nenhum item encontrado.`; - } - - const newOrder = { - id: orders.length + 1, - name: `Order ${orders.length + 1}`, - status: 'pending', - items: parsedItems, - total: parsedItems.reduce((acc, item) => acc + item.price * item.quantity, 0), - createdAt: new Date(), - updatedAt: new Date(), +export const createOrder = tool( + async ({ items }: { + items: { + name: string; + quantity: number; + observation?: string; + }[]; + }) => { + // Parse the items from the input, the input is a string with the item names + const parsedItems = items.map((itemName) => { + const item = availableItems.find((i) => + i.name.toLowerCase() === itemName.name.toLowerCase() + ); + if (item) { + return { + id: item.id, + name: item.name, + quantity: itemName.quantity, + price: item.price, + observation: itemName.observation, }; - - io.emit("message", JSON.stringify({ - type: "order", - order: newOrder, - })); - }, - { - name: "criarPedido", - description: "Cria um novo pedido com os itens especificados.", - schema: z.object({ - items: z.array(z.string()).describe("Nome dos itens que deseja adicionar ao pedido."), - }) + } else { + return null; + } + }).filter((item) => item !== null); + + if (parsedItems.length === 0) { + return `Nenhum item encontrado.`; } -) + + const newOrder = { + id: orders.length + 1, + name: `Order ${orders.length + 1}`, + status: "pending", + items: parsedItems, + total: parsedItems.reduce( + (acc, item) => acc + item.price * item.quantity, + 0, + ), + createdAt: new Date(), + updatedAt: new Date(), + }; + + io.emit( + "message", + JSON.stringify({ + type: "order", + order: newOrder, + }), + ); + }, + { + name: "criarPedido", + description: "Cria um novo pedido com os itens especificados.", + schema: z.object({ + items: z.array( + z.object({ + name: z.string().describe("Nome do item"), + quantity: z.number().describe("Quantidade do determinado item"), + observation: z.string().optional().describe( + "Observações sobre o item", + ), + }), + ).describe( + "Uma lista contendo as informações de cada item, como: nome do item, quantidade desse item e alguma possível observação", + ), + }), + }, +); export const getOrder = tool( - ({ orderId }: { orderId: number }) => { - const order = orders.find(order => order.id === orderId); - if (!order) { - return `Pedido não encontrado.`; - } - return `Pedido encontrado: ${JSON.stringify(order)}`; - }, - { - name: "verPedido", - description: "Verifica o status de um pedido.", - schema: z.object({ - orderId: z.number().describe("ID do pedido que deseja verificar."), - }) + ({ orderId }: { orderId: number }) => { + const order = orders.find((order) => order.id === orderId); + if (!order) { + return `Pedido não encontrado.`; } -) \ No newline at end of file + return `Pedido encontrado: ${JSON.stringify(order)}`; + }, + { + name: "verPedido", + description: "Verifica o status de um pedido.", + schema: z.object({ + orderId: z.number().describe("ID do pedido que deseja verificar."), + }), + }, +); diff --git a/src/main.ts b/src/main.ts index 09784bc..103206f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -22,28 +22,20 @@ app.get("/", (_req, res) => { }); io.on("connection", (socket) => { - const pendingConfirmations = new Map(); - socket.on("message", async (e) => { await addMessage(e).then((final) => { socket.emit("message", final); }); }); - socket.emit("message", JSON.stringify({ + socket.emit( + "message", + JSON.stringify({ type: "welcome", - message: "Olá, tudo bem ?, bem vindo ao PoliEats! Sou um assistente virtual e estou aqui para te ajudar com o que você precisar. Você pode me perguntar sobre o cardápio, horários de funcionamento, fazer pedidos e consultar o status dos pedidos em andamento. Como posso te ajudar hoje? 🤗", - }) + message: + "Olá, tudo bem ?, bem vindo ao PoliEats! Sou um assistente virtual e estou aqui para te ajudar com o que você precisar. Você pode me perguntar sobre o cardápio, horários de funcionamento, fazer pedidos e consultar o status dos pedidos em andamento. Como posso te ajudar hoje? 🤗", + }), ); - - socket.on("order_confirmation", (e) => { - const { orderId, confirmation } = e; - if (pendingConfirmations.has(orderId)) { - const resolve = pendingConfirmations.get(orderId); - pendingConfirmations.delete(orderId); - resolve(confirmation); - } - }) }); wsServer.listen(8000); From 0969429b2613f87a5a34acd42d067dfb3624e678 Mon Sep 17 00:00:00 2001 From: Matelz <49626198+Matelz@users.noreply.github.com> Date: Mon, 12 May 2025 16:28:28 -0300 Subject: [PATCH 06/12] feat: add confirmation for orders --- src/lc/tools.ts | 20 +++++++++++++++++++- src/main.ts | 15 +++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/lc/tools.ts b/src/lc/tools.ts index 755490d..ce71bbb 100644 --- a/src/lc/tools.ts +++ b/src/lc/tools.ts @@ -1,6 +1,6 @@ import { tool } from "@langchain/core/tools"; import { z } from "zod"; -import { io } from "../main.ts"; +import { io, pendingConfirmation } from "../main.ts"; const orders = [ { @@ -92,6 +92,24 @@ export const createOrder = tool( order: newOrder, }), ); + + const isConfirmed = await new Promise((resolve) => { + pendingConfirmation.set(newOrder.id, { resolve }); + + setTimeout(() => { + if (pendingConfirmation.has(newOrder.id)) { + pendingConfirmation.delete(newOrder.id); + resolve(false); + } + }, 30000); + }) + + if (isConfirmed) { + orders.push(newOrder); + return `Pedido ${newOrder.id} criado com sucesso!`; + } else { + return `Pedido ${newOrder.id} não confirmado.`; + } }, { name: "criarPedido", diff --git a/src/main.ts b/src/main.ts index 103206f..aadd044 100644 --- a/src/main.ts +++ b/src/main.ts @@ -21,6 +21,8 @@ app.get("/", (_req, res) => { res.send("Welcome to the Dinosaur API!"); }); +export const pendingConfirmation = new Map(); + io.on("connection", (socket) => { socket.on("message", async (e) => { await addMessage(e).then((final) => { @@ -28,6 +30,19 @@ io.on("connection", (socket) => { }); }); + socket.on("order_confirmation", (e) => { + const { orderId, type } = JSON.parse(e); + if (pendingConfirmation.has(orderId)) { + const { resolve } = pendingConfirmation.get(orderId); + pendingConfirmation.delete(orderId); + if (type === "confirm") { + resolve(true); + } else { + resolve(false); + } + } + }); + socket.emit( "message", JSON.stringify({ From e3d497e2255938ba02d6d7751d94fe4fb38682fc Mon Sep 17 00:00:00 2001 From: Matelz <49626198+Matelz@users.noreply.github.com> Date: Mon, 12 May 2025 23:37:53 -0300 Subject: [PATCH 07/12] feat: add types and new order handler --- deno.json | 1 + deno.lock | 374 +++++++++++++++++++++++++++++++++++++++--------- src/lc/tools.ts | 8 ++ src/main.ts | 2 + 4 files changed, 317 insertions(+), 68 deletions(-) diff --git a/deno.json b/deno.json index 9813d9b..08a0ff1 100644 --- a/deno.json +++ b/deno.json @@ -5,6 +5,7 @@ "imports": { "@langchain/core": "npm:@langchain/core@^0.3.53", "@langchain/langgraph": "npm:@langchain/langgraph@^0.2.68", + "@types/cors": "npm:@types/cors@^2.8.18", "@types/express": "npm:@types/express@^5.0.1", "cors": "npm:cors@^2.8.5", "drizzle-kit": "npm:drizzle-kit@^0.31.0", diff --git a/deno.lock b/deno.lock index f916256..aaa0753 100644 --- a/deno.lock +++ b/deno.lock @@ -1,14 +1,21 @@ { - "version": "4", + "version": "5", "specifiers": { "npm:@langchain/core@*": "0.3.53_zod@3.24.4", "npm:@langchain/core@~0.3.53": "0.3.53_zod@3.24.4", + "npm:@langchain/langgraph@~0.2.68": "0.2.68_@langchain+core@0.3.53__zod@3.24.4", "npm:@langchain/mistralai@0.2": "0.2.0_@langchain+core@0.3.53__zod@3.24.4_zod@3.24.4", + "npm:@types/cors@^2.8.18": "2.8.18", + "npm:@types/express@^5.0.1": "5.0.1", "npm:@types/node@*": "22.12.0", "npm:cors@*": "2.8.5", + "npm:cors@^2.8.5": "2.8.5", "npm:drizzle-kit@0.31": "0.31.1_esbuild@0.25.4", "npm:express@^5.1.0": "5.1.0", + "npm:socket.io-client@^4.8.1": "4.8.1", "npm:socket.io@*": "4.8.1", + "npm:socket.io@^4.8.1": "4.8.1", + "npm:uuid@^11.1.0": "11.1.0", "npm:zod@^3.24.4": "3.24.4" }, "npm": { @@ -23,155 +30,251 @@ "dependencies": [ "esbuild@0.18.20", "source-map-support" - ] + ], + "deprecated": true }, "@esbuild-kit/esm-loader@2.6.5": { "integrity": "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==", "dependencies": [ "@esbuild-kit/core-utils", "get-tsconfig" - ] + ], + "deprecated": true }, "@esbuild/aix-ppc64@0.25.4": { - "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==" + "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==", + "os": ["aix"], + "cpu": ["ppc64"] }, "@esbuild/android-arm64@0.18.20": { - "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==" + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "os": ["android"], + "cpu": ["arm64"] }, "@esbuild/android-arm64@0.25.4": { - "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==" + "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==", + "os": ["android"], + "cpu": ["arm64"] }, "@esbuild/android-arm@0.18.20": { - "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==" + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "os": ["android"], + "cpu": ["arm"] }, "@esbuild/android-arm@0.25.4": { - "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==" + "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==", + "os": ["android"], + "cpu": ["arm"] }, "@esbuild/android-x64@0.18.20": { - "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==" + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "os": ["android"], + "cpu": ["x64"] }, "@esbuild/android-x64@0.25.4": { - "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==" + "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==", + "os": ["android"], + "cpu": ["x64"] }, "@esbuild/darwin-arm64@0.18.20": { - "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==" + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "os": ["darwin"], + "cpu": ["arm64"] }, "@esbuild/darwin-arm64@0.25.4": { - "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==" + "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==", + "os": ["darwin"], + "cpu": ["arm64"] }, "@esbuild/darwin-x64@0.18.20": { - "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==" + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "os": ["darwin"], + "cpu": ["x64"] }, "@esbuild/darwin-x64@0.25.4": { - "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==" + "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==", + "os": ["darwin"], + "cpu": ["x64"] }, "@esbuild/freebsd-arm64@0.18.20": { - "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==" + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "os": ["freebsd"], + "cpu": ["arm64"] }, "@esbuild/freebsd-arm64@0.25.4": { - "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==" + "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==", + "os": ["freebsd"], + "cpu": ["arm64"] }, "@esbuild/freebsd-x64@0.18.20": { - "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==" + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "os": ["freebsd"], + "cpu": ["x64"] }, "@esbuild/freebsd-x64@0.25.4": { - "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==" + "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==", + "os": ["freebsd"], + "cpu": ["x64"] }, "@esbuild/linux-arm64@0.18.20": { - "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==" + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "os": ["linux"], + "cpu": ["arm64"] }, "@esbuild/linux-arm64@0.25.4": { - "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==" + "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==", + "os": ["linux"], + "cpu": ["arm64"] }, "@esbuild/linux-arm@0.18.20": { - "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==" + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "os": ["linux"], + "cpu": ["arm"] }, "@esbuild/linux-arm@0.25.4": { - "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==" + "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==", + "os": ["linux"], + "cpu": ["arm"] }, "@esbuild/linux-ia32@0.18.20": { - "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==" + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "os": ["linux"], + "cpu": ["ia32"] }, "@esbuild/linux-ia32@0.25.4": { - "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==" + "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==", + "os": ["linux"], + "cpu": ["ia32"] }, "@esbuild/linux-loong64@0.18.20": { - "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==" + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "os": ["linux"], + "cpu": ["loong64"] }, "@esbuild/linux-loong64@0.25.4": { - "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==" + "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==", + "os": ["linux"], + "cpu": ["loong64"] }, "@esbuild/linux-mips64el@0.18.20": { - "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==" + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "os": ["linux"], + "cpu": ["mips64el"] }, "@esbuild/linux-mips64el@0.25.4": { - "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==" + "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==", + "os": ["linux"], + "cpu": ["mips64el"] }, "@esbuild/linux-ppc64@0.18.20": { - "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==" + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "os": ["linux"], + "cpu": ["ppc64"] }, "@esbuild/linux-ppc64@0.25.4": { - "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==" + "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==", + "os": ["linux"], + "cpu": ["ppc64"] }, "@esbuild/linux-riscv64@0.18.20": { - "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==" + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "os": ["linux"], + "cpu": ["riscv64"] }, "@esbuild/linux-riscv64@0.25.4": { - "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==" + "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==", + "os": ["linux"], + "cpu": ["riscv64"] }, "@esbuild/linux-s390x@0.18.20": { - "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==" + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "os": ["linux"], + "cpu": ["s390x"] }, "@esbuild/linux-s390x@0.25.4": { - "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==" + "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==", + "os": ["linux"], + "cpu": ["s390x"] }, "@esbuild/linux-x64@0.18.20": { - "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==" + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "os": ["linux"], + "cpu": ["x64"] }, "@esbuild/linux-x64@0.25.4": { - "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==" + "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==", + "os": ["linux"], + "cpu": ["x64"] }, "@esbuild/netbsd-arm64@0.25.4": { - "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==" + "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==", + "os": ["netbsd"], + "cpu": ["arm64"] }, "@esbuild/netbsd-x64@0.18.20": { - "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==" + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "os": ["netbsd"], + "cpu": ["x64"] }, "@esbuild/netbsd-x64@0.25.4": { - "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==" + "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==", + "os": ["netbsd"], + "cpu": ["x64"] }, "@esbuild/openbsd-arm64@0.25.4": { - "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==" + "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==", + "os": ["openbsd"], + "cpu": ["arm64"] }, "@esbuild/openbsd-x64@0.18.20": { - "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==" + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "os": ["openbsd"], + "cpu": ["x64"] }, "@esbuild/openbsd-x64@0.25.4": { - "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==" + "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==", + "os": ["openbsd"], + "cpu": ["x64"] }, "@esbuild/sunos-x64@0.18.20": { - "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==" + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "os": ["sunos"], + "cpu": ["x64"] }, "@esbuild/sunos-x64@0.25.4": { - "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==" + "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==", + "os": ["sunos"], + "cpu": ["x64"] }, "@esbuild/win32-arm64@0.18.20": { - "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==" + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "os": ["win32"], + "cpu": ["arm64"] }, "@esbuild/win32-arm64@0.25.4": { - "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==" + "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==", + "os": ["win32"], + "cpu": ["arm64"] }, "@esbuild/win32-ia32@0.18.20": { - "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==" + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "os": ["win32"], + "cpu": ["ia32"] }, "@esbuild/win32-ia32@0.25.4": { - "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==" + "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==", + "os": ["win32"], + "cpu": ["ia32"] }, "@esbuild/win32-x64@0.18.20": { - "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==" + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "os": ["win32"], + "cpu": ["x64"] }, "@esbuild/win32-x64@0.25.4": { - "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==" + "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==", + "os": ["win32"], + "cpu": ["x64"] }, "@langchain/core@0.3.53_zod@3.24.4": { "integrity": "sha512-rHlBcEG5PNaWxlVhPTLiZ0WRCr/URNEUynhgZTZ8QbTJhQ1vEMibdr2YL9LYKHSXNyAp/b5j3itcu3epB8FD7Q==", @@ -185,17 +288,47 @@ "mustache", "p-queue", "p-retry", - "uuid", + "uuid@10.0.0", "zod", "zod-to-json-schema" ] }, + "@langchain/langgraph-checkpoint@0.0.17_@langchain+core@0.3.53__zod@3.24.4": { + "integrity": "sha512-6b3CuVVYx+7x0uWLG+7YXz9j2iBa+tn2AXvkLxzEvaAsLE6Sij++8PPbS2BZzC+S/FPJdWsz6I5bsrqL0BYrCA==", + "dependencies": [ + "@langchain/core", + "uuid@10.0.0" + ] + }, + "@langchain/langgraph-sdk@0.0.74_@langchain+core@0.3.53__zod@3.24.4": { + "integrity": "sha512-IUN0m4BYkGWdviFd4EaWDcQgxNq8z+1LIwXajCSt9B+Cb/pz0ZNpIPdu5hAIsf6a0RWu5yRUhzL1L40t7vu3Zg==", + "dependencies": [ + "@langchain/core", + "@types/json-schema", + "p-queue", + "p-retry", + "uuid@9.0.1" + ], + "optionalPeers": [ + "@langchain/core" + ] + }, + "@langchain/langgraph@0.2.68_@langchain+core@0.3.53__zod@3.24.4": { + "integrity": "sha512-wxdGmOeRUQutCWfdKwFb+92RMdZZ8jvXkG7PD3ZcVLiuOXhC9LSe5xcc1UbWHcV29fV542IDHMfvf2OVdv9dZQ==", + "dependencies": [ + "@langchain/core", + "@langchain/langgraph-checkpoint", + "@langchain/langgraph-sdk", + "uuid@10.0.0", + "zod" + ] + }, "@langchain/mistralai@0.2.0_@langchain+core@0.3.53__zod@3.24.4_zod@3.24.4": { "integrity": "sha512-VdfbKZopAuSXf/vlXbriGWLK3c7j5s47DoB3S31xpprY2BMSKZZiX9vE9TsgxMfAPuIDPIYcfgU7p1upvTYt8g==", "dependencies": [ "@langchain/core", "@mistralai/mistralai", - "uuid", + "uuid@10.0.0", "zod", "zod-to-json-schema" ] @@ -210,21 +343,87 @@ "@socket.io/component-emitter@3.1.2": { "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" }, + "@types/body-parser@1.19.5": { + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dependencies": [ + "@types/connect", + "@types/node" + ] + }, + "@types/connect@3.4.38": { + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dependencies": [ + "@types/node" + ] + }, "@types/cors@2.8.17": { "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", "dependencies": [ "@types/node" ] }, + "@types/cors@2.8.18": { + "integrity": "sha512-nX3d0sxJW41CqQvfOzVG1NCTXfFDrDWIghCZncpHeWlVFd81zxB/DLhg7avFg6eHLCRX7ckBmoIIcqa++upvJA==", + "dependencies": [ + "@types/node" + ] + }, + "@types/express-serve-static-core@5.0.6": { + "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==", + "dependencies": [ + "@types/node", + "@types/qs", + "@types/range-parser", + "@types/send" + ] + }, + "@types/express@5.0.1": { + "integrity": "sha512-UZUw8vjpWFXuDnjFTh7/5c2TWDlQqeXHi6hcN7F2XSVT5P+WmUnnbFS3KA6Jnc6IsEqI2qCVu2bK0R0J4A8ZQQ==", + "dependencies": [ + "@types/body-parser", + "@types/express-serve-static-core", + "@types/serve-static" + ] + }, + "@types/http-errors@2.0.4": { + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==" + }, + "@types/json-schema@7.0.15": { + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" + }, + "@types/mime@1.3.5": { + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" + }, "@types/node@22.12.0": { "integrity": "sha512-Fll2FZ1riMjNmlmJOdAyY5pUbkftXslB5DgEzlIuNaiWhXd00FhWxVC/r4yV/4wBb9JfImTu+jiSvXTkJ7F/gA==", "dependencies": [ "undici-types" ] }, + "@types/qs@6.9.18": { + "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==" + }, + "@types/range-parser@1.2.7": { + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" + }, "@types/retry@0.12.0": { "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" }, + "@types/send@0.17.4": { + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dependencies": [ + "@types/mime", + "@types/node" + ] + }, + "@types/serve-static@1.15.7": { + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dependencies": [ + "@types/http-errors", + "@types/node", + "@types/send" + ] + }, "@types/uuid@10.0.0": { "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==" }, @@ -361,9 +560,10 @@ "dependencies": [ "@drizzle-team/brocli", "@esbuild-kit/esm-loader", - "esbuild-register", - "esbuild@0.25.4" - ] + "esbuild@0.25.4", + "esbuild-register" + ], + "bin": true }, "dunder-proto@1.0.1": { "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", @@ -379,13 +579,23 @@ "encodeurl@2.0.0": { "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" }, + "engine.io-client@6.6.3": { + "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==", + "dependencies": [ + "@socket.io/component-emitter", + "debug@4.3.7", + "engine.io-parser", + "ws", + "xmlhttprequest-ssl" + ] + }, "engine.io-parser@5.2.3": { "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==" }, "engine.io@6.6.4": { "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", "dependencies": [ - "@types/cors", + "@types/cors@2.8.17", "@types/node", "accepts@1.3.8", "base64id", @@ -417,16 +627,16 @@ }, "esbuild@0.18.20": { "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", - "dependencies": [ - "@esbuild/android-arm64@0.18.20", + "optionalDependencies": [ "@esbuild/android-arm@0.18.20", + "@esbuild/android-arm64@0.18.20", "@esbuild/android-x64@0.18.20", "@esbuild/darwin-arm64@0.18.20", "@esbuild/darwin-x64@0.18.20", "@esbuild/freebsd-arm64@0.18.20", "@esbuild/freebsd-x64@0.18.20", - "@esbuild/linux-arm64@0.18.20", "@esbuild/linux-arm@0.18.20", + "@esbuild/linux-arm64@0.18.20", "@esbuild/linux-ia32@0.18.20", "@esbuild/linux-loong64@0.18.20", "@esbuild/linux-mips64el@0.18.20", @@ -440,21 +650,23 @@ "@esbuild/win32-arm64@0.18.20", "@esbuild/win32-ia32@0.18.20", "@esbuild/win32-x64@0.18.20" - ] + ], + "scripts": true, + "bin": true }, "esbuild@0.25.4": { "integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==", - "dependencies": [ + "optionalDependencies": [ "@esbuild/aix-ppc64", - "@esbuild/android-arm64@0.25.4", "@esbuild/android-arm@0.25.4", + "@esbuild/android-arm64@0.25.4", "@esbuild/android-x64@0.25.4", "@esbuild/darwin-arm64@0.25.4", "@esbuild/darwin-x64@0.25.4", "@esbuild/freebsd-arm64@0.25.4", "@esbuild/freebsd-x64@0.25.4", - "@esbuild/linux-arm64@0.25.4", "@esbuild/linux-arm@0.25.4", + "@esbuild/linux-arm64@0.25.4", "@esbuild/linux-ia32@0.25.4", "@esbuild/linux-loong64@0.25.4", "@esbuild/linux-mips64el@0.25.4", @@ -470,7 +682,9 @@ "@esbuild/win32-arm64@0.25.4", "@esbuild/win32-ia32@0.25.4", "@esbuild/win32-x64@0.25.4" - ] + ], + "scripts": true, + "bin": true }, "escape-html@1.0.3": { "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" @@ -616,7 +830,7 @@ "p-queue", "p-retry", "semver", - "uuid" + "uuid@10.0.0" ] }, "math-intrinsics@1.1.0": { @@ -650,7 +864,8 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "mustache@4.2.0": { - "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==" + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "bin": true }, "negotiator@0.6.3": { "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" @@ -753,7 +968,8 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "semver@7.7.1": { - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==" + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "bin": true }, "send@1.2.0": { "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", @@ -829,6 +1045,15 @@ "ws" ] }, + "socket.io-client@4.8.1": { + "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "dependencies": [ + "@socket.io/component-emitter", + "debug@4.3.7", + "engine.io-client", + "socket.io-parser" + ] + }, "socket.io-parser@4.2.4": { "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", "dependencies": [ @@ -885,7 +1110,16 @@ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" }, "uuid@10.0.0": { - "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==" + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "bin": true + }, + "uuid@11.1.0": { + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "bin": true + }, + "uuid@9.0.1": { + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "bin": true }, "vary@1.1.2": { "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" @@ -896,6 +1130,9 @@ "ws@8.17.1": { "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==" }, + "xmlhttprequest-ssl@2.1.2": { + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==" + }, "zod-to-json-schema@3.24.5_zod@3.24.4": { "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", "dependencies": [ @@ -911,6 +1148,7 @@ "npm:@langchain/core@~0.3.53", "npm:@langchain/langgraph@~0.2.68", "npm:@langchain/mistralai@0.2", + "npm:@types/cors@^2.8.18", "npm:@types/express@^5.0.1", "npm:cors@^2.8.5", "npm:drizzle-kit@0.31", diff --git a/src/lc/tools.ts b/src/lc/tools.ts index ce71bbb..6c4eb4f 100644 --- a/src/lc/tools.ts +++ b/src/lc/tools.ts @@ -106,6 +106,14 @@ export const createOrder = tool( if (isConfirmed) { orders.push(newOrder); + + io.emit( + "new_order", + JSON.stringify({ + newOrder + }), + ); + return `Pedido ${newOrder.id} criado com sucesso!`; } else { return `Pedido ${newOrder.id} não confirmado.`; diff --git a/src/main.ts b/src/main.ts index aadd044..edbad01 100644 --- a/src/main.ts +++ b/src/main.ts @@ -21,6 +21,8 @@ app.get("/", (_req, res) => { res.send("Welcome to the Dinosaur API!"); }); + +// Handle messages from chat export const pendingConfirmation = new Map(); io.on("connection", (socket) => { From 0e925df3614a817a343c7b4b74092f8e39dbee18 Mon Sep 17 00:00:00 2001 From: Matelz <49626198+Matelz@users.noreply.github.com> Date: Tue, 13 May 2025 15:52:39 -0300 Subject: [PATCH 08/12] feat: add database interface and mock database for testing --- .gitignore | 1 + deno.json | 11 ++- deno.lock | 145 ++++++++++++++++++++++++++++++++- src/database/MockDatabase.ts | 93 +++++++++++++++++++++ src/{db => database}/schema.ts | 0 src/interfaces/IDatabase.ts | 76 +++++++++++++++++ 6 files changed, 322 insertions(+), 4 deletions(-) create mode 100644 .gitignore create mode 100644 src/database/MockDatabase.ts rename src/{db => database}/schema.ts (100%) create mode 100644 src/interfaces/IDatabase.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..40b878d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ \ No newline at end of file diff --git a/deno.json b/deno.json index 08a0ff1..9ac2c1f 100644 --- a/deno.json +++ b/deno.json @@ -7,13 +7,20 @@ "@langchain/langgraph": "npm:@langchain/langgraph@^0.2.68", "@types/cors": "npm:@types/cors@^2.8.18", "@types/express": "npm:@types/express@^5.0.1", + "@types/pg": "npm:@types/pg@^8.15.1", "cors": "npm:cors@^2.8.5", - "drizzle-kit": "npm:drizzle-kit@^0.31.0", + "drizzle-kit": "npm:drizzle-kit@^0.31.1", + "drizzle-orm": "npm:drizzle-orm@^0.43.1", "express": "npm:express@^5.1.0", "@langchain/mistralai": "npm:@langchain/mistralai@^0.2.0", + "pg": "npm:pg@^8.16.0", "socket.io": "npm:socket.io@^4.8.1", "socket.io-client": "npm:socket.io-client@^4.8.1", "uuid": "npm:uuid@^11.1.0", "zod": "npm:zod@^3.24.4" - } + }, + "nodeModulesDir": "auto", + "unstable": [ + "sloppy-imports" + ] } diff --git a/deno.lock b/deno.lock index aaa0753..01ac173 100644 --- a/deno.lock +++ b/deno.lock @@ -8,10 +8,13 @@ "npm:@types/cors@^2.8.18": "2.8.18", "npm:@types/express@^5.0.1": "5.0.1", "npm:@types/node@*": "22.12.0", + "npm:@types/pg@^8.15.1": "8.15.1", "npm:cors@*": "2.8.5", "npm:cors@^2.8.5": "2.8.5", - "npm:drizzle-kit@0.31": "0.31.1_esbuild@0.25.4", + "npm:drizzle-kit@~0.31.1": "0.31.1_esbuild@0.25.4", + "npm:drizzle-orm@~0.43.1": "0.43.1_pg@8.16.0_@types+pg@8.15.1", "npm:express@^5.1.0": "5.1.0", + "npm:pg@^8.16.0": "8.16.0", "npm:socket.io-client@^4.8.1": "4.8.1", "npm:socket.io@*": "4.8.1", "npm:socket.io@^4.8.1": "4.8.1", @@ -400,6 +403,14 @@ "undici-types" ] }, + "@types/pg@8.15.1": { + "integrity": "sha512-YKHrkGWBX5+ivzvOQ66I0fdqsQTsvxqM0AGP2i0XrVZ9DP5VA/deEbTf7VuLPGpY7fJB9uGbkZ6KjVhuHcrTkQ==", + "dependencies": [ + "@types/node", + "pg-protocol", + "pg-types@4.0.2" + ] + }, "@types/qs@6.9.18": { "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==" }, @@ -565,6 +576,29 @@ ], "bin": true }, + "drizzle-orm@0.43.1": { + "integrity": "sha512-dUcDaZtE/zN4RV/xqGrVSMpnEczxd5cIaoDeor7Zst9wOe/HzC/7eAaulywWGYXdDEc9oBPMjayVEDg0ziTLJA==" + }, + "drizzle-orm@0.43.1_pg@8.16.0": { + "integrity": "sha512-dUcDaZtE/zN4RV/xqGrVSMpnEczxd5cIaoDeor7Zst9wOe/HzC/7eAaulywWGYXdDEc9oBPMjayVEDg0ziTLJA==", + "dependencies": [ + "pg" + ], + "optionalPeers": [ + "pg" + ] + }, + "drizzle-orm@0.43.1_pg@8.16.0_@types+pg@8.15.1": { + "integrity": "sha512-dUcDaZtE/zN4RV/xqGrVSMpnEczxd5cIaoDeor7Zst9wOe/HzC/7eAaulywWGYXdDEc9oBPMjayVEDg0ziTLJA==", + "dependencies": [ + "@types/pg", + "pg" + ], + "optionalPeers": [ + "@types/pg", + "pg" + ] + }, "dunder-proto@1.0.1": { "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "dependencies": [ @@ -879,6 +913,9 @@ "object-inspect@1.13.4": { "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==" }, + "obuf@1.1.2": { + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==" + }, "on-finished@2.4.1": { "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dependencies": [ @@ -920,6 +957,101 @@ "path-to-regexp@8.2.0": { "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==" }, + "pg-cloudflare@1.2.5": { + "integrity": "sha512-OOX22Vt0vOSRrdoUPKJ8Wi2OpE/o/h9T8X1s4qSkCedbNah9ei2W2765be8iMVxQUsvgT7zIAT2eIa9fs5+vtg==" + }, + "pg-connection-string@2.9.0": { + "integrity": "sha512-P2DEBKuvh5RClafLngkAuGe9OUlFV7ebu8w1kmaaOgPcpJd1RIFh7otETfI6hAR8YupOLFTY7nuvvIn7PLciUQ==" + }, + "pg-int8@1.0.1": { + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" + }, + "pg-numeric@1.0.2": { + "integrity": "sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==" + }, + "pg-pool@3.10.0_pg@8.16.0": { + "integrity": "sha512-DzZ26On4sQ0KmqnO34muPcmKbhrjmyiO4lCCR0VwEd7MjmiKf5NTg/6+apUEu0NF7ESa37CGzFxH513CoUmWnA==", + "dependencies": [ + "pg" + ] + }, + "pg-protocol@1.10.0": { + "integrity": "sha512-IpdytjudNuLv8nhlHs/UrVBhU0e78J0oIS/0AVdTbWxSOkFUVdsHC/NrorO6nXsQNDTT1kzDSOMJubBQviX18Q==" + }, + "pg-types@2.2.0": { + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "dependencies": [ + "pg-int8", + "postgres-array@2.0.0", + "postgres-bytea@1.0.0", + "postgres-date@1.0.7", + "postgres-interval@1.2.0" + ] + }, + "pg-types@4.0.2": { + "integrity": "sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng==", + "dependencies": [ + "pg-int8", + "pg-numeric", + "postgres-array@3.0.4", + "postgres-bytea@3.0.0", + "postgres-date@2.1.0", + "postgres-interval@3.0.0", + "postgres-range" + ] + }, + "pg@8.16.0": { + "integrity": "sha512-7SKfdvP8CTNXjMUzfcVTaI+TDzBEeaUnVwiVGZQD1Hh33Kpev7liQba9uLd4CfN8r9mCVsD0JIpq03+Unpz+kg==", + "dependencies": [ + "pg-connection-string", + "pg-pool", + "pg-protocol", + "pg-types@2.2.0", + "pgpass" + ], + "optionalDependencies": [ + "pg-cloudflare" + ] + }, + "pgpass@1.0.5": { + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "dependencies": [ + "split2" + ] + }, + "postgres-array@2.0.0": { + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==" + }, + "postgres-array@3.0.4": { + "integrity": "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ==" + }, + "postgres-bytea@1.0.0": { + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==" + }, + "postgres-bytea@3.0.0": { + "integrity": "sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==", + "dependencies": [ + "obuf" + ] + }, + "postgres-date@1.0.7": { + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==" + }, + "postgres-date@2.1.0": { + "integrity": "sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==" + }, + "postgres-interval@1.2.0": { + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "dependencies": [ + "xtend" + ] + }, + "postgres-interval@3.0.0": { + "integrity": "sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==" + }, + "postgres-range@1.1.4": { + "integrity": "sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==" + }, "proxy-addr@2.0.7": { "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "dependencies": [ @@ -1083,6 +1215,9 @@ "source-map@0.6.1": { "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, + "split2@4.2.0": { + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==" + }, "statuses@2.0.1": { "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" }, @@ -1133,6 +1268,9 @@ "xmlhttprequest-ssl@2.1.2": { "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==" }, + "xtend@4.0.2": { + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, "zod-to-json-schema@3.24.5_zod@3.24.4": { "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", "dependencies": [ @@ -1150,9 +1288,12 @@ "npm:@langchain/mistralai@0.2", "npm:@types/cors@^2.8.18", "npm:@types/express@^5.0.1", + "npm:@types/pg@^8.15.1", "npm:cors@^2.8.5", - "npm:drizzle-kit@0.31", + "npm:drizzle-kit@~0.31.1", + "npm:drizzle-orm@~0.43.1", "npm:express@^5.1.0", + "npm:pg@^8.16.0", "npm:socket.io-client@^4.8.1", "npm:socket.io@^4.8.1", "npm:uuid@^11.1.0", diff --git a/src/database/MockDatabase.ts b/src/database/MockDatabase.ts new file mode 100644 index 0000000..291e93c --- /dev/null +++ b/src/database/MockDatabase.ts @@ -0,0 +1,93 @@ +// deno-lint-ignore-file +import { PgTableWithColumns } from "drizzle-orm/pg-core/table"; +import { InferInsertModel, InferSelectModel, TableConfig } from "drizzle-orm/table"; +import { IDatabase } from "../interfaces/IDatabase.ts"; + +export class MockDatabase implements IDatabase { + constructor() { + this.data = new Map(); + } + + private generateId(): string { + return Math.random().toString(36).substring(2, 15); + } + + private generateNumberId(): number { + return Math.floor(Math.random() * 1000000); + } + + private data: Map; + + insert( + _table: PgTableWithColumns, + data: InferInsertModel>, + ): Promise<{ + id: typeof _table["id"]["_"]["data"]; + }> { + let id; + if (typeof _table.id == "number") { + id = this.generateNumberId(); + } + + id = this.generateId(); + this.data.set(id, { ...data, id, table: _table }); + return Promise.resolve({ id }); + } + + update( + _table: PgTableWithColumns, + id: string | number, + data: InferInsertModel>, + ): Promise<{ + id: typeof _table["id"]["_"]["data"]; + }> { + if (this.data.has(id)) { + this.data.set(id, { ...this.data.get(id), ...data }); + return Promise.resolve({ id }); + } + return Promise.reject(new Error("Not found")); + } + + delete( + _table: PgTableWithColumns, + id: string | number, + ): Promise { + if (this.data.has(id)) { + this.data.delete(id); + return Promise.resolve(true); + } + return Promise.resolve(false); + } + + select( + _table: PgTableWithColumns, + id: string | number, + ): Promise> | null> { + if (this.data.has(id)) { + return Promise.resolve(this.data.get(id)); + } + return Promise.resolve(null); + } + + selectAll( + table: PgTableWithColumns, + ): Promise>[]> { + const results: InferSelectModel>[] = []; + this.data.forEach((value) => { + if (value.table === table) { + results.push(value); + } + }); + return Promise.resolve(results); + } + + selectByField(table: PgTableWithColumns, field: keyof InferSelectModel>, value: string | number): Promise>[]> { + const results: InferSelectModel>[] = []; + this.data.forEach((record) => { + if (record.table === table && record[field] === value) { + results.push(record); + } + }); + return Promise.resolve(results); + } +} \ No newline at end of file diff --git a/src/db/schema.ts b/src/database/schema.ts similarity index 100% rename from src/db/schema.ts rename to src/database/schema.ts diff --git a/src/interfaces/IDatabase.ts b/src/interfaces/IDatabase.ts new file mode 100644 index 0000000..acd8314 --- /dev/null +++ b/src/interfaces/IDatabase.ts @@ -0,0 +1,76 @@ +import { InferInsertModel, InferSelectModel } from "drizzle-orm"; +import { PgTableWithColumns, TableConfig } from "drizzle-orm/pg-core"; + +export interface IDatabase { + /** + * Inserts a new record into the specified table. + * @param table - The table to insert the record into. + * @param data - The data to insert. + * @returns The inserted record's ID. + */ + insert( + table: PgTableWithColumns, + data: InferInsertModel>, + ): Promise<{ + id: typeof table["id"]["_"]["data"]; + }>; + + /** + * Updates an existing record in the specified table. + * @param table - The table to update the record in. + * @param id - The ID of the record to update. + * @param data - The data to update. + * @returns The updated record's ID. + */ + update( + table: PgTableWithColumns, + id: string | number, + data: InferInsertModel>, + ): Promise<{ + id: typeof table["id"]["_"]["data"]; + }>; + + /** + * Deletes a record from the specified table. + * @param table - The table to delete the record from. + * @param id - The ID of the record to delete. + * @returns A boolean indicating whether the deletion was successful. + */ + delete( + table: PgTableWithColumns, + id: string | number, + ): Promise; + + /** + * Selects a record from the specified table by its ID. + * @param table - The table to select the record from. + * @param id - The ID of the record to select. + * @returns The selected record or null if not found. + */ + select( + table: PgTableWithColumns, + id: string | number, + ): Promise> | null>; + + /** + * Selects all records from the specified table. + * @param table - The table to select records from. + * @returns An array of selected records. + */ + selectAll( + table: PgTableWithColumns, + ): Promise>[]>; + + /** + * Selects records from the specified table by a specific field and value. + * @param table - The table to select records from. + * @param field - The field to filter by. + * @param value - The value to filter by. + * @returns An array of selected records. + */ + selectByField( + table: PgTableWithColumns, + field: keyof InferSelectModel>, + value: string | number, + ): Promise>[]>; +} \ No newline at end of file From 63cb35aab021728b3758dbda3b773ba96153746e Mon Sep 17 00:00:00 2001 From: Matelz <49626198+Matelz@users.noreply.github.com> Date: Tue, 13 May 2025 16:35:45 -0300 Subject: [PATCH 09/12] feat: add auth service and unit tests --- deno.json | 1 + deno.lock | 35 +++++++++ src/database/schema.ts | 15 ++++ src/interfaces/IUser.ts | 9 +++ src/mocks/User.ts | 18 +++++ src/services/AuthenticationService.ts | 59 ++++++++++++++ tests/authentication.test.ts | 109 ++++++++++++++++++++++++++ 7 files changed, 246 insertions(+) create mode 100644 src/interfaces/IUser.ts create mode 100644 src/mocks/User.ts create mode 100644 src/services/AuthenticationService.ts create mode 100644 tests/authentication.test.ts diff --git a/deno.json b/deno.json index 9ac2c1f..c2c0c79 100644 --- a/deno.json +++ b/deno.json @@ -3,6 +3,7 @@ "dev": "deno run --allow-net ./src/main.ts" }, "imports": { + "@faker-js/faker": "npm:@faker-js/faker@^9.7.0", "@langchain/core": "npm:@langchain/core@^0.3.53", "@langchain/langgraph": "npm:@langchain/langgraph@^0.2.68", "@types/cors": "npm:@types/cors@^2.8.18", diff --git a/deno.lock b/deno.lock index 01ac173..8be2841 100644 --- a/deno.lock +++ b/deno.lock @@ -1,6 +1,12 @@ { "version": "5", "specifiers": { + "jsr:@std/assert@^1.0.10": "1.0.11", + "jsr:@std/assert@^1.0.11": "1.0.11", + "jsr:@std/expect@*": "1.0.13", + "jsr:@std/internal@^1.0.5": "1.0.5", + "jsr:@std/testing@*": "1.0.9", + "npm:@faker-js/faker@^9.7.0": "9.7.0", "npm:@langchain/core@*": "0.3.53_zod@3.24.4", "npm:@langchain/core@~0.3.53": "0.3.53_zod@3.24.4", "npm:@langchain/langgraph@~0.2.68": "0.2.68_@langchain+core@0.3.53__zod@3.24.4", @@ -21,6 +27,31 @@ "npm:uuid@^11.1.0": "11.1.0", "npm:zod@^3.24.4": "3.24.4" }, + "jsr": { + "@std/assert@1.0.11": { + "integrity": "2461ef3c368fe88bc60e186e7744a93112f16fd110022e113a0849e94d1c83c1", + "dependencies": [ + "jsr:@std/internal" + ] + }, + "@std/expect@1.0.13": { + "integrity": "d8e236c7089cd9fcf5e6032f27dadc3db6349d0aee48c15bc71d717bca5baa42", + "dependencies": [ + "jsr:@std/assert@^1.0.11", + "jsr:@std/internal" + ] + }, + "@std/internal@1.0.5": { + "integrity": "54a546004f769c1ac9e025abd15a76b6671ddc9687e2313b67376125650dc7ba" + }, + "@std/testing@1.0.9": { + "integrity": "9bdd4ac07cb13e7594ac30e90f6ceef7254ac83a9aeaa089be0008f33aab5cd4", + "dependencies": [ + "jsr:@std/assert@^1.0.10", + "jsr:@std/internal" + ] + } + }, "npm": { "@cfworker/json-schema@4.1.1": { "integrity": "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==" @@ -279,6 +310,9 @@ "os": ["win32"], "cpu": ["x64"] }, + "@faker-js/faker@9.7.0": { + "integrity": "sha512-aozo5vqjCmDoXLNUJarFZx2IN/GgGaogY4TMJ6so/WLZOWpSV7fvj2dmrV6sEAnUm1O7aCrhTibjpzeDFgNqbg==" + }, "@langchain/core@0.3.53_zod@3.24.4": { "integrity": "sha512-rHlBcEG5PNaWxlVhPTLiZ0WRCr/URNEUynhgZTZ8QbTJhQ1vEMibdr2YL9LYKHSXNyAp/b5j3itcu3epB8FD7Q==", "dependencies": [ @@ -1283,6 +1317,7 @@ }, "workspace": { "dependencies": [ + "npm:@faker-js/faker@^9.7.0", "npm:@langchain/core@~0.3.53", "npm:@langchain/langgraph@~0.2.68", "npm:@langchain/mistralai@0.2", diff --git a/src/database/schema.ts b/src/database/schema.ts index e69de29..5ff7c9a 100644 --- a/src/database/schema.ts +++ b/src/database/schema.ts @@ -0,0 +1,15 @@ +import { pgTable, serial, text, timestamp } from "drizzle-orm/pg-core"; + +export const user = pgTable("user", { + id: serial("id").primaryKey(), + name: text("name").notNull(), + email: text("email").notNull().unique(), + document: text("document").notNull().unique(), + password: text("password").notNull(), + createdAt: timestamp("created_at", { + withTimezone: true, + }).notNull().defaultNow(), + updatedAt: timestamp("updated_at", { + withTimezone: true, + }).notNull().$onUpdate(() => new Date()), +}) \ No newline at end of file diff --git a/src/interfaces/IUser.ts b/src/interfaces/IUser.ts new file mode 100644 index 0000000..e7d04a0 --- /dev/null +++ b/src/interfaces/IUser.ts @@ -0,0 +1,9 @@ +export interface IUser { + id: number; + name: string; + email: string; + document: string; + password: string; + createdAt: Date; + updatedAt: Date; +} \ No newline at end of file diff --git a/src/mocks/User.ts b/src/mocks/User.ts new file mode 100644 index 0000000..970ef4d --- /dev/null +++ b/src/mocks/User.ts @@ -0,0 +1,18 @@ +import { Faker, pt_BR } from "@faker-js/faker"; +import { IUser } from "../interfaces/IUser.ts"; + +export function generateMockUser(): IUser { + const faker = new Faker({ + locale: pt_BR, + }) + + return { + id: faker.number.int({ min: 1, max: 1000 }), + name: faker.person.fullName(), + email: faker.internet.email(), + document: faker.string.numeric(11), + password: faker.internet.password(), + createdAt: faker.date.past(), + updatedAt: faker.date.recent(), + }; +} \ No newline at end of file diff --git a/src/services/AuthenticationService.ts b/src/services/AuthenticationService.ts new file mode 100644 index 0000000..c649c38 --- /dev/null +++ b/src/services/AuthenticationService.ts @@ -0,0 +1,59 @@ +import { user as userTable } from "../database/schema.ts"; +import { IDatabase } from "../interfaces/IDatabase.ts"; +import { IUser } from "../interfaces/IUser.ts"; + +export class AuthenticationService { + private db: IDatabase; + + constructor(db: IDatabase) { + this.db = db; + } + + async registerUser(user: IUser): Promise { + const existingUser = await this.db.selectByField(userTable, "email", user.email); + if (existingUser.length > 0) { + throw new Error("User already exists"); + } + + const newUser = await this.db.insert(userTable, user); + return newUser.id; + } + + async loginUser(email: string, password: string): Promise { + const user = await this.db.selectByField(userTable, "email", email); + if (!user) { + throw new Error("User not found"); + } + + if (user[0].password !== password) { + throw new Error("Invalid password"); + } + + return user[0]; + } + + async updateUser(id: number, user: IUser): Promise { + const existingUser = await this.db.select(userTable, id); + if (!existingUser) { + throw new Error("User not found"); + } + + const updatedUser = await this.db.update(userTable, id, user); + return updatedUser.id; + } + + async deleteUser(id: number): Promise { + const existingUser = await this.db.select(userTable, id); + if (!existingUser) { + throw new Error("User not found"); + } + + const deleted = await this.db.delete(userTable, id); + return deleted; + } + + async getUserById(id: number): Promise { + const user = await this.db.select(userTable, id); + return user; + } +} \ No newline at end of file diff --git a/tests/authentication.test.ts b/tests/authentication.test.ts new file mode 100644 index 0000000..2f95b70 --- /dev/null +++ b/tests/authentication.test.ts @@ -0,0 +1,109 @@ +import { expect } from "jsr:@std/expect"; +import { describe, it } from "jsr:@std/testing/bdd"; +import { MockDatabase } from "../src/database/MockDatabase.ts"; +import { generateMockUser } from "../src/mocks/User.ts"; +import { AuthenticationService } from "../src/services/AuthenticationService.ts"; + +describe("User authentication", () => { + it("should register a new user", async () => { + const db = new MockDatabase(); + const authService = new AuthenticationService(db); + + const user = generateMockUser(); + + const userId = await authService.registerUser(user); + const registeredUser = await authService.getUserById(userId); + + expect(registeredUser).toBeDefined(); + expect(registeredUser?.name).toBe(user.name); + expect(registeredUser?.email).toBe(user.email); + expect(registeredUser?.document).toBe(user.document); + expect(registeredUser?.password).toBe(user.password); + expect(registeredUser?.createdAt).toBeDefined(); + expect(registeredUser?.updatedAt).toBeDefined(); + }); + + it("should not register a user with an existing email", async () => { + const db = new MockDatabase(); + const authService = new AuthenticationService(db); + + const user = generateMockUser(); + + await authService.registerUser(user); + + try { + await authService.registerUser(user); + } catch (error: unknown) { + if (!(error instanceof Error)) { + throw error; + } + expect(error.message).toBe("User already exists"); + } + }); + + it("should login a user with valid credentials", async () => { + const db = new MockDatabase(); + const authService = new AuthenticationService(db); + + const user = generateMockUser(); + + await authService.registerUser(user); + + const loggedInUser = await authService.loginUser(user.email, user.password); + + expect(loggedInUser).toBeDefined(); + expect(loggedInUser?.email).toBe(user.email); + }); + + it("should not login a user with invalid credentials", async () => { + const db = new MockDatabase(); + const authService = new AuthenticationService(db); + + const user = generateMockUser(); + + await authService.registerUser(user); + + try { + await authService.loginUser(user.email, "wrongpassword"); + } catch (error: unknown) { + if (!(error instanceof Error)) { + throw error; + } + expect(error.message).toBe("Invalid password"); + } + }); + + it("should update an existing user", async () => { + const db = new MockDatabase(); + const authService = new AuthenticationService(db); + + const user = generateMockUser(); + + const userId = await authService.registerUser(user); + + const updatedUser = { ...user, name: "Updated Name" }; + await authService.updateUser(userId, updatedUser); + + const fetchedUser = await authService.getUserById(userId); + + expect(fetchedUser).toBeDefined(); + expect(fetchedUser?.name).toBe(updatedUser.name); + }); + + it("should delete an existing user", async () => { + const db = new MockDatabase(); + const authService = new AuthenticationService(db); + + const user = generateMockUser(); + + const userId = await authService.registerUser(user); + + const deleted = await authService.deleteUser(userId); + + expect(deleted).toBe(true); + + const fetchedUser = await authService.getUserById(userId); + + expect(fetchedUser).toBeNull(); + }); +}) \ No newline at end of file From d30bcab3330dd156dd7f8ad79af69b06508a524f Mon Sep 17 00:00:00 2001 From: Matelz <49626198+Matelz@users.noreply.github.com> Date: Tue, 13 May 2025 16:48:13 -0300 Subject: [PATCH 10/12] feat: add jwt token generation and verification --- .env | 6 ++++- deno.json | 1 + deno.lock | 5 ++++ src/services/AuthenticationService.ts | 35 +++++++++++++++++++++++++++ tests/authentication.test.ts | 29 ++++++++++++++++++++++ 5 files changed, 75 insertions(+), 1 deletion(-) diff --git a/.env b/.env index de39eda..dc76c9c 100644 --- a/.env +++ b/.env @@ -1,2 +1,6 @@ +ENVIRONMENT=development + +SECRET_KEY=polieats + DATABASE_URL=postgresql://user:password@localhost/dbname -MISTRAL_API_KEY=Yn7p3GEHvjZ1HV0nLKjw5hNaUYfJn5Oi \ No newline at end of file +MISTRAL_API_KEY=Yn7p3GEHvjZ1HV0nLKjw5hNaUYfJn5Oi diff --git a/deno.json b/deno.json index c2c0c79..8f44fd5 100644 --- a/deno.json +++ b/deno.json @@ -14,6 +14,7 @@ "drizzle-orm": "npm:drizzle-orm@^0.43.1", "express": "npm:express@^5.1.0", "@langchain/mistralai": "npm:@langchain/mistralai@^0.2.0", + "jose": "npm:jose@^6.0.11", "pg": "npm:pg@^8.16.0", "socket.io": "npm:socket.io@^4.8.1", "socket.io-client": "npm:socket.io-client@^4.8.1", diff --git a/deno.lock b/deno.lock index 8be2841..6bd52b7 100644 --- a/deno.lock +++ b/deno.lock @@ -20,6 +20,7 @@ "npm:drizzle-kit@~0.31.1": "0.31.1_esbuild@0.25.4", "npm:drizzle-orm@~0.43.1": "0.43.1_pg@8.16.0_@types+pg@8.15.1", "npm:express@^5.1.0": "5.1.0", + "npm:jose@^6.0.11": "6.0.11", "npm:pg@^8.16.0": "8.16.0", "npm:socket.io-client@^4.8.1": "4.8.1", "npm:socket.io@*": "4.8.1", @@ -883,6 +884,9 @@ "is-promise@4.0.0": { "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" }, + "jose@6.0.11": { + "integrity": "sha512-QxG7EaliDARm1O1S8BGakqncGT9s25bKL1WSf6/oa17Tkqwi8D2ZNglqCF+DsYF88/rV66Q/Q2mFAy697E1DUg==" + }, "js-tiktoken@1.0.20": { "integrity": "sha512-Xlaqhhs8VfCd6Sh7a1cFkZHQbYTLCwVJJWiHVxBYzLPxW0XsoxBy1hitmjkdIjD3Aon5BXLHFwU5O8WUx6HH+A==", "dependencies": [ @@ -1328,6 +1332,7 @@ "npm:drizzle-kit@~0.31.1", "npm:drizzle-orm@~0.43.1", "npm:express@^5.1.0", + "npm:jose@^6.0.11", "npm:pg@^8.16.0", "npm:socket.io-client@^4.8.1", "npm:socket.io@^4.8.1", diff --git a/src/services/AuthenticationService.ts b/src/services/AuthenticationService.ts index c649c38..7a2cd44 100644 --- a/src/services/AuthenticationService.ts +++ b/src/services/AuthenticationService.ts @@ -1,12 +1,16 @@ +import { JWTPayload, jwtVerify, SignJWT } from "jose"; import { user as userTable } from "../database/schema.ts"; import { IDatabase } from "../interfaces/IDatabase.ts"; import { IUser } from "../interfaces/IUser.ts"; export class AuthenticationService { private db: IDatabase; + private secretKey: Uint8Array; constructor(db: IDatabase) { this.db = db; + + this.secretKey = new TextEncoder().encode("poli_eats"); } async registerUser(user: IUser): Promise { @@ -19,6 +23,37 @@ export class AuthenticationService { return newUser.id; } + async createJWT(userId: number): Promise { + const user = await this.getUserById(userId); + + if (!user) { + throw new Error("User not found"); + } + + const payload = { + id: user.id, + name: user.name, + }; + + const jwt = await new SignJWT(payload) + .setProtectedHeader({ alg: "HS256" }) + .setIssuedAt() + .setExpirationTime("2h") + .sign(this.secretKey); + + return jwt; + } + + async verifyJWT(token: string): Promise { + try { + const { payload } = await jwtVerify(token, this.secretKey); + return payload; + } catch (error) { + console.error("JWT verification failed:", error); + return null; + } + } + async loginUser(email: string, password: string): Promise { const user = await this.db.selectByField(userTable, "email", email); if (!user) { diff --git a/tests/authentication.test.ts b/tests/authentication.test.ts index 2f95b70..93e4093 100644 --- a/tests/authentication.test.ts +++ b/tests/authentication.test.ts @@ -106,4 +106,33 @@ describe("User authentication", () => { expect(fetchedUser).toBeNull(); }); + + it("should generate a JWT token for a user", async () => { + const db = new MockDatabase(); + const authService = new AuthenticationService(db); + + const user = generateMockUser(); + + const userId = await authService.registerUser(user); + + const token = await authService.createJWT(userId); + + expect(token).toBeDefined(); + }); + + it("should verify a valid JWT token", async () => { + const db = new MockDatabase(); + const authService = new AuthenticationService(db); + + const user = generateMockUser(); + + const userId = await authService.registerUser(user); + + const token = await authService.createJWT(userId); + + const payload = await authService.verifyJWT(token); + + expect(payload).toBeDefined(); + expect(payload?.id).toBe(userId); + }); }) \ No newline at end of file From eba9d2fe89b52f39dcc9799436fc3ff090560609 Mon Sep 17 00:00:00 2001 From: Matelz <49626198+Matelz@users.noreply.github.com> Date: Tue, 13 May 2025 17:30:41 -0300 Subject: [PATCH 11/12] feat: add hash and salt tables and their functions --- deno.json | 2 ++ deno.lock | 29 ++++++++++++++++ src/database/schema.ts | 25 ++++++++++---- src/interfaces/IUser.ts | 1 - src/mocks/User.ts | 12 ++++++- src/services/AuthenticationService.ts | 42 ++++++++++++++++++---- tests/authentication.test.ts | 50 ++++++++++++++++++++------- 7 files changed, 132 insertions(+), 29 deletions(-) diff --git a/deno.json b/deno.json index 8f44fd5..6611ec0 100644 --- a/deno.json +++ b/deno.json @@ -6,9 +6,11 @@ "@faker-js/faker": "npm:@faker-js/faker@^9.7.0", "@langchain/core": "npm:@langchain/core@^0.3.53", "@langchain/langgraph": "npm:@langchain/langgraph@^0.2.68", + "@types/bcrypt": "npm:@types/bcrypt@^5.0.2", "@types/cors": "npm:@types/cors@^2.8.18", "@types/express": "npm:@types/express@^5.0.1", "@types/pg": "npm:@types/pg@^8.15.1", + "bcrypt": "npm:bcrypt@^6.0.0", "cors": "npm:cors@^2.8.5", "drizzle-kit": "npm:drizzle-kit@^0.31.1", "drizzle-orm": "npm:drizzle-orm@^0.43.1", diff --git a/deno.lock b/deno.lock index 6bd52b7..c66e95c 100644 --- a/deno.lock +++ b/deno.lock @@ -3,6 +3,7 @@ "specifiers": { "jsr:@std/assert@^1.0.10": "1.0.11", "jsr:@std/assert@^1.0.11": "1.0.11", + "jsr:@std/crypto@*": "1.0.4", "jsr:@std/expect@*": "1.0.13", "jsr:@std/internal@^1.0.5": "1.0.5", "jsr:@std/testing@*": "1.0.9", @@ -11,10 +12,12 @@ "npm:@langchain/core@~0.3.53": "0.3.53_zod@3.24.4", "npm:@langchain/langgraph@~0.2.68": "0.2.68_@langchain+core@0.3.53__zod@3.24.4", "npm:@langchain/mistralai@0.2": "0.2.0_@langchain+core@0.3.53__zod@3.24.4_zod@3.24.4", + "npm:@types/bcrypt@^5.0.2": "5.0.2", "npm:@types/cors@^2.8.18": "2.8.18", "npm:@types/express@^5.0.1": "5.0.1", "npm:@types/node@*": "22.12.0", "npm:@types/pg@^8.15.1": "8.15.1", + "npm:bcrypt@6": "6.0.0", "npm:cors@*": "2.8.5", "npm:cors@^2.8.5": "2.8.5", "npm:drizzle-kit@~0.31.1": "0.31.1_esbuild@0.25.4", @@ -35,6 +38,9 @@ "jsr:@std/internal" ] }, + "@std/crypto@1.0.4": { + "integrity": "cee245c453bd5366207f4d8aa25ea3e9c86cecad2be3fefcaa6cb17203d79340" + }, "@std/expect@1.0.13": { "integrity": "d8e236c7089cd9fcf5e6032f27dadc3db6349d0aee48c15bc71d717bca5baa42", "dependencies": [ @@ -381,6 +387,12 @@ "@socket.io/component-emitter@3.1.2": { "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" }, + "@types/bcrypt@5.0.2": { + "integrity": "sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==", + "dependencies": [ + "@types/node" + ] + }, "@types/body-parser@1.19.5": { "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", "dependencies": [ @@ -502,6 +514,14 @@ "base64id@2.0.0": { "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" }, + "bcrypt@6.0.0": { + "integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==", + "dependencies": [ + "node-addon-api", + "node-gyp-build" + ], + "scripts": true + }, "body-parser@2.2.0": { "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", "dependencies": [ @@ -945,6 +965,13 @@ "negotiator@1.0.0": { "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==" }, + "node-addon-api@8.3.1": { + "integrity": "sha512-lytcDEdxKjGJPTLEfW4mYMigRezMlyJY8W4wxJK8zE533Jlb8L8dRuObJFWg2P+AuOIxoCgKF+2Oq4d4Zd0OUA==" + }, + "node-gyp-build@4.8.4": { + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "bin": true + }, "object-assign@4.1.1": { "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" }, @@ -1325,9 +1352,11 @@ "npm:@langchain/core@~0.3.53", "npm:@langchain/langgraph@~0.2.68", "npm:@langchain/mistralai@0.2", + "npm:@types/bcrypt@^5.0.2", "npm:@types/cors@^2.8.18", "npm:@types/express@^5.0.1", "npm:@types/pg@^8.15.1", + "npm:bcrypt@6", "npm:cors@^2.8.5", "npm:drizzle-kit@~0.31.1", "npm:drizzle-orm@~0.43.1", diff --git a/src/database/schema.ts b/src/database/schema.ts index 5ff7c9a..1d07295 100644 --- a/src/database/schema.ts +++ b/src/database/schema.ts @@ -5,11 +5,22 @@ export const user = pgTable("user", { name: text("name").notNull(), email: text("email").notNull().unique(), document: text("document").notNull().unique(), + createdAt: timestamp("created_at").notNull().defaultNow(), + updatedAt: timestamp("updated_at").notNull().$onUpdate(() => new Date()), +}) + +export const salt = pgTable("salt", { + id: serial("id").primaryKey(), + userId: serial("user_id").references(() => user.id), + salt: text("salt").notNull(), + createdAt: timestamp("created_at").notNull().defaultNow(), + updatedAt: timestamp("updated_at").notNull().$onUpdate(() => new Date()), +}); + +export const password = pgTable("password", { + id: serial("id").primaryKey(), + userId: serial("user_id").references(() => user.id), password: text("password").notNull(), - createdAt: timestamp("created_at", { - withTimezone: true, - }).notNull().defaultNow(), - updatedAt: timestamp("updated_at", { - withTimezone: true, - }).notNull().$onUpdate(() => new Date()), -}) \ No newline at end of file + createdAt: timestamp("created_at").notNull().defaultNow(), + updatedAt: timestamp("updated_at").notNull().$onUpdate(() => new Date()), +}); \ No newline at end of file diff --git a/src/interfaces/IUser.ts b/src/interfaces/IUser.ts index e7d04a0..ef4ac71 100644 --- a/src/interfaces/IUser.ts +++ b/src/interfaces/IUser.ts @@ -3,7 +3,6 @@ export interface IUser { name: string; email: string; document: string; - password: string; createdAt: Date; updatedAt: Date; } \ No newline at end of file diff --git a/src/mocks/User.ts b/src/mocks/User.ts index 970ef4d..2b9145c 100644 --- a/src/mocks/User.ts +++ b/src/mocks/User.ts @@ -11,8 +11,18 @@ export function generateMockUser(): IUser { name: faker.person.fullName(), email: faker.internet.email(), document: faker.string.numeric(11), - password: faker.internet.password(), createdAt: faker.date.past(), updatedAt: faker.date.recent(), }; +} + +export function generateMockPassword(): string { + const faker = new Faker({ + locale: pt_BR, + }) + + return faker.internet.password({ + length: 8, + memorable: true, + }); } \ No newline at end of file diff --git a/src/services/AuthenticationService.ts b/src/services/AuthenticationService.ts index 7a2cd44..3969ebf 100644 --- a/src/services/AuthenticationService.ts +++ b/src/services/AuthenticationService.ts @@ -1,5 +1,7 @@ +import bcrypt from "bcrypt"; import { JWTPayload, jwtVerify, SignJWT } from "jose"; -import { user as userTable } from "../database/schema.ts"; +import { JWSInvalid } from "jose/errors"; +import { password as passwordTable, salt as saltTable, user as userTable } from "../database/schema.ts"; import { IDatabase } from "../interfaces/IDatabase.ts"; import { IUser } from "../interfaces/IUser.ts"; @@ -13,13 +15,26 @@ export class AuthenticationService { this.secretKey = new TextEncoder().encode("poli_eats"); } - async registerUser(user: IUser): Promise { + async registerUser(user: IUser, password: string): Promise { const existingUser = await this.db.selectByField(userTable, "email", user.email); if (existingUser.length > 0) { throw new Error("User already exists"); } - + const newUser = await this.db.insert(userTable, user); + + const salt = await bcrypt.genSalt(10); + const hashedPassword = await bcrypt.hash(password, salt); + + await this.db.insert(passwordTable, { + userId: newUser.id, + password: hashedPassword, + }); + + await this.db.insert(saltTable, { + userId: newUser.id, + salt: salt, + }); return newUser.id; } @@ -48,19 +63,32 @@ export class AuthenticationService { try { const { payload } = await jwtVerify(token, this.secretKey); return payload; - } catch (error) { - console.error("JWT verification failed:", error); - return null; + } catch (error: unknown) { + return error instanceof JWSInvalid ? null : Promise.reject(error); } } async loginUser(email: string, password: string): Promise { const user = await this.db.selectByField(userTable, "email", email); + if (!user) { throw new Error("User not found"); } - if (user[0].password !== password) { + const userPassword = await this.db.selectByField(passwordTable, "userId", user[0].id); + if (!userPassword) { + throw new Error("User password not found"); + } + + const salt = await this.db.selectByField(saltTable, "userId", user[0].id); + + if (!salt) { + throw new Error("User salt not found"); + } + + const hashedPassword = await bcrypt.hash(password, salt[0].salt); + + if (userPassword[0].password !== hashedPassword) { throw new Error("Invalid password"); } diff --git a/tests/authentication.test.ts b/tests/authentication.test.ts index 93e4093..9ba7a86 100644 --- a/tests/authentication.test.ts +++ b/tests/authentication.test.ts @@ -1,7 +1,8 @@ +import { JWSInvalid } from "jose/errors"; import { expect } from "jsr:@std/expect"; import { describe, it } from "jsr:@std/testing/bdd"; import { MockDatabase } from "../src/database/MockDatabase.ts"; -import { generateMockUser } from "../src/mocks/User.ts"; +import { generateMockPassword, generateMockUser } from "../src/mocks/User.ts"; import { AuthenticationService } from "../src/services/AuthenticationService.ts"; describe("User authentication", () => { @@ -10,15 +11,15 @@ describe("User authentication", () => { const authService = new AuthenticationService(db); const user = generateMockUser(); + const password = generateMockPassword(); - const userId = await authService.registerUser(user); + const userId = await authService.registerUser(user, password); const registeredUser = await authService.getUserById(userId); expect(registeredUser).toBeDefined(); expect(registeredUser?.name).toBe(user.name); expect(registeredUser?.email).toBe(user.email); expect(registeredUser?.document).toBe(user.document); - expect(registeredUser?.password).toBe(user.password); expect(registeredUser?.createdAt).toBeDefined(); expect(registeredUser?.updatedAt).toBeDefined(); }); @@ -28,11 +29,12 @@ describe("User authentication", () => { const authService = new AuthenticationService(db); const user = generateMockUser(); + const password = generateMockPassword(); - await authService.registerUser(user); + await authService.registerUser(user, password); try { - await authService.registerUser(user); + await authService.registerUser(user, password); } catch (error: unknown) { if (!(error instanceof Error)) { throw error; @@ -46,10 +48,11 @@ describe("User authentication", () => { const authService = new AuthenticationService(db); const user = generateMockUser(); + const password = generateMockPassword(); + + await authService.registerUser(user, password); - await authService.registerUser(user); - - const loggedInUser = await authService.loginUser(user.email, user.password); + const loggedInUser = await authService.loginUser(user.email, password); expect(loggedInUser).toBeDefined(); expect(loggedInUser?.email).toBe(user.email); @@ -60,8 +63,9 @@ describe("User authentication", () => { const authService = new AuthenticationService(db); const user = generateMockUser(); + const password = generateMockPassword(); - await authService.registerUser(user); + await authService.registerUser(user, password); try { await authService.loginUser(user.email, "wrongpassword"); @@ -78,8 +82,9 @@ describe("User authentication", () => { const authService = new AuthenticationService(db); const user = generateMockUser(); + const password = generateMockPassword(); - const userId = await authService.registerUser(user); + const userId = await authService.registerUser(user, password); const updatedUser = { ...user, name: "Updated Name" }; await authService.updateUser(userId, updatedUser); @@ -95,8 +100,9 @@ describe("User authentication", () => { const authService = new AuthenticationService(db); const user = generateMockUser(); + const password = generateMockPassword(); - const userId = await authService.registerUser(user); + const userId = await authService.registerUser(user, password); const deleted = await authService.deleteUser(userId); @@ -112,8 +118,9 @@ describe("User authentication", () => { const authService = new AuthenticationService(db); const user = generateMockUser(); + const password = generateMockPassword(); - const userId = await authService.registerUser(user); + const userId = await authService.registerUser(user, password); const token = await authService.createJWT(userId); @@ -125,8 +132,9 @@ describe("User authentication", () => { const authService = new AuthenticationService(db); const user = generateMockUser(); + const password = generateMockPassword(); - const userId = await authService.registerUser(user); + const userId = await authService.registerUser(user, password); const token = await authService.createJWT(userId); @@ -135,4 +143,20 @@ describe("User authentication", () => { expect(payload).toBeDefined(); expect(payload?.id).toBe(userId); }); + + it("should not verify an invalid JWT token", async () => { + const db = new MockDatabase(); + const authService = new AuthenticationService(db); + + const invalidToken = "invalidtoken"; + + try { + await authService.verifyJWT(invalidToken); + } catch (error: unknown) { + if (!(error instanceof JWSInvalid)) { + throw error; + } + expect(error.code).toBe("ERR_JWS_INVALID"); + } + }); }) \ No newline at end of file From 19f936a8b146bea1ce6ea3e9e521d3904e1cdf02 Mon Sep 17 00:00:00 2001 From: Matelz <49626198+Matelz@users.noreply.github.com> Date: Tue, 13 May 2025 19:14:30 -0300 Subject: [PATCH 12/12] feat: add remainder of unit tests and workflow pipeline --- .env.example | 6 ++ .github/workflows/deno_test.yml | 24 ++++++ README.md | 47 ++++++++++ deno.json | 7 +- deno.lock | 148 +++++++++++++++++++++++++++++++- src/interfaces/IUser.ts | 6 ++ src/main.ts | 69 ++++++++++++++- src/middlewares/ValidateJWT.ts | 28 ++++++ tests/routes.test.ts | 142 ++++++++++++++++++++++++++++++ 9 files changed, 474 insertions(+), 3 deletions(-) create mode 100644 .env.example create mode 100644 .github/workflows/deno_test.yml create mode 100644 README.md create mode 100644 src/middlewares/ValidateJWT.ts create mode 100644 tests/routes.test.ts diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..42745f8 --- /dev/null +++ b/.env.example @@ -0,0 +1,6 @@ +ENVIRONMENT=development + +SECRET_KEY=secret_key_here + +DATABASE_URL=postgresql://user:password@localhost/dbname +MISTRAL_API_KEY=mistral_api_key_here \ No newline at end of file diff --git a/.github/workflows/deno_test.yml b/.github/workflows/deno_test.yml new file mode 100644 index 0000000..ce2b46f --- /dev/null +++ b/.github/workflows/deno_test.yml @@ -0,0 +1,24 @@ +name: CI/Deno Test + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up Deno + uses: denoland/setup-deno@v1 + with: + deno-version: v2.3.1 + + - name: Run unit tests + run: deno task test \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..df01c9a --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# PoliEats 🍽 - Backend + +## Descrição: + +O PoliEats é um chatbot desenvolvido para auxiliar os alunos, alunos e demais visitantes do colégio **Poliedro** a +fazerem pedidos de comida e bebida a partir de uma interface de chat totalmente automatizada. O bot é capaz de responder perguntas frequentes, fornecer informações sobre o cardápio e realizar pedidos de forma rápida e eficiente. O objetivo principal do PoliEats é facilitar a experiência de compra dos usuários, tornando o processo mais ágil e prático. + +## Funcionalidades: +- **Cardápio**: O bot fornece informações detalhadas sobre o cardápio, incluindo preços e opções disponíveis. +- **Pedidos**: Os usuários podem fazer pedidos diretamente pelo bot, que irá encaminhar as informações para a equipe responsável. +- **Perguntas Frequentes**: O bot é capaz de responder perguntas frequentes sobre o colégio, cardápio e outros assuntos relacionados. +- **Horários**: O bot fornece informações sobre os horários de funcionamento do colégio e do serviço de alimentação. + +## Tecnologias Utilizadas: +- **TypeScript**: Linguagem de programação utilizada para desenvolver o backend. +- **Deno**: Ambiente de execução para o TypeScript. +- **PostgreSQL**: Banco de dados utilizado para armazenar informações sobre o cardápio, pedidos e usuários. +- **DrizzleORM**: ORM utilizado para facilitar a interação com o banco de dados PostgreSQL. +- **Mistral AI**: Modelo de linguagem utilizado para processar as mensagens dos usuários e gerar respostas. +- **LangChain**: Biblioteca utilizada para integrar o modelo de linguagem com o bot e facilitar a construção de fluxos de conversa. + +## Como executar o projeto: +1. Clone o repositório: +```bash +git clone https://github.com/PoliEats/Backend.git +cd Backend +``` + +2. Instale as dependências: +```bash +deno install +``` + +3. Configure o .env: +```bash +cp .env.example .env +``` + +4. Configure o banco de dados: +```bash +deno task db:migrate +``` + +5. Execute o projeto: +```bash +deno task start +``` \ No newline at end of file diff --git a/deno.json b/deno.json index 6611ec0..a8ecd45 100644 --- a/deno.json +++ b/deno.json @@ -1,16 +1,20 @@ { "tasks": { - "dev": "deno run --allow-net ./src/main.ts" + "dev": "deno run --allow-net ./src/main.ts", + "test": "deno test --env --allow-env --allow-read --allow-ffi --allow-sys --allow-net" }, "imports": { "@faker-js/faker": "npm:@faker-js/faker@^9.7.0", "@langchain/core": "npm:@langchain/core@^0.3.53", "@langchain/langgraph": "npm:@langchain/langgraph@^0.2.68", "@types/bcrypt": "npm:@types/bcrypt@^5.0.2", + "@types/cookie-parser": "npm:@types/cookie-parser@^1.4.8", "@types/cors": "npm:@types/cors@^2.8.18", "@types/express": "npm:@types/express@^5.0.1", "@types/pg": "npm:@types/pg@^8.15.1", + "@types/supertest": "npm:@types/supertest@^6.0.3", "bcrypt": "npm:bcrypt@^6.0.0", + "cookie-parser": "npm:cookie-parser@^1.4.7", "cors": "npm:cors@^2.8.5", "drizzle-kit": "npm:drizzle-kit@^0.31.1", "drizzle-orm": "npm:drizzle-orm@^0.43.1", @@ -20,6 +24,7 @@ "pg": "npm:pg@^8.16.0", "socket.io": "npm:socket.io@^4.8.1", "socket.io-client": "npm:socket.io-client@^4.8.1", + "supertest": "npm:supertest@^7.1.1", "uuid": "npm:uuid@^11.1.0", "zod": "npm:zod@^3.24.4" }, diff --git a/deno.lock b/deno.lock index c66e95c..8323c0b 100644 --- a/deno.lock +++ b/deno.lock @@ -13,11 +13,14 @@ "npm:@langchain/langgraph@~0.2.68": "0.2.68_@langchain+core@0.3.53__zod@3.24.4", "npm:@langchain/mistralai@0.2": "0.2.0_@langchain+core@0.3.53__zod@3.24.4_zod@3.24.4", "npm:@types/bcrypt@^5.0.2": "5.0.2", + "npm:@types/cookie-parser@^1.4.8": "1.4.8_@types+express@5.0.1", "npm:@types/cors@^2.8.18": "2.8.18", "npm:@types/express@^5.0.1": "5.0.1", "npm:@types/node@*": "22.12.0", "npm:@types/pg@^8.15.1": "8.15.1", + "npm:@types/supertest@^6.0.3": "6.0.3", "npm:bcrypt@6": "6.0.0", + "npm:cookie-parser@^1.4.7": "1.4.7", "npm:cors@*": "2.8.5", "npm:cors@^2.8.5": "2.8.5", "npm:drizzle-kit@~0.31.1": "0.31.1_esbuild@0.25.4", @@ -28,6 +31,7 @@ "npm:socket.io-client@^4.8.1": "4.8.1", "npm:socket.io@*": "4.8.1", "npm:socket.io@^4.8.1": "4.8.1", + "npm:supertest@^7.1.1": "7.1.1", "npm:uuid@^11.1.0": "11.1.0", "npm:zod@^3.24.4": "3.24.4" }, @@ -384,6 +388,15 @@ "zod-to-json-schema" ] }, + "@noble/hashes@1.8.0": { + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==" + }, + "@paralleldrive/cuid2@2.2.2": { + "integrity": "sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==", + "dependencies": [ + "@noble/hashes" + ] + }, "@socket.io/component-emitter@3.1.2": { "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" }, @@ -406,6 +419,15 @@ "@types/node" ] }, + "@types/cookie-parser@1.4.8_@types+express@5.0.1": { + "integrity": "sha512-l37JqFrOJ9yQfRQkljb41l0xVphc7kg5JTjjr+pLRZ0IyZ49V4BQ8vbF4Ut2C2e+WH4al3xD3ZwYwIUfnbT4NQ==", + "dependencies": [ + "@types/express" + ] + }, + "@types/cookiejar@2.1.5": { + "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==" + }, "@types/cors@2.8.17": { "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", "dependencies": [ @@ -441,6 +463,9 @@ "@types/json-schema@7.0.15": { "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" }, + "@types/methods@1.1.4": { + "integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==" + }, "@types/mime@1.3.5": { "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" }, @@ -482,6 +507,22 @@ "@types/send" ] }, + "@types/superagent@8.1.9": { + "integrity": "sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==", + "dependencies": [ + "@types/cookiejar", + "@types/methods", + "@types/node", + "form-data" + ] + }, + "@types/supertest@6.0.3": { + "integrity": "sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==", + "dependencies": [ + "@types/methods", + "@types/superagent" + ] + }, "@types/uuid@10.0.0": { "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==" }, @@ -508,6 +549,12 @@ "ansi-styles@5.2.0": { "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==" }, + "asap@2.0.6": { + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" + }, + "asynckit@0.4.0": { + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "base64-js@1.5.1": { "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, @@ -575,6 +622,15 @@ "color-name@1.1.4": { "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "combined-stream@1.0.8": { + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": [ + "delayed-stream" + ] + }, + "component-emitter@1.3.1": { + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==" + }, "console-table-printer@2.12.1": { "integrity": "sha512-wKGOQRRvdnd89pCeH96e2Fn4wkbenSP6LMHfjfyNLMbGuHEFbMqQNuxXqd0oXG9caIOQ1FTvc5Uijp9/4jujnQ==", "dependencies": [ @@ -590,12 +646,25 @@ "content-type@1.0.5": { "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" }, + "cookie-parser@1.4.7": { + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "dependencies": [ + "cookie", + "cookie-signature@1.0.6" + ] + }, + "cookie-signature@1.0.6": { + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, "cookie-signature@1.2.2": { "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==" }, "cookie@0.7.2": { "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==" }, + "cookiejar@2.1.4": { + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==" + }, "cors@2.8.5": { "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", "dependencies": [ @@ -618,9 +687,19 @@ "decamelize@1.2.0": { "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==" }, + "delayed-stream@1.0.0": { + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, "depd@2.0.0": { "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" }, + "dezalgo@1.0.4": { + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dependencies": [ + "asap", + "wrappy" + ] + }, "drizzle-kit@0.31.1_esbuild@0.25.4": { "integrity": "sha512-PUjYKWtzOzPtdtQlTHQG3qfv4Y0XT8+Eas6UbxCmxTj7qgMf+39dDujf1BP1I+qqZtw9uzwTh8jYtkMuCq+B0Q==", "dependencies": [ @@ -707,6 +786,15 @@ "es-errors" ] }, + "es-set-tostringtag@2.1.0": { + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dependencies": [ + "es-errors", + "get-intrinsic", + "has-tostringtag", + "hasown" + ] + }, "esbuild-register@3.6.0_esbuild@0.25.4": { "integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==", "dependencies": [ @@ -792,7 +880,7 @@ "content-disposition", "content-type", "cookie", - "cookie-signature", + "cookie-signature@1.2.2", "debug@4.4.0", "encodeurl", "escape-html", @@ -816,6 +904,9 @@ "vary" ] }, + "fast-safe-stringify@2.1.1": { + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" + }, "finalhandler@2.1.0": { "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", "dependencies": [ @@ -827,6 +918,23 @@ "statuses" ] }, + "form-data@4.0.2": { + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "dependencies": [ + "asynckit", + "combined-stream", + "es-set-tostringtag", + "mime-types@2.1.35" + ] + }, + "formidable@3.5.4": { + "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", + "dependencies": [ + "@paralleldrive/cuid2", + "dezalgo", + "once" + ] + }, "forwarded@0.2.0": { "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" }, @@ -873,6 +981,12 @@ "has-symbols@1.1.0": { "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" }, + "has-tostringtag@1.0.2": { + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": [ + "has-symbols" + ] + }, "hasown@2.0.2": { "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dependencies": [ @@ -934,6 +1048,9 @@ "merge-descriptors@2.0.0": { "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==" }, + "methods@1.1.2": { + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" + }, "mime-db@1.52.0": { "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" }, @@ -952,6 +1069,10 @@ "mime-db@1.54.0" ] }, + "mime@2.6.0": { + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "bin": true + }, "ms@2.1.3": { "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, @@ -1286,6 +1407,27 @@ "statuses@2.0.1": { "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" }, + "superagent@10.2.1": { + "integrity": "sha512-O+PCv11lgTNJUzy49teNAWLjBZfc+A1enOwTpLlH6/rsvKcTwcdTT8m9azGkVqM7HBl5jpyZ7KTPhHweokBcdg==", + "dependencies": [ + "component-emitter", + "cookiejar", + "debug@4.4.0", + "fast-safe-stringify", + "form-data", + "formidable", + "methods", + "mime", + "qs" + ] + }, + "supertest@7.1.1": { + "integrity": "sha512-aI59HBTlG9e2wTjxGJV+DygfNLgnWbGdZxiA/sgrnNNikIW8lbDvCtF6RnhZoJ82nU7qv7ZLjrvWqCEm52fAmw==", + "dependencies": [ + "methods", + "superagent" + ] + }, "supports-color@7.2.0": { "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dependencies": [ @@ -1353,10 +1495,13 @@ "npm:@langchain/langgraph@~0.2.68", "npm:@langchain/mistralai@0.2", "npm:@types/bcrypt@^5.0.2", + "npm:@types/cookie-parser@^1.4.8", "npm:@types/cors@^2.8.18", "npm:@types/express@^5.0.1", "npm:@types/pg@^8.15.1", + "npm:@types/supertest@^6.0.3", "npm:bcrypt@6", + "npm:cookie-parser@^1.4.7", "npm:cors@^2.8.5", "npm:drizzle-kit@~0.31.1", "npm:drizzle-orm@~0.43.1", @@ -1365,6 +1510,7 @@ "npm:pg@^8.16.0", "npm:socket.io-client@^4.8.1", "npm:socket.io@^4.8.1", + "npm:supertest@^7.1.1", "npm:uuid@^11.1.0", "npm:zod@^3.24.4" ] diff --git a/src/interfaces/IUser.ts b/src/interfaces/IUser.ts index ef4ac71..2f87ec3 100644 --- a/src/interfaces/IUser.ts +++ b/src/interfaces/IUser.ts @@ -1,3 +1,9 @@ +/** + * User Interface + * This interface defines the structure of a user object. + * It includes properties such as id, name, email, document, createdAt, and updatedAt. +*/ + export interface IUser { id: number; name: string; diff --git a/src/main.ts b/src/main.ts index edbad01..4ed91e7 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,16 +1,28 @@ +import cookieParser from "cookie-parser"; import express from "express"; import { createServer } from "node:http"; import cors from "npm:cors"; import { Server } from "npm:socket.io"; +import { MockDatabase } from "./database/MockDatabase.ts"; import { addMessage } from "./lc/model.ts"; +import { ValidateJWT } from "./middlewares/ValidateJWT.ts"; +import { AuthenticationService } from "./services/AuthenticationService.ts"; const app = express(); +// Instantiate the services +const db = new MockDatabase(); +const authenticationService = new AuthenticationService(db); +const JWTmiddleware = new ValidateJWT(authenticationService); + +app.use(cookieParser()) +app.use(express.json()); + app.use(cors({ origin: "*", })); -const wsServer = createServer(app); +export const wsServer = createServer(app); export const io = new Server(wsServer, { cors: { origin: "*", @@ -21,6 +33,61 @@ app.get("/", (_req, res) => { res.send("Welcome to the Dinosaur API!"); }); +app.post("/auth/register", async (req, res) => { + const { name, email, document, password } = req.body; + try { + const userId = await authenticationService.registerUser( + { id: 123, name, email, document, createdAt: new Date(), updatedAt: new Date() }, + password, + ); + + const token = await authenticationService.createJWT(userId); + + res.cookie("token", token, { + httpOnly: true, + secure: false, + sameSite: "strict", + }); + + res.status(201).send(); + } catch (error: unknown) { + if (!(error instanceof Error)) { + throw error; + } + + res.status(400).json({ error: error.message }); + } +}); + +app.post("/auth/login", async (req, res) => { + const { email, password } = req.body; + try { + const userId = await authenticationService.loginUser(email, password); + if (!userId) { + res.status(401).json({ error: "Invalid email or password" }); + return; + } + + const token = await authenticationService.createJWT(userId.id); + res.cookie("token", token, { + httpOnly: true, + secure: false, + sameSite: "strict", + }); + + res.status(200).send(); + } catch (error: unknown) { + if (!(error instanceof Error)) { + throw error; + } + + res.status(401).json({ error: "Email ou senha inválidos." }); + } +}); + +app.get("/hidden", JWTmiddleware.validateToken, (_req, res) => { + res.status(200).json({ message: "This is a hidden route" }); +}); // Handle messages from chat export const pendingConfirmation = new Map(); diff --git a/src/middlewares/ValidateJWT.ts b/src/middlewares/ValidateJWT.ts new file mode 100644 index 0000000..13ba4b6 --- /dev/null +++ b/src/middlewares/ValidateJWT.ts @@ -0,0 +1,28 @@ +import { NextFunction, Request, Response } from "express"; +import { AuthenticationService } from "../services/AuthenticationService.ts"; + +export class ValidateJWT { + private authService: AuthenticationService; + + constructor(authService: AuthenticationService) { + this.authService = authService; + this.validateToken = this.validateToken.bind(this); + } + + async validateToken(req: Request, res: Response, next: NextFunction) { + const token = req.cookies.token; + + if (!token) { + res.status(401).json({ error: "Unauthorized" }); + return; + } + + await this.authService.verifyJWT(token) + .then(() => { + next(); + }) + .catch(() => { + res.status(401).json({ error: "Invalid token" }); + }); + } +} \ No newline at end of file diff --git a/tests/routes.test.ts b/tests/routes.test.ts new file mode 100644 index 0000000..7d1eab9 --- /dev/null +++ b/tests/routes.test.ts @@ -0,0 +1,142 @@ +import { expect } from "jsr:@std/expect/expect"; +import { describe, it } from "jsr:@std/testing/bdd"; +import request from "supertest"; +import { wsServer } from "../src/main.ts"; +import { generateMockPassword, generateMockUser } from "../src/mocks/User.ts"; + +describe("POST /auth/register", () => { + it("should register a new user", async () => { + const newUser = generateMockUser(); + const password = generateMockPassword(); + + const res = await request(wsServer) + .post("/auth/register") + .send({ + name: newUser.name, + email: newUser.email, + document: newUser.document, + password: password, + }) + + expect(res.status).toBe(201); + }); + + it("should not register a user with an existing email", async () => { + const newUser = generateMockUser(); + const password = generateMockPassword(); + + await request(wsServer) + .post("/auth/register") + .send({ + name: newUser.name, + email: newUser.email, + document: newUser.document, + password: password, + }) + + const res = await request(wsServer) + .post("/auth/register") + .send({ + name: newUser.name, + email: newUser.email, + document: newUser.document, + password: password, + }) + + expect(res.status).toBe(400); + }); + + it("should not register a user with missing fields", async () => { + const res = await request(wsServer) + .post("/auth/register") + .send({ + name: "John Doe", + email: "test@test.com", + document: "12345678901", + }) + expect(res.status).toBe(400); + }); +}) + +describe("POST /auth/login", () => { + it("should login a user with valid credentials", async () => { + const newUser = generateMockUser(); + const password = generateMockPassword(); + + await request(wsServer) + .post("/auth/register") + .send({ + name: newUser.name, + email: newUser.email, + document: newUser.document, + password: password, + }) + + const res = await request(wsServer) + .post("/auth/login") + .send({ + email: newUser.email, + password: password, + }) + + expect(res.status).toBe(200); + }); + + it("should not login a user with invalid credentials", async () => { + const newUser = generateMockUser(); + const password = generateMockPassword(); + + await request(wsServer) + .post("/auth/register") + .send({ + name: newUser.name, + email: newUser.email, + document: newUser.document, + password: password, + }) + + const res = await request(wsServer) + .post("/auth/login") + .send({ + email: newUser.email, + password: "wrongpassword", + }) + expect(res.status).toBe(401); + }); + + it("should be able to see hidden content with a valid JWT", async () => { + const newUser = generateMockUser(); + const password = generateMockPassword(); + + await request(wsServer) + .post("/auth/register") + .send({ + name: newUser.name, + email: newUser.email, + document: newUser.document, + password: password, + }) + + const loginRes = await request(wsServer) + .post("/auth/login") + .send({ + email: newUser.email, + password: password, + }) + + const token = loginRes.headers["set-cookie"][0].split("=")[1].split(";")[0]; + + const res = await request(wsServer) + .get("/hidden") + .set("Cookie", `token=${token}`) + + expect(res.status).toBe(200); + }); + + it("should not be able to see hidden content without a valid JWT", async () => { + const res = await request(wsServer) + .get("/hidden") + + expect(res.status).toBe(401); + }); +}); \ No newline at end of file