Compare commits

..

21 commits

Author SHA1 Message Date
1b402c791c execution: log channel ID to terminal if it's unknown
channelId is set for commands executed outside of common guilds
no changes for usage logging in database is required
2024-04-26 08:13:29 +02:00
c4edf55f65 commands/ask: enforce userLimit 2024-04-26 08:07:46 +02:00
6f5f425166 configDefault: add status options 2024-04-26 07:14:41 +02:00
b1e464fe50 Dockerfile: create db directory 2024-04-26 06:14:57 +02:00
6efb6e5876 commands/ask: create
Allows to interact with the bot when bot is user installed in discord.
2024-04-26 05:41:53 +02:00
9f5dfefb31 execution: support for requests that don't have channel set
Interactions initiated outside of servers where bot is don't have channel assigned
2024-04-26 05:41:53 +02:00
2fab1b1b42 execution: factor out replying code to it's own fuction 2024-04-26 05:17:32 +02:00
482f72a4d1 execution: factor out chat completion process 2024-04-26 04:03:34 +02:00
d3567c3607 execution: handle undefined message in logUsedTokens 2024-04-26 04:02:09 +02:00
67a6e4d486 execution+configDefault: retrofit for tooll_calls api 2024-04-26 03:27:15 +02:00
370b7623b5 Dockerfile: chmod dist to 777 for config.ts compilation 2024-04-24 02:38:52 +02:00
b567e13f2a functionManager: use json-schema-to-ts to derive arguments for OpenAIFunctions 2024-04-24 02:27:31 +02:00
d9bee2dcf2 check-quota: define integration types and context to any 2024-04-23 21:11:40 +02:00
3c10f4ed6f Allow chatting with bot through direct messages
Added new command to make the bot see direct messages,
because by default these are not visible,
unless bot opens direct message with the user.
2024-04-23 21:10:52 +02:00
63cb52e7f4 Command: Add integration and context types
While discord api supports that, discord api types, which discord.js depends on does not.
2024-04-23 20:56:55 +02:00
0e5c8d22cc functionManager: retrofit for tool calls api 2024-04-23 17:46:24 +02:00
91232e99a7 Update dependencies 2024-04-23 16:47:08 +02:00
dc01146ee8 ignore @everyone mentions 2023-12-25 16:09:35 +01:00
74359067d0 functionManager: don't return empty function array for openai
openai doesn't accept empty function parameter,
it was discovered after I removed the testing buildin function
2023-12-13 21:08:09 +01:00
24e85f535a "Gracefully" exit when receiving sigint 2023-12-13 20:56:23 +01:00
02a5b0f7b9 Remove getTime model function
it lead to higher usage of tokens.
2023-12-13 17:42:59 +01:00
14 changed files with 790 additions and 417 deletions

View file

@ -15,5 +15,17 @@ COPY src ./src
COPY scripts ./scripts
RUN npx tsc
# Permissions for dist directory,
# so config.ts can be compiled there during runtime
# regardless of the user of the container
RUN chmod 777 dist
# Create a db directory for sqlite database file
# it is required because in order to write to sqlite file
# the directory must be writable
RUN mkdir /db
RUN chmod 777 /db
# Run the app
CMD ["node", "dist/src/index.js"]
STOPSIGNAL SIGINT

548
package-lock.json generated
View file

@ -9,21 +9,22 @@
"version": "0.1.0",
"license": "MIT",
"dependencies": {
"@prisma/client": "5.7.0",
"discord.js": "^14.8.0",
"@prisma/client": "5.13.0",
"discord.js": "14.14.1",
"fold-to-ascii": "^5.0.1",
"gpt-3-encoder": "^1.1.4",
"openai": "^4.10.0",
"openai": "^4.38.3",
"require-directory": "^2.1.1",
"typescript": "^5.1.6"
"typescript": "^5.4.5"
},
"devDependencies": {
"@types/fold-to-ascii": "^5.0.0",
"@types/require-directory": "^2.1.2",
"@typescript-eslint/eslint-plugin": "^6.2.0",
"@typescript-eslint/parser": "^6.2.0",
"eslint": "^8.46.0",
"prisma": "5.7.0"
"@typescript-eslint/eslint-plugin": "^7.7.1",
"@typescript-eslint/parser": "^7.7.1",
"eslint": "^8.57.0",
"json-schema-to-ts": "^3.0.1",
"prisma": "5.13.0"
}
},
"node_modules/@aashutoshrathi/word-wrap": {
@ -35,6 +36,18 @@
"node": ">=0.10.0"
}
},
"node_modules/@babel/runtime": {
"version": "7.24.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz",
"integrity": "sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==",
"dev": true,
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@discordjs/builders": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.7.0.tgz",
@ -180,37 +193,81 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/@eslint/eslintrc/node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/@eslint/js": {
"version": "8.55.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.55.0.tgz",
"integrity": "sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==",
"version": "8.57.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz",
"integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==",
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/@fastify/busboy": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.0.tgz",
"integrity": "sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==",
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz",
"integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==",
"engines": {
"node": ">=14"
}
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.13",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz",
"integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==",
"version": "0.11.14",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
"integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==",
"dev": true,
"dependencies": {
"@humanwhocodes/object-schema": "^2.0.1",
"debug": "^4.1.1",
"@humanwhocodes/object-schema": "^2.0.2",
"debug": "^4.3.1",
"minimatch": "^3.0.5"
},
"engines": {
"node": ">=10.10.0"
}
},
"node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/@humanwhocodes/config-array/node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/@humanwhocodes/module-importer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
@ -225,9 +282,9 @@
}
},
"node_modules/@humanwhocodes/object-schema": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz",
"integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==",
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
"integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
"dev": true
},
"node_modules/@nodelib/fs.scandir": {
@ -266,9 +323,9 @@
}
},
"node_modules/@prisma/client": {
"version": "5.7.0",
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.7.0.tgz",
"integrity": "sha512-cZmglCrfNbYpzUtz7HscVHl38e9CrUs31nrVoGUK1nIPXGgt8hT4jj2s657UXcNdQ/jBUxDgGmHyu2Nyrq1txg==",
"version": "5.13.0",
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.13.0.tgz",
"integrity": "sha512-uYdfpPncbZ/syJyiYBwGZS8Gt1PTNoErNYMuqHDa2r30rNSFtgTA/LXsSk55R7pdRTMi5pHkeP9B14K6nHmwkg==",
"hasInstallScript": true,
"engines": {
"node": ">=16.13"
@ -283,70 +340,69 @@
}
},
"node_modules/@prisma/debug": {
"version": "5.7.0",
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.7.0.tgz",
"integrity": "sha512-tZ+MOjWlVvz1kOEhNYMa4QUGURY+kgOUBqLHYIV8jmCsMuvA1tWcn7qtIMLzYWCbDcQT4ZS8xDgK0R2gl6/0wA==",
"version": "5.13.0",
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.13.0.tgz",
"integrity": "sha512-699iqlEvzyCj9ETrXhs8o8wQc/eVW+FigSsHpiskSFydhjVuwTJEfj/nIYqTaWFYuxiWQRfm3r01meuW97SZaQ==",
"devOptional": true
},
"node_modules/@prisma/engines": {
"version": "5.7.0",
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.7.0.tgz",
"integrity": "sha512-TkOMgMm60n5YgEKPn9erIvFX2/QuWnl3GBo6yTRyZKk5O5KQertXiNnrYgSLy0SpsKmhovEPQb+D4l0SzyE7XA==",
"version": "5.13.0",
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.13.0.tgz",
"integrity": "sha512-hIFLm4H1boj6CBZx55P4xKby9jgDTeDG0Jj3iXtwaaHmlD5JmiDkZhh8+DYWkTGchu+rRF36AVROLnk0oaqhHw==",
"devOptional": true,
"hasInstallScript": true,
"dependencies": {
"@prisma/debug": "5.7.0",
"@prisma/engines-version": "5.7.0-41.79fb5193cf0a8fdbef536e4b4a159cad677ab1b9",
"@prisma/fetch-engine": "5.7.0",
"@prisma/get-platform": "5.7.0"
"@prisma/debug": "5.13.0",
"@prisma/engines-version": "5.13.0-23.b9a39a7ee606c28e3455d0fd60e78c3ba82b1a2b",
"@prisma/fetch-engine": "5.13.0",
"@prisma/get-platform": "5.13.0"
}
},
"node_modules/@prisma/engines-version": {
"version": "5.7.0-41.79fb5193cf0a8fdbef536e4b4a159cad677ab1b9",
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.7.0-41.79fb5193cf0a8fdbef536e4b4a159cad677ab1b9.tgz",
"integrity": "sha512-V6tgRVi62jRwTm0Hglky3Scwjr/AKFBFtS+MdbsBr7UOuiu1TKLPc6xfPiyEN1+bYqjEtjxwGsHgahcJsd1rNg==",
"version": "5.13.0-23.b9a39a7ee606c28e3455d0fd60e78c3ba82b1a2b",
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.13.0-23.b9a39a7ee606c28e3455d0fd60e78c3ba82b1a2b.tgz",
"integrity": "sha512-AyUuhahTINGn8auyqYdmxsN+qn0mw3eg+uhkp8zwknXYIqoT3bChG4RqNY/nfDkPvzWAPBa9mrDyBeOnWSgO6A==",
"devOptional": true
},
"node_modules/@prisma/fetch-engine": {
"version": "5.7.0",
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.7.0.tgz",
"integrity": "sha512-zIn/qmO+N/3FYe7/L9o+yZseIU8ivh4NdPKSkQRIHfg2QVTVMnbhGoTcecbxfVubeTp+DjcbjS0H9fCuM4W04w==",
"version": "5.13.0",
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.13.0.tgz",
"integrity": "sha512-Yh4W+t6YKyqgcSEB3odBXt7QyVSm0OQlBSldQF2SNXtmOgMX8D7PF/fvH6E6qBCpjB/yeJLy/FfwfFijoHI6sA==",
"devOptional": true,
"dependencies": {
"@prisma/debug": "5.7.0",
"@prisma/engines-version": "5.7.0-41.79fb5193cf0a8fdbef536e4b4a159cad677ab1b9",
"@prisma/get-platform": "5.7.0"
"@prisma/debug": "5.13.0",
"@prisma/engines-version": "5.13.0-23.b9a39a7ee606c28e3455d0fd60e78c3ba82b1a2b",
"@prisma/get-platform": "5.13.0"
}
},
"node_modules/@prisma/get-platform": {
"version": "5.7.0",
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.7.0.tgz",
"integrity": "sha512-ZeV/Op4bZsWXuw5Tg05WwRI8BlKiRFhsixPcAM+5BKYSiUZiMKIi713tfT3drBq8+T0E1arNZgYSA9QYcglWNA==",
"version": "5.13.0",
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.13.0.tgz",
"integrity": "sha512-B/WrQwYTzwr7qCLifQzYOmQhZcFmIFhR81xC45gweInSUn2hTEbfKUPd2keAog+y5WI5xLAFNJ3wkXplvSVkSw==",
"devOptional": true,
"dependencies": {
"@prisma/debug": "5.7.0"
"@prisma/debug": "5.13.0"
}
},
"node_modules/@sapphire/async-queue": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.1.tgz",
"integrity": "sha512-1RdpsmDQR/aWfp8oJzPtn4dNQrbpqSL5PIA0uAB/XwerPXUf994Ug1au1e7uGcD7ei8/F63UDjr5GWps1g/HxQ==",
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.2.tgz",
"integrity": "sha512-7X7FFAA4DngXUl95+hYbUF19bp1LGiffjJtu7ygrZrbdCSsdDDBaSjB7Akw0ZbOu6k0xpXyljnJ6/RZUvLfRdg==",
"engines": {
"node": ">=v14.0.0",
"npm": ">=7.0.0"
}
},
"node_modules/@sapphire/shapeshift": {
"version": "3.9.4",
"resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-3.9.4.tgz",
"integrity": "sha512-SiOoCBmm8O7QuadLJnX4V0tAkhC54NIOZJtmvw+5zwnHaiulGkjY02wxCuK8Gf4V540ILmGz+UulC0U8mrOZjg==",
"version": "3.9.7",
"resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-3.9.7.tgz",
"integrity": "sha512-4It2mxPSr4OGn4HSQWGmhFMsNFGfFVhWeRPCRwbH972Ek2pzfGRZtb0pJ4Ze6oIzcyh2jw7nUDa6qGlWofgd9g==",
"dependencies": {
"fast-deep-equal": "^3.1.3",
"lodash": "^4.17.21"
},
"engines": {
"node": ">=v14.0.0",
"npm": ">=7.0.0"
"node": ">=v16"
}
},
"node_modules/@sapphire/snowflake": {
@ -371,17 +427,17 @@
"dev": true
},
"node_modules/@types/node": {
"version": "20.10.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.4.tgz",
"integrity": "sha512-D08YG6rr8X90YB56tSIuBaddy/UXAA9RKJoFvrsnogAum/0pmjkgi4+2nx96A330FmioegBWmEYQ+syqCFaveg==",
"version": "20.12.7",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz",
"integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==",
"dependencies": {
"undici-types": "~5.26.4"
}
},
"node_modules/@types/node-fetch": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.9.tgz",
"integrity": "sha512-bQVlnMLFJ2d35DkPNjEPmd9ueO/rh5EiaZt2bhqiSarPjZIuIV6bPQVqcrEyvNo+AfTrRGVazle1tl597w3gfA==",
"version": "2.6.11",
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz",
"integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==",
"dependencies": {
"@types/node": "*",
"form-data": "^4.0.0"
@ -397,9 +453,9 @@
}
},
"node_modules/@types/semver": {
"version": "7.5.6",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz",
"integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==",
"version": "7.5.8",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
"integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==",
"dev": true
},
"node_modules/@types/ws": {
@ -411,33 +467,33 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.14.0.tgz",
"integrity": "sha512-1ZJBykBCXaSHG94vMMKmiHoL0MhNHKSVlcHVYZNw+BKxufhqQVTOawNpwwI1P5nIFZ/4jLVop0mcY6mJJDFNaw==",
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.7.1.tgz",
"integrity": "sha512-KwfdWXJBOviaBVhxO3p5TJiLpNuh2iyXyjmWN0f1nU87pwyvfS0EmjC6ukQVYVFJd/K1+0NWGPDXiyEyQorn0Q==",
"dev": true,
"dependencies": {
"@eslint-community/regexpp": "^4.5.1",
"@typescript-eslint/scope-manager": "6.14.0",
"@typescript-eslint/type-utils": "6.14.0",
"@typescript-eslint/utils": "6.14.0",
"@typescript-eslint/visitor-keys": "6.14.0",
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "7.7.1",
"@typescript-eslint/type-utils": "7.7.1",
"@typescript-eslint/utils": "7.7.1",
"@typescript-eslint/visitor-keys": "7.7.1",
"debug": "^4.3.4",
"graphemer": "^1.4.0",
"ignore": "^5.2.4",
"ignore": "^5.3.1",
"natural-compare": "^1.4.0",
"semver": "^7.5.4",
"ts-api-utils": "^1.0.1"
"semver": "^7.6.0",
"ts-api-utils": "^1.3.0"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
"node": "^18.18.0 || >=20.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha",
"eslint": "^7.0.0 || ^8.0.0"
"@typescript-eslint/parser": "^7.0.0",
"eslint": "^8.56.0"
},
"peerDependenciesMeta": {
"typescript": {
@ -446,26 +502,26 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.14.0.tgz",
"integrity": "sha512-QjToC14CKacd4Pa7JK4GeB/vHmWFJckec49FR4hmIRf97+KXole0T97xxu9IFiPxVQ1DBWrQ5wreLwAGwWAVQA==",
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.7.1.tgz",
"integrity": "sha512-vmPzBOOtz48F6JAGVS/kZYk4EkXao6iGrD838sp1w3NQQC0W8ry/q641KU4PrG7AKNAf56NOcR8GOpH8l9FPCw==",
"dev": true,
"dependencies": {
"@typescript-eslint/scope-manager": "6.14.0",
"@typescript-eslint/types": "6.14.0",
"@typescript-eslint/typescript-estree": "6.14.0",
"@typescript-eslint/visitor-keys": "6.14.0",
"@typescript-eslint/scope-manager": "7.7.1",
"@typescript-eslint/types": "7.7.1",
"@typescript-eslint/typescript-estree": "7.7.1",
"@typescript-eslint/visitor-keys": "7.7.1",
"debug": "^4.3.4"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
"node": "^18.18.0 || >=20.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^7.0.0 || ^8.0.0"
"eslint": "^8.56.0"
},
"peerDependenciesMeta": {
"typescript": {
@ -474,16 +530,16 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.14.0.tgz",
"integrity": "sha512-VT7CFWHbZipPncAZtuALr9y3EuzY1b1t1AEkIq2bTXUPKw+pHoXflGNG5L+Gv6nKul1cz1VH8fz16IThIU0tdg==",
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.7.1.tgz",
"integrity": "sha512-PytBif2SF+9SpEUKynYn5g1RHFddJUcyynGpztX3l/ik7KmZEv19WCMhUBkHXPU9es/VWGD3/zg3wg90+Dh2rA==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.14.0",
"@typescript-eslint/visitor-keys": "6.14.0"
"@typescript-eslint/types": "7.7.1",
"@typescript-eslint/visitor-keys": "7.7.1"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
"node": "^18.18.0 || >=20.0.0"
},
"funding": {
"type": "opencollective",
@ -491,25 +547,25 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.14.0.tgz",
"integrity": "sha512-x6OC9Q7HfYKqjnuNu5a7kffIYs3No30isapRBJl1iCHLitD8O0lFbRcVGiOcuyN837fqXzPZ1NS10maQzZMKqw==",
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.7.1.tgz",
"integrity": "sha512-ZksJLW3WF7o75zaBPScdW1Gbkwhd/lyeXGf1kQCxJaOeITscoSl0MjynVvCzuV5boUz/3fOI06Lz8La55mu29Q==",
"dev": true,
"dependencies": {
"@typescript-eslint/typescript-estree": "6.14.0",
"@typescript-eslint/utils": "6.14.0",
"@typescript-eslint/typescript-estree": "7.7.1",
"@typescript-eslint/utils": "7.7.1",
"debug": "^4.3.4",
"ts-api-utils": "^1.0.1"
"ts-api-utils": "^1.3.0"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
"node": "^18.18.0 || >=20.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^7.0.0 || ^8.0.0"
"eslint": "^8.56.0"
},
"peerDependenciesMeta": {
"typescript": {
@ -518,12 +574,12 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.14.0.tgz",
"integrity": "sha512-uty9H2K4Xs8E47z3SnXEPRNDfsis8JO27amp2GNCnzGETEW3yTqEIVg5+AI7U276oGF/tw6ZA+UesxeQ104ceA==",
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.7.1.tgz",
"integrity": "sha512-AmPmnGW1ZLTpWa+/2omPrPfR7BcbUU4oha5VIbSbS1a1Tv966bklvLNXxp3mrbc+P2j4MNOTfDffNsk4o0c6/w==",
"dev": true,
"engines": {
"node": "^16.0.0 || >=18.0.0"
"node": "^18.18.0 || >=20.0.0"
},
"funding": {
"type": "opencollective",
@ -531,21 +587,22 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.14.0.tgz",
"integrity": "sha512-yPkaLwK0yH2mZKFE/bXkPAkkFgOv15GJAUzgUVonAbv0Hr4PK/N2yaA/4XQbTZQdygiDkpt5DkxPELqHguNvyw==",
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.7.1.tgz",
"integrity": "sha512-CXe0JHCXru8Fa36dteXqmH2YxngKJjkQLjxzoj6LYwzZ7qZvgsLSc+eqItCrqIop8Vl2UKoAi0StVWu97FQZIQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.14.0",
"@typescript-eslint/visitor-keys": "6.14.0",
"@typescript-eslint/types": "7.7.1",
"@typescript-eslint/visitor-keys": "7.7.1",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
"semver": "^7.5.4",
"ts-api-utils": "^1.0.1"
"minimatch": "^9.0.4",
"semver": "^7.6.0",
"ts-api-utils": "^1.3.0"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
"node": "^18.18.0 || >=20.0.0"
},
"funding": {
"type": "opencollective",
@ -558,41 +615,41 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.14.0.tgz",
"integrity": "sha512-XwRTnbvRr7Ey9a1NT6jqdKX8y/atWG+8fAIu3z73HSP8h06i3r/ClMhmaF/RGWGW1tHJEwij1uEg2GbEmPYvYg==",
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.7.1.tgz",
"integrity": "sha512-QUvBxPEaBXf41ZBbaidKICgVL8Hin0p6prQDu6bbetWo39BKbWJxRsErOzMNT1rXvTll+J7ChrbmMCXM9rsvOQ==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"@types/json-schema": "^7.0.12",
"@types/semver": "^7.5.0",
"@typescript-eslint/scope-manager": "6.14.0",
"@typescript-eslint/types": "6.14.0",
"@typescript-eslint/typescript-estree": "6.14.0",
"semver": "^7.5.4"
"@types/json-schema": "^7.0.15",
"@types/semver": "^7.5.8",
"@typescript-eslint/scope-manager": "7.7.1",
"@typescript-eslint/types": "7.7.1",
"@typescript-eslint/typescript-estree": "7.7.1",
"semver": "^7.6.0"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
"node": "^18.18.0 || >=20.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^7.0.0 || ^8.0.0"
"eslint": "^8.56.0"
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.14.0.tgz",
"integrity": "sha512-fB5cw6GRhJUz03MrROVuj5Zm/Q+XWlVdIsFj+Zb1Hvqouc8t+XP2H5y53QYU/MGtd2dPg6/vJJlhoX3xc2ehfw==",
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.7.1.tgz",
"integrity": "sha512-gBL3Eq25uADw1LQ9kVpf3hRM+DWzs0uZknHYK3hq4jcTPqVCClHGDnB6UUUV2SFeBeA4KWHWbbLqmbGcZ4FYbw==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.14.0",
"eslint-visitor-keys": "^3.4.1"
"@typescript-eslint/types": "7.7.1",
"eslint-visitor-keys": "^3.4.3"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
"node": "^18.18.0 || >=20.0.0"
},
"funding": {
"type": "opencollective",
@ -626,9 +683,9 @@
}
},
"node_modules/acorn": {
"version": "8.11.2",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz",
"integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==",
"version": "8.11.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
"dev": true,
"bin": {
"acorn": "bin/acorn"
@ -723,19 +780,13 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
"node_modules/base-64": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz",
"integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA=="
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
"balanced-match": "^1.0.0"
}
},
"node_modules/braces": {
@ -775,14 +826,6 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/charenc": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
"integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==",
"engines": {
"node": "*"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@ -832,14 +875,6 @@
"node": ">= 8"
}
},
"node_modules/crypt": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
"integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==",
"engines": {
"node": "*"
}
},
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@ -871,15 +906,6 @@
"node": ">=0.4.0"
}
},
"node_modules/digest-fetch": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/digest-fetch/-/digest-fetch-1.3.0.tgz",
"integrity": "sha512-CGJuv6iKNM7QyZlM2T3sPAdZWd/p9zQiRNS9G+9COUCwzWFTs0Xp8NF5iePx7wtvhDykReiRRrSeNb4oMmB8lA==",
"dependencies": {
"base-64": "^0.1.0",
"md5": "^2.3.0"
}
},
"node_modules/dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@ -946,16 +972,16 @@
}
},
"node_modules/eslint": {
"version": "8.55.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.55.0.tgz",
"integrity": "sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA==",
"version": "8.57.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz",
"integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
"@eslint/eslintrc": "^2.1.4",
"@eslint/js": "8.55.0",
"@humanwhocodes/config-array": "^0.11.13",
"@eslint/js": "8.57.0",
"@humanwhocodes/config-array": "^0.11.14",
"@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
"@ungap/structured-clone": "^1.2.0",
@ -1028,6 +1054,28 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint/node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/eslint/node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/espree": {
"version": "9.6.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
@ -1141,9 +1189,9 @@
"dev": true
},
"node_modules/fastq": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
"integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==",
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
"integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
"dev": true,
"dependencies": {
"reusify": "^1.0.4"
@ -1204,9 +1252,9 @@
}
},
"node_modules/flatted": {
"version": "3.2.9",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz",
"integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==",
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz",
"integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
"dev": true
},
"node_modules/fold-to-ascii": {
@ -1293,6 +1341,28 @@
"node": ">=10.13.0"
}
},
"node_modules/glob/node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/glob/node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/globals": {
"version": "13.24.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
@ -1357,9 +1427,9 @@
}
},
"node_modules/ignore": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz",
"integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==",
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
"integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==",
"dev": true,
"engines": {
"node": ">= 4"
@ -1406,11 +1476,6 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
"node_modules/is-buffer": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
},
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@ -1474,6 +1539,19 @@
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
"dev": true
},
"node_modules/json-schema-to-ts": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.0.1.tgz",
"integrity": "sha512-ANphQxnKbzLWPeYDmdoci8C9g9ttpfMx8etTlJJ8UCEmNXH9jxGkn3AAbMe+lR4N5OG/01nYxPrDyugLdsRt+A==",
"dev": true,
"dependencies": {
"@babel/runtime": "^7.18.3",
"ts-algebra": "^1.2.2"
},
"engines": {
"node": ">=16"
}
},
"node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@ -1552,19 +1630,9 @@
}
},
"node_modules/magic-bytes.js": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.7.0.tgz",
"integrity": "sha512-YzVU2+/hrjwx8xcgAw+ffNq3jkactpj+f1iSL4LonrFKhvnwDzHSqtFdk/MMRP53y9ScouJ7cKEnqYsJwsHoYA=="
},
"node_modules/md5": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
"integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==",
"dependencies": {
"charenc": "0.0.2",
"crypt": "0.0.2",
"is-buffer": "~1.1.6"
}
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.10.0.tgz",
"integrity": "sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ=="
},
"node_modules/merge2": {
"version": "1.4.1",
@ -1608,15 +1676,18 @@
}
},
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"version": "9.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz",
"integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==",
"dev": true,
"dependencies": {
"brace-expansion": "^1.1.7"
"brace-expansion": "^2.0.1"
},
"engines": {
"node": "*"
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/ms": {
@ -1677,15 +1748,14 @@
}
},
"node_modules/openai": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/openai/-/openai-4.21.0.tgz",
"integrity": "sha512-HT0Jm2iGI5+Maq7Da/DPTeIAxNvpa5pamkhlNnJJAqJgVjaFDvMUBjGhFJoVohkYWwZjM9oSyfSC0eoVNPioaQ==",
"version": "4.38.3",
"resolved": "https://registry.npmjs.org/openai/-/openai-4.38.3.tgz",
"integrity": "sha512-mIL9WtrFNOanpx98mJ+X/wkoepcxdqqu0noWFoNQHl/yODQ47YM7NEYda7qp8JfjqpLFVxY9mQhshoS/Fqac0A==",
"dependencies": {
"@types/node": "^18.11.18",
"@types/node-fetch": "^2.6.4",
"abort-controller": "^3.0.0",
"agentkeepalive": "^4.2.1",
"digest-fetch": "^1.3.0",
"form-data-encoder": "1.7.2",
"formdata-node": "^4.3.2",
"node-fetch": "^2.6.7",
@ -1696,9 +1766,9 @@
}
},
"node_modules/openai/node_modules/@types/node": {
"version": "18.19.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.3.tgz",
"integrity": "sha512-k5fggr14DwAytoA/t8rPrIz++lXK7/DqckthCmoZOKNsEbJkId4Z//BqgApXBUGrGddrigYa1oqheo/7YmW4rg==",
"version": "18.19.31",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.31.tgz",
"integrity": "sha512-ArgCD39YpyyrtFKIqMDvjz79jto5fcI/SVUs2HwB+f0dAzq68yqOdyaSivLiLugSziTpNXLQrVb7RZFmdZzbhA==",
"dependencies": {
"undici-types": "~5.26.4"
}
@ -1820,13 +1890,13 @@
}
},
"node_modules/prisma": {
"version": "5.7.0",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-5.7.0.tgz",
"integrity": "sha512-0rcfXO2ErmGAtxnuTNHQT9ztL0zZheQjOI/VNJzdq87C3TlGPQtMqtM+KCwU6XtmkoEr7vbCQqA7HF9IY0ST+Q==",
"version": "5.13.0",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-5.13.0.tgz",
"integrity": "sha512-kGtcJaElNRAdAGsCNykFSZ7dBKpL14Cbs+VaQ8cECxQlRPDjBlMHNFYeYt0SKovAVy2Y65JXQwB3A5+zIQwnTg==",
"devOptional": true,
"hasInstallScript": true,
"dependencies": {
"@prisma/engines": "5.7.0"
"@prisma/engines": "5.13.0"
},
"bin": {
"prisma": "build/index.js"
@ -1864,6 +1934,12 @@
}
]
},
"node_modules/regenerator-runtime": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
"dev": true
},
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@ -1930,9 +2006,9 @@
}
},
"node_modules/semver": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
@ -2033,22 +2109,28 @@
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"node_modules/ts-algebra": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-1.2.2.tgz",
"integrity": "sha512-kloPhf1hq3JbCPOTYoOWDKxebWjNb2o/LKnNfkWhxVVisFFmMJPPdJeGoGmM+iRLyoXAR61e08Pb+vUXINg8aA==",
"dev": true
},
"node_modules/ts-api-utils": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz",
"integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==",
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
"integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==",
"dev": true,
"engines": {
"node": ">=16.13.0"
"node": ">=16"
},
"peerDependencies": {
"typescript": ">=4.2.0"
}
},
"node_modules/ts-mixer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.3.tgz",
"integrity": "sha512-k43M7uCG1AkTyxgnmI5MPwKoUvS/bRvLvUb7+Pgpdlmok8AoqmUaZxUUw8zKM5B1lqZrt41GjYgnvAi0fppqgQ=="
"version": "6.0.4",
"resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz",
"integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA=="
},
"node_modules/tslib": {
"version": "2.6.2",
@ -2080,9 +2162,9 @@
}
},
"node_modules/typescript": {
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
"integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
"version": "5.4.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
"integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@ -2117,9 +2199,9 @@
}
},
"node_modules/web-streams-polyfill": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz",
"integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==",
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
"integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
"engines": {
"node": ">= 8"
}

View file

@ -11,20 +11,21 @@
"author": "Wroclaw",
"license": "MIT",
"dependencies": {
"@prisma/client": "5.7.0",
"discord.js": "^14.8.0",
"@prisma/client": "5.13.0",
"discord.js": "14.14.1",
"fold-to-ascii": "^5.0.1",
"gpt-3-encoder": "^1.1.4",
"openai": "^4.10.0",
"openai": "^4.38.3",
"require-directory": "^2.1.1",
"typescript": "^5.1.6"
"typescript": "^5.4.5"
},
"devDependencies": {
"@types/fold-to-ascii": "^5.0.0",
"@types/require-directory": "^2.1.2",
"@typescript-eslint/eslint-plugin": "^6.2.0",
"@typescript-eslint/parser": "^6.2.0",
"eslint": "^8.46.0",
"prisma": "5.7.0"
"@typescript-eslint/eslint-plugin": "^7.7.1",
"@typescript-eslint/parser": "^7.7.1",
"eslint": "^8.57.0",
"json-schema-to-ts": "^3.0.1",
"prisma": "5.13.0"
}
}

View file

@ -4,13 +4,25 @@ import { REST, RESTGetAPIOAuth2CurrentApplicationResult, RESTPostAPIApplicationC
import { config } from "../src/index";
import requireDirectory from "require-directory";
import Command from "../src/command";
import
Command
, {
ApplicationIntegrationType
, InteractionContextTypes
, InteractionTypeMap
} from "../src/command";
const post: RESTPostAPIApplicationCommandsJSONBody[] = [];
const guildId = process.argv.slice(2)[0];
const importedCommands = requireDirectory(module, "../src/commands");
function isGuildCommand(command: Command<keyof InteractionTypeMap>): boolean {
// guild Commmand is when it's a guild install and context is guild (and these are defaults if not provided)
return (command.integration_types?.includes(ApplicationIntegrationType.Guild_Install) ?? true)
&& (command.contexts?.includes(InteractionContextTypes.Guild) ?? true);
}
for (const obj in importedCommands) {
try {
const allExports = importedCommands[obj] as {default: unknown};
@ -19,6 +31,10 @@ for (const obj in importedCommands) {
// @ts-expect-error
const constructedExport = new defaultExport() as unknown;
if (!(constructedExport instanceof Command)) throw new Error(`${obj}'s default does not extends Command`);
if (guildId && guildId !== "" && isGuildCommand(constructedExport as Command<keyof InteractionTypeMap>)) {
console.log(`Skipping ${obj} because it's not a guild command`);
continue;
}
post.push(constructedExport.toRESTPostApplicationCommands());
} catch (e) {
console.error(e);

View file

@ -1,17 +1,16 @@
{ pkgs ? import <nixpkgs> {} }:
{ pkgs ? import <nixpkgs> {}
, unstable ? import <nixos-unstable> {}
}:
let
# Updating this package will force an update for nodePackages.prisma. The
# version of prisma-engines and nodePackages.prisma must be the same for them to
# function correctly.
prisma-version = "5.7.0";
prisma-version = "5.13.0";
prisma-src = pkgs.fetchFromGitHub {
owner = "prisma";
repo = "prisma-engines";
rev = prisma-version;
hash = "sha256-gZEz0UtgNwumsZbweAyx3TOVHJshpBigc9pzWN7Gb/A=";
hash = "sha256-8LC2RV3FRr1F0TZxQNxvvEoTyhKusgzB5omlxLAnHG0=";
};
new-prisma-engines = pkgs.rustPlatform.buildRustPackage {
new-prisma-engines = unstable.rustPlatform.buildRustPackage {
pname = "prisma-engines";
version = prisma-version;
@ -30,10 +29,10 @@ let
cargoLock = {
lockFile = "${prisma-src}/Cargo.lock";
outputHashes = {
"cuid-1.3.2" = "sha256-ZihFrLerEIOdbJggaBbByRbC1sZRvF4M0LN2albB7vA=";
"cuid-1.3.2" = "sha256-qBu1k/dJiA6rWBwk4nOOqouIneD9h2TTBT8tvs0TDfA=";
"barrel-0.6.6-alpha.0" = "sha256-USh0lQ1z+3Spgc69bRFySUzhuY79qprLlEExTmYWFN8=";
"graphql-parser-0.3.0" = "sha256-0ZAsj2mW6fCLhwTETucjbu4rPNzfbNiHu2wVTBlTNe4=";
"mysql_async-0.31.3" = "sha256-QIO9s0Upc0/1W7ux1RNJNGKqzO4gB4gMV3NoakAbxkQ=";
"mysql_async-0.31.3" = "sha256-2wOupQ/LFV9pUifqBLwTvA0tySv+XWbxHiqs7iTzvvg=";
"postgres-native-tls-0.5.0" = "sha256-UYPsxhCkXXWk8yPbqjNS0illwjS5mVm3Z/jFwpVwqfw=";
};
};
@ -62,14 +61,6 @@ let
# Tests are long to compile
doCheck = false;
# meta = with lib; {
# description = "A collection of engines that power the core stack for Prisma";
# homepage = "https://www.prisma.io/";
# license = licenses.asl20;
# platforms = platforms.unix;
# maintainers = with maintainers; [ pimeys tomhoule ivan aqrln ];
# };
};
in
pkgs.mkShell {

View file

@ -2,12 +2,47 @@ import { AutocompleteInteraction, PermissionsBitField } from "discord.js";
import { RESTPostAPIApplicationCommandsJSONBody } from "discord.js";
import { APIApplicationCommandOption, ApplicationCommandType, ChatInputCommandInteraction, LocalizationMap, MessageInteraction, PermissionResolvable, UserSelectMenuInteraction } from "discord.js";
type InteractionTypeMap = {
export type InteractionTypeMap = {
// [CommandType]: [Interaction, Description]
[ApplicationCommandType.ChatInput]: [ChatInputCommandInteraction, string];
[ApplicationCommandType.Message]: [MessageInteraction, never];
[ApplicationCommandType.User]: [UserSelectMenuInteraction, never];
};
// TODO: At time of coding, Discord api types doesn't support user installations of bot/application yet
// replace this with the types from the discord api types when it's available
/**
* https://discord.com/developers/docs/resources/application#application-object-application-integration-types
*/
export enum ApplicationIntegrationType {
Guild_Install = 0,
User_Install = 1,
}
/**
* https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-interaction-context-types
*/
export enum InteractionContextTypes {
Guild = 0,
BotDM = 1,
PrivateChannel = 2,
}
/**
* https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-structure
*/
export type FutureRESTPostAPIApplicationCommandsJSONBody =
RESTPostAPIApplicationCommandsJSONBody
& {
/**
* @deprecated use contexts instead
*/
dm_permission?: boolean;
integration_types?: ApplicationIntegrationType[];
contexts?: InteractionContextTypes[];
};
interface Command<Type extends keyof InteractionTypeMap = ApplicationCommandType> {
readonly name: string;
readonly name_localizations?: LocalizationMap;
@ -17,7 +52,10 @@ interface Command<Type extends keyof InteractionTypeMap = ApplicationCommandType
readonly default_member_permissions?: PermissionResolvable;
readonly type: Type;
readonly nsfw?: boolean;
/** @deprecated use contexts instead */
readonly dm_permission?: boolean;
readonly integration_types?: ApplicationIntegrationType[];
readonly contexts?: InteractionContextTypes[];
}
abstract class Command<Type extends keyof InteractionTypeMap = ApplicationCommandType> {
@ -25,7 +63,7 @@ abstract class Command<Type extends keyof InteractionTypeMap = ApplicationComman
autocomplete?(interaction: Type extends ApplicationCommandType.ChatInput ? AutocompleteInteraction : never ): Promise<void>;
toRESTPostApplicationCommands(): RESTPostAPIApplicationCommandsJSONBody {
toRESTPostApplicationCommands(): FutureRESTPostAPIApplicationCommandsJSONBody {
return {
name: this.name,
name_localizations: this.name_localizations,
@ -36,6 +74,8 @@ abstract class Command<Type extends keyof InteractionTypeMap = ApplicationComman
type: this.type,
nsfw: this.nsfw,
dm_permission: this.dm_permission,
integration_types: this.integration_types,
contexts: this.contexts,
};
}
}

90
src/commands/ask.ts Normal file
View file

@ -0,0 +1,90 @@
import {
APIApplicationCommandOption
, ApplicationCommandOptionType
, ApplicationCommandType
, ChatInputCommandInteraction
} from "discord.js";
import { ChatCompletionMessageParam } from "openai/resources";
import
Command
,{ApplicationIntegrationType
, InteractionContextTypes
} from "../command";
import { config } from "../index";
import { executeChatCompletion, replyInMultiMessage } from "../execution";
import { formatName } from "../toOpenAIMessages";
export default class Ask extends Command implements Command {
name = "ask";
description = "Promts the bot to reply to a single message without any history context";
type = ApplicationCommandType.ChatInput;
options: APIApplicationCommandOption[] = [
{
name: "content",
description: "The content of the prompt",
type: ApplicationCommandOptionType.String,
required: true,
},
{
name: "ephemeral",
description: "if true, only you can see the response (default true)",
type: ApplicationCommandOptionType.Boolean,
required: false,
}
];
integration_types = [
ApplicationIntegrationType.Guild_Install,
ApplicationIntegrationType.User_Install,
];
contexts = [
InteractionContextTypes.Guild,
InteractionContextTypes.BotDM,
InteractionContextTypes.PrivateChannel,
];
async execute(interaction: ChatInputCommandInteraction) {
const content = interaction.options.getString("content", true);
const ephemeral = interaction.options.getBoolean("ephemeral", false) ?? true;
if (!interaction.channel && !interaction.channelId) {
console.error("No channel found in interaction");
console.error(interaction);
await interaction.reply({
content: "No channel found in interaction???",
ephemeral: true
});
return;
}
const userLimit = await config.quota.checkUser(interaction.user, interaction);
if (userLimit.used >= userLimit.quota) {
interaction.reply({
embeds: [{
color: 0xff0000,
description: "You've used up your quota,\n" + userLimit.toString(),
}],
ephemeral: true,
}).catch(e => {
console.error("Failed to reply to user: ", e);
});
return;
}
// TODO: check content in moderation API
const messages: ChatCompletionMessageParam[] = [
...config.systemPrompt(interaction),
{ role: "user", name: formatName(interaction.user.displayName), content }
];
const [answer] = await Promise.all([
executeChatCompletion(messages, interaction),
interaction.deferReply({ ephemeral }),
]);
await replyInMultiMessage(answer.choices[0].message.content, interaction);
}
}

View file

@ -1,6 +1,10 @@
import { ApplicationCommandType, ChatInputCommandInteraction, APIApplicationCommandOption, ApplicationCommandOptionType, APIEmbedField } from "discord.js";
import Command from "../command";
import
Command
,{ApplicationIntegrationType
, InteractionContextTypes
} from "../command";
import { config } from "../index";
export default class MyLimit extends Command implements Command {
@ -21,6 +25,15 @@ export default class MyLimit extends Command implements Command {
required: false,
}
];
integration_types = [
ApplicationIntegrationType.Guild_Install,
ApplicationIntegrationType.User_Install
];
contexts = [
InteractionContextTypes.Guild,
InteractionContextTypes.BotDM,
InteractionContextTypes.PrivateChannel
];
async execute(interaction: ChatInputCommandInteraction) {
let recoveryFor = interaction.options.getInteger("recovery-for", false);

26
src/commands/listen.ts Normal file
View file

@ -0,0 +1,26 @@
import {ApplicationCommandType, ChatInputCommandInteraction } from "discord.js";
import Command, { ApplicationIntegrationType, InteractionContextTypes } from "../command";
export default class Listen extends Command implements Command {
// This command exists because Discord bots don't receive direct messages
// unless they explicitly open a DM channel with the user
name = "listen";
description = "Makes the bot listen on your direct messages";
type = ApplicationCommandType.ChatInput;
options = [];
integration_types = [
ApplicationIntegrationType.Guild_Install,
ApplicationIntegrationType.User_Install
];
contexts = [InteractionContextTypes.BotDM];
async execute(interaction: ChatInputCommandInteraction) {
await interaction.user.createDM();
await interaction.reply({
content: "I'm now listening to your direct messages",
ephemeral: true,
});
}
}

View file

@ -1,4 +1,8 @@
import { Message } from "discord.js";
import {
ActivityType
, type PresenceStatusData
, type PresenceData
} from "discord.js";
import {
ChatCompletionMessageParam as OpenAIMessage,
ChatCompletionCreateParamsNonStreaming as ChatCompletionRequestData,
@ -6,18 +10,20 @@ import {
import IQuota from "./IQuota";
import MessageCount from "./quota/messageCount";
import { apiRequest } from "./execution";
export interface IConfigRequired {
readonly calendarParams: Intl.DateTimeFormatOptions;
/** Tokens to authentiate with */
readonly tokens: {
readonly Discord: string;
readonly OpenAI: string;
};
/** Discord bot status */
readonly status: PresenceData
/** Messages to append at the start of every chat history when sending to API */
systemPrompt(context: Message): OpenAIMessage[];
systemPrompt(context: apiRequest): OpenAIMessage[];
/** OpenAI model config */
readonly chatCompletionParams: Omit<ChatCompletionRequestData, "messages" | "function_call" | "functions" | "n">;
readonly chatCompletionParams: Omit<ChatCompletionRequestData, "messages" | "function_call" | "tool_call" | "functions" | "n">;
/** Limits for message selection */
readonly readLimits: {
/** Maximum message age to include (in miliseconds) */
@ -40,6 +46,10 @@ export default function newConfig(config?: IConfig): IConfigRequired {
return { ...defaultConfig, ...config };
}
function isEnvDefined(key: string): boolean {
return process.env[key] !== undefined;
}
function envAsString(key: string): string | undefined {
key = key.toLocaleUpperCase();
return process.env[key];
@ -51,16 +61,54 @@ function envAsNumber(key: string): number | undefined {
return !Number.isNaN(value) ? value : undefined;
}
function envAsBoolean(key: string): boolean | undefined {
key = key.toUpperCase();
const value = process.env[key];
return !(value === "false" || value === "0");
}
function envAsActivityType(key: string): ActivityType | undefined {
key = key.toUpperCase();
const value = process.env[key]?.toUpperCase();
switch (value) {
case "0":
case "PLAYING":
return ActivityType.Playing;
case "1":
case "STREAMING":
return ActivityType.Streaming;
case "2":
case "LISTENING":
return ActivityType.Listening;
case "3":
case "WATCHING":
return ActivityType.Watching;
case "4":
case "CUSTOM":
return ActivityType.Custom;
case "5":
case "COMPETING":
return ActivityType.Competing;
default:
return undefined;
}
}
function envAsPresenceStatusData(key: string): PresenceStatusData | undefined {
key = key.toUpperCase();
const value = process.env[key]?.toLowerCase();
switch (value) {
case "online":
case "idle":
case "dnd":
case "invisible":
return value;
default:
return undefined;
}
}
const defaultConfig: IConfigRequired = {
calendarParams: {
weekday: "short",
year: "numeric",
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
hour12: false,
},
tokens: {
Discord: envAsString("TOKENS__DISCORD") ?? "",
OpenAI: envAsString("TOKENS__OPENAI") ?? "",
@ -75,6 +123,16 @@ const defaultConfig: IConfigRequired = {
},
];
},
status: {
activities: isEnvDefined("STATUS__NAME") ? [{
name: envAsString("STATUS__NAME") as string,
type: envAsActivityType("STATUS__TYPE") ?? ActivityType.Custom,
state: envAsString("STATUS__STATE"),
url: envAsString("STATUS__URL"),
}] : undefined,
status: envAsPresenceStatusData("STATUS__STATUS"),
afk: envAsBoolean("STATUS__AFK"),
},
chatCompletionParams: {
model: envAsString("CHAT_COMPLETION_PARAMS__MODEL") ?? "gpt-3.5-turbo",
max_tokens: envAsNumber("CHAT_COMPLETION_PARAMS__MAX_TOKENS") ?? 384,

View file

@ -73,7 +73,7 @@ export function getAuthor(request: apiRequest) {
* @returns Promise of the done action
*/
function requestReply(
request: RequestMessage,
request: apiRequest,
message: DiscordApi.MessageReplyOptions & DiscordApi.InteractionReplyOptions,
// TODO: add support for these below
replyOptions: DiscordApi.MessageReplyOptions = {},
@ -172,29 +172,42 @@ export async function queueRequest(request: apiRequest) {
* Logs used tokens to the terminal and to the database
* @param answer the response that OpenAI returned
* @param message the message that initiated the execution
* @param functionRan counter of how many function have been ran
* @param functionRan counter of how many function have been ran (to distinct records in database)
*/
function logUsedTokens(
answer: ChatCompletion,
message: RequestMessage,
functionRan: number,
message: apiRequest | undefined = undefined,
functionRan: number = 0,
) {
const usage = answer.usage;
const functionName = answer.choices[0].message?.function_call?.name;
const functionNames =
answer.choices[0].message.tool_calls?.map(
v => v.type === "function" ? v.function.name : `[unknown type]`
);
if (usage !== undefined) {
const channelName: string = !message.channel.isDMBased() ? `${message.channel.name} (${message.guild?.name})` : `@${getAuthor(message).tag}`;
console.log(`Used ${usage.total_tokens} (${usage.prompt_tokens} + ${usage.completion_tokens}) tokens for ${getAuthor(message).tag} (${getAuthor(message).id}) in #${channelName}${functionName ? " [Function: " + functionName + "]" : ""}`);
if (!message) {
// log usage to stdout even if we can't store it in database
console.warn(`Used ${usage.total_tokens} (${usage.prompt_tokens} + ${usage.completion_tokens}) tokens from unknown call`);
// it doesn't make sense to store usage in database if we don't know where it came from
return;
}
const channelName: string = !message.channelId ? "[No channel]"
: !message.channel ? `[Unknown channel: ${message.channelId}]`
: !message.channel.isDMBased() ? `#${message.channel.name} (${message.guild?.name})`
: `#@${getAuthor(message).tag}`
;
console.log(`Used ${usage.total_tokens} (${usage.prompt_tokens} + ${usage.completion_tokens}) tokens for ${getAuthor(message).tag} (${getAuthor(message).id}) in ${channelName}${functionNames && functionNames.length > 0 ? " [Tools: " + functionNames.join(", ") + "]" : ""}`);
database.usage.create({
data: {
timestamp: message.createdAt,
user: BigInt(getAuthor(message).id),
channel: BigInt(message.channelId),
channel: BigInt(message.channelId ?? 0),
guild: message.guildId ? BigInt(message.guildId) : null,
usageRequest: usage.prompt_tokens,
usageResponse: usage.completion_tokens,
functionName: functionName ?? null,
functionRan: functionName ? functionRan : 0,
functionName: functionNames?.join(", ") ?? null,
functionRan: functionRan,
}
}).catch((e => {
console.error("Failed to push to a database");
@ -210,9 +223,7 @@ function logUsedTokens(
async function executeFromQueue(channel: string) {
const channelQueue = channelsRunning.get(channel) as ChannelsRunningValue;
const message = channelQueue.at(0) as RequestMessage;
let functionRanCounter = 0;
let OpenAImessages: ChatCompletionMessageParam[] = [];
let modelUsernameMap: Map<string, string>;
// ignore if we can't even send anything to reply
if (!canReplyToRequest(message)) return;
@ -238,57 +249,14 @@ async function executeFromQueue(channel: string) {
return b.createdTimestamp - a.createdTimestamp;
});
[OpenAImessages, modelUsernameMap] = toOpenAIMessages(messages.values());
let generatedMessage: ChatCompletionMessage | undefined = undefined;
let answer: Awaited<ReturnType<typeof openai.chat.completions.create>>;
do {
answer = await openai.chat.completions.create({
...config.chatCompletionParams,
messages: OpenAImessages,
// FIXME: don't use new instance of FunctionManager
functions: new FunctionManager().getFunctions(),
});
logUsedTokens(answer, message, ++functionRanCounter);
generatedMessage = answer.choices[0].message;
if (!generatedMessage) throw new Error("Empty message received");
// handle function calls
if (generatedMessage.function_call) {
OpenAImessages.push(generatedMessage);
// FIXME: don't use new instance of FunctionManager
OpenAImessages.push(
new FunctionManager().handleFunction(generatedMessage.function_call, modelUsernameMap)
);
}
} while (generatedMessage.function_call);
OpenAImessages = toOpenAIMessages(messages.values());
const answer = await executeChatCompletion(OpenAImessages, message);
channelQueue.stopTyping();
const answerContent = answer.choices[0].message?.content;
if (answerContent === null || answerContent === "") {
if (message instanceof DiscordApi.Message) message.react("😶").catch(() => {/* GRACEFAIL: It's okay if the bot won't reply */});
}
else {
const answerMessagesContent :string[] = [""];
for (const i of answerContent.split(/\n\n/)) {
if (answerMessagesContent[answerMessagesContent.length-1].length + i.length < 2000) {
answerMessagesContent[answerMessagesContent.length-1] += "\n\n" + i;
}
else {
answerMessagesContent.push(i);
}
}
for (const i of answerMessagesContent) {
const response = requestReply(message, {content: i}, {allowedMentions: { repliedUser: false }});
await response.then(rval => Moderation.checkMessageNoReturn(rval));
}
}
await replyInMultiMessage(answerContent, message);
} catch (e) {
let errorText: string = "";
channelQueue.stopTyping();
@ -342,3 +310,70 @@ async function executeFromQueue(channel: string) {
else
return executeFromQueue(channel);
}
/**
* Replies to a message and splits to multiple messages if needed.
* @param answerContent - The content of the answer.
* @param message - The request message to reply to.
*/
export async function replyInMultiMessage(answerContent: string | null, message: apiRequest) {
if (answerContent === null || answerContent === "") {
if (message instanceof DiscordApi.Message) message.react("😶").catch(() => { });
}
else {
const answerMessagesContent: string[] = [""];
for (const i of answerContent.split(/\n\n/)) {
if (answerMessagesContent[answerMessagesContent.length - 1].length + i.length < 2000) {
answerMessagesContent[answerMessagesContent.length - 1] += "\n\n" + i;
}
else {
answerMessagesContent.push(i);
}
}
for (const i of answerMessagesContent) {
const response = requestReply(message, { content: i }, { allowedMentions: { repliedUser: false } });
await response.then(rval => Moderation.checkMessageNoReturn(rval));
}
}
}
/**
* Executes the chat completion process.
*
* @param OpenAImessages An array of ChatCompletionMessageParam objects representing the messages for chat completion.
* @param message An optional RequestMessage object representing the request message, used for logging.
* @returns A Promise that resolves to the answer from the chat completion process.
*/
export async function executeChatCompletion(
OpenAImessages: ChatCompletionMessageParam[],
message: apiRequest | undefined,
) {
let generatedMessage: ChatCompletionMessage | undefined = undefined;
let answer: Awaited<ReturnType<typeof openai.chat.completions.create>>;
let functionRanCounter = 0;
do {
answer = await openai.chat.completions.create({
...config.chatCompletionParams,
messages: OpenAImessages,
// FIXME: don't use new instance of FunctionManager
tools: new FunctionManager().getToolsForOpenAi(),
});
functionRanCounter += answer.choices[0].message?.tool_calls?.length ?? 0;
logUsedTokens(answer, message, ++functionRanCounter);
generatedMessage = answer.choices[0].message;
if (!generatedMessage) throw new Error("Empty message received");
// handle tool calls
if (generatedMessage.tool_calls !== undefined && generatedMessage.tool_calls.length > 0) {
OpenAImessages.push(generatedMessage);
// FIXME: don't use new instance of FunctionManager
OpenAImessages.push(...(await new FunctionManager().handleToolCalls(generatedMessage.tool_calls)));
}
} while (generatedMessage.tool_calls !== undefined && generatedMessage.tool_calls.length > 0);
return answer;
}

View file

@ -1,52 +1,43 @@
import { ChatCompletionCreateParams, ChatCompletionMessage, ChatCompletionMessageParam } from "openai/resources/chat";
import { FunctionDefinition } from "openai/resources";
import {
ChatCompletionMessageParam
, ChatCompletionMessageToolCall
, ChatCompletionTool
} from "openai/resources/chat";
import { type FromSchema, type JSONSchema } from "json-schema-to-ts";
import { config } from "./index";
type OpenAIFunctionRequestData = (JSONSchema & {
type: "object"
});
type parameterMap = {
string: string,
number: number,
};
type ChatCompletionToolDefinition = ChatCompletionTool;
type ChatCompletionToolCall = ChatCompletionMessageToolCall;
type nameTypeMap = {[name: string]: keyof parameterMap} | Record<string, never>;
type OpenAIFunctionRequestData<T extends nameTypeMap> = {
[name in keyof T]: T[name];
};
type ChatCompletionFunctions = ChatCompletionCreateParams.Function;
type ChatCompletionFunctionCall = ChatCompletionMessage.FunctionCall;
type ChatCompletionFunctionDefinition = FunctionDefinition;
/**
* Represents the function that can be ran by the OpenAI model
*/
export interface OpenAIFunction<T extends nameTypeMap = nameTypeMap> {
export interface OpenAIFunction<
T extends Readonly<OpenAIFunctionRequestData> = Readonly<OpenAIFunctionRequestData>
> {
name: string,
description?: string,
parameters: {
type: "object",
properties: T extends Record<string, never> ? Record<string, never> : {
[name in T[string]]: {
type: T[name],
description?: string,
}
},
required?: Array<keyof T>,
},
parameters: T,
}
export abstract class OpenAIFunction<T extends nameTypeMap = nameTypeMap> {
getSettings(): ChatCompletionFunctions {
export abstract class OpenAIFunction<
T extends Readonly<OpenAIFunctionRequestData> = Readonly<OpenAIFunctionRequestData>
> {
getSettings(): ChatCompletionFunctionDefinition {
return {
name: this.name,
description: this.description,
parameters: this.parameters,
parameters: this.parameters as Record<string, unknown>,
};
}
abstract execute(
data: OpenAIFunctionRequestData<T>,
modelUsernameMap: Map<string, string>,
): string;
abstract execute(data: FromSchema<T>): Promise<string>;
}
/*
@ -56,63 +47,70 @@ export default class FunctionManager {
store = new Map<string, OpenAIFunction>();
constructor() {
this.store.set("getTime", new GetTime());
// TODO: import functions from functions directory
}
public getFunctions(): ChatCompletionFunctions[] {
const rvalue: ChatCompletionFunctions[] = [];
public getTools(): ChatCompletionToolDefinition[] {
const rvalue: ChatCompletionToolDefinition[] = [];
for (const [, value] of this.store) {
rvalue.push(value.getSettings());
rvalue.push({type: "function", function: value.getSettings()});
}
return rvalue;
}
public handleFunction(
request: ChatCompletionFunctionCall,
modelUsernameMap: Map<string, string> = new Map(),
): ChatCompletionMessageParam {
public getToolsForOpenAi(): ChatCompletionTool[] | undefined {
const rvalue = this.getTools();
return rvalue.length > 0 ? rvalue : undefined;
}
public handleFunction(request: ChatCompletionToolCall): Promise<ChatCompletionMessageParam> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let parsedArguments: any;
const functionToRun = this.store.get(request.name ?? "");
const functionToRun = this.store.get(request.function.name);
// check if the function is registered
if (!functionToRun) {
return {
return Promise.resolve({
role: "system",
content: "Only use functions that were provided to you",
};
content: `Only use functions that were provided to you (response for tool call ID: ${request.id})`,
});
}
try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
parsedArguments = JSON.parse(request.arguments ?? "");
parsedArguments = JSON.parse(request.function.arguments);
}
catch (e) {
console.error("Function arguments raw: " + request.arguments);
throw new Error(`Failed to parse the function JSON arguments when running function [${request.name}]`, {cause: e});
console.error("Function arguments raw: " + request.function.arguments);
throw new Error(`Failed to parse the function JSON arguments when running function [${request.function.name}]`, {cause: e});
}
// FIXME: Verify if the parsedArguments matches the requested function argument declaration.
return {
role: "function",
name: request.name,
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
content: functionToRun.execute(parsedArguments, modelUsernameMap),
return functionToRun.execute(parsedArguments).then(content => {
return {
role: "tool",
tool_call_id: request.id,
content: content,
};
}
});
}
// buildins
public handleToolCall(call: ChatCompletionToolCall): Promise<ChatCompletionMessageParam> {
if (call.type === "function") {
return this.handleFunction(call);
}
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
throw new Error(`Unsupported tool call type: ${call.type || "never"}`);
}
class GetTime extends OpenAIFunction<Record<string, never>> {
name = "getTime";
description = "Gets current date and time with a timezone attached";
parameters = {
type: "object" as const,
properties: {} as Record<string, never>,
};
execute(): string {
return `${Intl.DateTimeFormat().resolvedOptions().timeZone}): ${new Date().toLocaleString("en-US", config.calendarParams)}`;
public handleToolCalls(calls: ChatCompletionToolCall[]) {
const rvalue: Promise<ChatCompletionMessageParam>[] = [];
for (const call of calls) {
if (call.type === "function") {
rvalue.push(this.handleToolCall(call));
}
}
return Promise.all(rvalue);
}
}

View file

@ -11,6 +11,7 @@ const discord = new DiscordApi.Client({
intents: [
DiscordApi.GatewayIntentBits.Guilds,
DiscordApi.GatewayIntentBits.GuildMessages,
DiscordApi.GatewayIntentBits.DirectMessages,
DiscordApi.GatewayIntentBits.MessageContent,
]
});
@ -54,13 +55,29 @@ interactionManager.bindClient(discord);
discord.on("ready", event => {
console.log(`Connected to Discord as ${event.user.tag} (${event.user.id})`);
event.user.setPresence(config.status);
});
discord.on("messageCreate", message => {
if (message.author.bot) return;
if (!message.mentions.has(message.client.user)) return;
if (!message.channel.isDMBased()) {
if (!message.mentions.has(message.client.user, { ignoreEveryone: true })) return;
}
return queueRequest(message);
});
if (require.main === module) void discord.login(config.tokens.Discord);
if (require.main === module) {
void discord.login(config.tokens.Discord);
process.on("SIGINT", () => {
console.log("got SIGINT, exiting");
//FIXME: finish executing requests then exit
discord.destroy()
.then(() => process.exit())
.catch((e) => {
console.error("Failed to gracefully exit");
console.error(e);
process.exit();
});
});
}

View file

@ -63,7 +63,7 @@ export function formatMessage(message: DiscordMessage): string {
* @param name the name to format
* @returns formatted name
*/
function formatName(name: string): string {
export function formatName(name: string): string {
// replace all characters to ascii equivelant
return FoldToAscii.foldReplacing(name)
// White spaces are not allowed
@ -95,34 +95,28 @@ function getAuthorUsername(message: DiscordMessage): string | undefined {
/**
* Converts the Iterable of Discord Messages to array of OpenAI Messages to send
* first message in the interable will be the last of the returned array (reverse order)
* (first should be newest)
* @param messages the iterable to convert
* @returns [0] the converted messages
* @returns [1] username mappings to id
* @returns the converted messages
*/
export default function toOpenAIMessages(
messages: Iterable<DiscordMessage>,
): [OpenAIMessage[], Map<string, string>] {
const rmessages: OpenAIMessage[] = [];
const rUserMap = new Map<string, string>();
): OpenAIMessage[] {
const rvalue: OpenAIMessage[] = [];
let tokenCount = 0;
for (const message of messages) {
// FIXME: multiple users could have the same name here.
const content = formatMessage(message);
// FIXME: tokens are not being counted properly (it's lower than it is) but it's enough for me for now.
tokenCount += countTokens(content);
if (tokenCount > config.readLimits.tokens) break;
const name = getAuthorUsername(message);
rmessages.push({
rvalue.push({
role: message.author.id === message.client.user.id ? "assistant" : "user",
content: content,
name: name,
name: getAuthorUsername(message),
});
if (name && rUserMap.has(name)) rUserMap.set(name, message.author.id);
}
rmessages.push(...config.systemPrompt([...messages][0]).reverse());
rvalue.push(...config.systemPrompt([...messages][0]).reverse());
return [rmessages.reverse(), rUserMap];
return rvalue.reverse();
}