From e05ecbebf72d521b2c9acba03d0cb5956ccb91c5 Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Mon, 4 Nov 2024 00:48:18 +0000 Subject: [PATCH 1/7] feat: /newtask command --- .cspell.json | 4 +- .github/workflows/compute.yml | 2 +- .github/workflows/update-configuration.yml | 4 +- .../features/commands/shared/task-creation.ts | 66 +++++++++++++++++++ src/bot/index.ts | 2 + src/bot/setcommands.ts | 8 +++ 6 files changed, 82 insertions(+), 4 deletions(-) create mode 100644 src/bot/features/commands/shared/task-creation.ts diff --git a/.cspell.json b/.cspell.json index 956c53e..014b2cf 100644 --- a/.cspell.json +++ b/.cspell.json @@ -41,7 +41,9 @@ "Superbase", "SUPABASE", "CODEOWNER", - "nosniff" + "nosniff", + "newtask", + "supergroup" ], "dictionaries": ["typescript", "node", "software-terms"], "import": ["@cspell/dict-typescript/cspell-ext.json", "@cspell/dict-node/cspell-ext.json", "@cspell/dict-software-terms"], diff --git a/.github/workflows/compute.yml b/.github/workflows/compute.yml index 329c9f7..374dacf 100644 --- a/.github/workflows/compute.yml +++ b/.github/workflows/compute.yml @@ -39,4 +39,4 @@ jobs: env: TELEGRAM_BOT_ENV: ${{ secrets.TELEGRAM_BOT_ENV }} APP_ID: ${{ secrets.APP_ID }} - APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }} \ No newline at end of file + APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }} diff --git a/.github/workflows/update-configuration.yml b/.github/workflows/update-configuration.yml index 96314ab..44f0938 100644 --- a/.github/workflows/update-configuration.yml +++ b/.github/workflows/update-configuration.yml @@ -13,8 +13,8 @@ jobs: steps: - uses: ubiquity-os/action-deploy-plugin@main with: - pluginEntry: '${{ github.workspace }}/src/workflow-entry.ts' - schemaPath: '${{ github.workspace }}/src/types/plugin-inputs.ts' + pluginEntry: "${{ github.workspace }}/src/workflow-entry.ts" + schemaPath: "${{ github.workspace }}/src/types/plugin-inputs.ts" env: APP_ID: ${{ secrets.APP_ID }} APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }} diff --git a/src/bot/features/commands/shared/task-creation.ts b/src/bot/features/commands/shared/task-creation.ts new file mode 100644 index 0000000..773cffe --- /dev/null +++ b/src/bot/features/commands/shared/task-creation.ts @@ -0,0 +1,66 @@ +import { chatAction } from "@grammyjs/auto-chat-action"; +import { Composer } from "grammy"; +import { GrammyContext } from "../../../helpers/grammy-context"; +import { logHandle } from "../../../helpers/logging"; +import { isAdmin } from "../../../filters/is-admin"; +import { logger } from "../../../../utils/logger"; + +const composer = new Composer(); + +const feature = composer.chatType(["group", "private", "supergroup", "channel"]); + +/** + * This is responsible for creating a task on GitHub. It's going to be a direct reply + * callback to the user who wrote the comment that we'll turn into a fully featured github + * task specification. + */ + +feature.command("newtask", logHandle("task-creation"), chatAction("typing"), async (ctx: GrammyContext) => { + if (!ctx.message || !ctx.message.reply_to_message) { + logger.info(`No message or reply to message`); + return await ctx.reply("To create a new task, reply to the message with `/newtask /`"); + } + + const taskToCreate = ctx.message.reply_to_message.text; + + console.log("taskToCreate", taskToCreate); + + if (!taskToCreate || taskToCreate.length < 10) { + return await ctx.reply("A new task needs substantially more content than that"); + } + + const repoToCreateIn = ctx.message.text?.split(" ")[1]; + + if (!repoToCreateIn) { + logger.info(`No repo to create task in`); + return await ctx.reply("To create a new task, reply to the message with `/newtask /`"); + } + + const [owner, repo] = repoToCreateIn.split("/"); + + if (!owner || !repo) { + return await ctx.reply("To create a new task, reply to the message with `/newtask /`"); + } + + const fromId = ctx.message.from.id; + const isReplierAdmin = isAdmin([fromId])(ctx); + // a cheap workaround for ctx being inferred as never if not an admin fsr, needs looked into. + // ctx types are complex here with mixins and such and the grammy ctx is highly dynamic. + // my assumption is that the ctx returned by isAdmin is replacing the initial ctx type. + const replyFn = ctx.reply; + + if (!isReplierAdmin) { + logger.info(`User ${fromId} is not an admin`); + return await replyFn("Only admins can create tasks"); + } + + const task = await ctx.octokit.rest.issues.create({ + owner, + repo, + title: taskToCreate, + }); + + return await ctx.reply(`Task created: ${task.data.html_url}`); +}); + +export { composer as newTaskFeature }; diff --git a/src/bot/index.ts b/src/bot/index.ts index 3b73bcd..3855c0c 100644 --- a/src/bot/index.ts +++ b/src/bot/index.ts @@ -23,6 +23,7 @@ import { welcomeFeature } from "./features/start-command"; import { unhandledFeature } from "./features/helpers/unhandled"; import { Context } from "../types"; import { session } from "./middlewares/session"; +import { newTaskFeature } from "./features/commands/shared/task-creation"; interface Dependencies { config: Context["env"]; @@ -80,6 +81,7 @@ export async function createBot(token: string, dependencies: Dependencies, optio bot.use(userIdFeature); bot.use(chatIdFeature); bot.use(botIdFeature); + bot.use(newTaskFeature); // Private chat commands bot.use(registerFeature); diff --git a/src/bot/setcommands.ts b/src/bot/setcommands.ts index 1e790ec..6b854fa 100644 --- a/src/bot/setcommands.ts +++ b/src/bot/setcommands.ts @@ -103,6 +103,10 @@ function getPrivateChatAdminCommands(): BotCommand[] { command: "setwebhook", description: "Set the webhook URL", }, + { + command: "newtask", + description: "Create a new task", + }, ]; } @@ -112,6 +116,10 @@ function getGroupChatCommands(): BotCommand[] { command: "ban", description: "Ban a user", }, + { + command: "newtask", + description: "Create a new task", + }, ]; } From cbcc7c253af6bead0580b7e3eae3d1f5f687f144 Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Mon, 4 Nov 2024 02:15:20 +0000 Subject: [PATCH 2/7] chore: eslint ignore dist --- eslint.config.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index ae345aa..68fb8c4 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -9,7 +9,7 @@ export default tsEslint.config({ "@typescript-eslint": tsEslint.plugin, "check-file": checkFile, }, - ignores: [".github/knip.ts", "tests/**/*.ts", "eslint.config.mjs", ".wrangler/**/*.{js,ts}", "coverage/**/*.js"], + ignores: [".github/knip.ts", "tests/**/*.ts", "eslint.config.mjs", ".wrangler/**/*.{js,ts}", "coverage/**/*.js", "dist/**/*.js"], extends: [eslint.configs.recommended, ...tsEslint.configs.recommended, sonarjs.configs.recommended], languageOptions: { parser: tsEslint.parser, From 0ec3eae74ce3ac9a9eb115cb06c04a428e1dd1ec Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Mon, 4 Nov 2024 02:16:06 +0000 Subject: [PATCH 3/7] chore(deps): openai --- package.json | 1 + yarn.lock | 107 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 106 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 0b20917..5dd185f 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "grammy-guard": "0.5.0", "hono": "^4.5.9", "octokit": "^4.0.2", + "openai": "^4.70.2", "telegram": "^2.24.11", "typebox-validators": "0.3.5" }, diff --git a/yarn.lock b/yarn.lock index 3f5eb4e..14f282a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2960,6 +2960,14 @@ dependencies: "@types/node" "*" +"@types/node-fetch@^2.6.4": + version "2.6.11" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.11.tgz#9b39b78665dae0e82a08f02f4967d62c66f95d24" + integrity sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g== + dependencies: + "@types/node" "*" + form-data "^4.0.0" + "@types/node-forge@^1.3.0": version "1.3.11" resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.11.tgz#0972ea538ddb0f4d9c2fa0ec5db5724773a604da" @@ -2981,6 +2989,13 @@ dependencies: undici-types "~5.26.4" +"@types/node@^18.11.18": + version "18.19.64" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.64.tgz#122897fb79f2a9ec9c979bded01c11461b2b1478" + integrity sha512-955mDqvO2vFf/oL7V3WiUtiz+BugyX8uVbaT2H8oj3+8dRyH2FLiNdowe7eNqRM7IOIZvzDH76EoAT+gwm6aIQ== + dependencies: + undici-types "~5.26.4" + "@types/phoenix@^1.5.4": version "1.6.5" resolved "https://registry.yarnpkg.com/@types/phoenix/-/phoenix-1.6.5.tgz#5654e14ec7ad25334a157a20015996b6d7d2075e" @@ -3268,6 +3283,13 @@ acorn@^8.11.0, acorn@^8.12.0, acorn@^8.8.0, acorn@^8.9.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== +agentkeepalive@^4.2.1: + version "4.5.0" + resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.5.0.tgz#2673ad1389b3c418c5a20c5d7364f93ca04be923" + integrity sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew== + dependencies: + humanize-ms "^1.2.1" + aggregate-error@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" @@ -3527,6 +3549,11 @@ async@^3.2.3: resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + available-typed-arrays@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" @@ -3996,6 +4023,13 @@ colorette@^2.0.20: resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + commander@^12.0.0, commander@^12.1.0, commander@~12.1.0: version "12.1.0" resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3" @@ -4412,6 +4446,11 @@ defu@^6.1.4: resolved "https://registry.yarnpkg.com/defu/-/defu-6.1.4.tgz#4e0c9cf9ff68fe5f3d7f2765cc1a012dfdcb0479" integrity sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg== +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + deprecation@^2.0.0: version "2.3.1" resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" @@ -5335,6 +5374,28 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" +form-data-encoder@1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-1.7.2.tgz#1f1ae3dccf58ed4690b86d87e4f57c654fbab040" + integrity sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A== + +form-data@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.1.tgz#ba1076daaaa5bfd7e99c1a6cb02aa0a5cff90d48" + integrity sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +formdata-node@^4.3.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/formdata-node/-/formdata-node-4.4.1.tgz#23f6a5cb9cb55315912cbec4ff7b0f59bbd191e2" + integrity sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ== + dependencies: + node-domexception "1.0.0" + web-streams-polyfill "4.0.0-beta.3" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -5659,6 +5720,13 @@ human-signals@^5.0.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28" integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ== +humanize-ms@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" + integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== + dependencies: + ms "^2.0.0" + husky@9.1.5: version "9.1.5" resolved "https://registry.yarnpkg.com/husky/-/husky-9.1.5.tgz#2b6edede53ee1adbbd3a3da490628a23f5243b83" @@ -6903,6 +6971,18 @@ micromatch@^4.0.4, micromatch@^4.0.5, micromatch@^4.0.7, micromatch@~4.0.7: braces "^3.0.3" picomatch "^2.3.1" +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + mime@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/mime/-/mime-3.0.0.tgz#b374550dca3a0c18443b0c950a6a58f1931cf7a7" @@ -6994,7 +7074,7 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@^2.1.1: +ms@^2.0.0, ms@^2.1.1: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -7057,7 +7137,12 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== -node-fetch@^2.7.0: +node-domexception@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" + integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== + +node-fetch@^2.6.7, node-fetch@^2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== @@ -7264,6 +7349,19 @@ onetime@^7.0.0: dependencies: mimic-function "^5.0.0" +openai@^4.70.2: + version "4.70.2" + resolved "https://registry.yarnpkg.com/openai/-/openai-4.70.2.tgz#dfe54b2b13996b76d93f2aaac07203d2660d6e65" + integrity sha512-Q2ymi/KPUYv+LJ9rFxeYxpkVAhcrZFTVvnJbdF1pUHg9eMC6lY8PU4TO1XOK5UZzOZuuVicouRwVMi1iDrT4qw== + dependencies: + "@types/node" "^18.11.18" + "@types/node-fetch" "^2.6.4" + abort-controller "^3.0.0" + agentkeepalive "^4.2.1" + form-data-encoder "1.7.2" + formdata-node "^4.3.2" + node-fetch "^2.6.7" + optionator@^0.9.3: version "0.9.4" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" @@ -8813,6 +8911,11 @@ wcwidth@^1.0.1: dependencies: defaults "^1.0.3" +web-streams-polyfill@4.0.0-beta.3: + version "4.0.0-beta.3" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz#2898486b74f5156095e473efe989dcf185047a38" + integrity sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug== + webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" From 57d7483bbc2308500d01532468cd98e4857095b6 Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Mon, 4 Nov 2024 02:16:49 +0000 Subject: [PATCH 4/7] chore: ai adapter --- src/adapters/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/adapters/index.ts b/src/adapters/index.ts index caa57be..09a0608 100644 --- a/src/adapters/index.ts +++ b/src/adapters/index.ts @@ -1,6 +1,7 @@ import { Context } from "../types"; import { SessionManagerFactory } from "../bot/mtproto-api/bot/session/session-manager"; import { UserBaseStorage, ChatAction, HandleChatParams, StorageTypes, RetrievalHelper, Chat } from "../types/storage"; +import { Completions } from "./openai/openai"; export interface Storage { userSnapshot(chatId: number, userIds: number[]): Promise; @@ -20,8 +21,10 @@ export interface Storage { export function createAdapters(ctx: Context) { const { config: { shouldUseGithubStorage }, + env: { OPENAI_API_KEY }, } = ctx; return { storage: SessionManagerFactory.createSessionManager(shouldUseGithubStorage, ctx).storage, + ai: new Completions(OPENAI_API_KEY), }; } From d5b62e5223542feab87e8bdfd9f507871a0a04fa Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Mon, 4 Nov 2024 02:17:37 +0000 Subject: [PATCH 5/7] chore: new inputs, new env var --- src/types/env.ts | 1 + src/types/plugin-inputs.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/types/env.ts b/src/types/env.ts index b970673..202dcc9 100644 --- a/src/types/env.ts +++ b/src/types/env.ts @@ -93,6 +93,7 @@ export const env = T.Object({ APP_ID: T.String(), APP_PRIVATE_KEY: T.String(), TEMP_SAFE_PAT: T.Optional(T.String()), + OPENAI_API_KEY: T.String(), }); export type Env = StaticDecode; diff --git a/src/types/plugin-inputs.ts b/src/types/plugin-inputs.ts index 8d99f65..9affabd 100644 --- a/src/types/plugin-inputs.ts +++ b/src/types/plugin-inputs.ts @@ -20,6 +20,7 @@ export const pluginSettingsSchema = T.Object({ .Encode((value) => value.toString()), shouldUseGithubStorage: T.Boolean({ default: false }), storageOwner: T.String({ default: "ubiquity-os-marketplace" }), + maxCompletionTokens: T.Number({ default: 7000 }), }); export const pluginSettingsValidator = new StandardValidator(pluginSettingsSchema); From ccfc8bef83212244aaef17047a6f9af26b8ecaaf Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Mon, 4 Nov 2024 02:18:20 +0000 Subject: [PATCH 6/7] chore: completions --- src/adapters/openai/openai.ts | 102 ++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 src/adapters/openai/openai.ts diff --git a/src/adapters/openai/openai.ts b/src/adapters/openai/openai.ts new file mode 100644 index 0000000..5f13615 --- /dev/null +++ b/src/adapters/openai/openai.ts @@ -0,0 +1,102 @@ +import OpenAI from "openai"; +import { PluginContext } from "../../types/plugin-context-single"; + +export interface ResponseFromLlm { + answer: string; + tokenUsage: { + input: number; + output: number; + total: number; + }; +} + +export class Completions { + protected client: OpenAI; + + constructor(apiKey: string) { + this.client = new OpenAI({ apiKey: apiKey }); + } + + createSystemMessage({ + additionalContext, + constraints, + directives, + embeddingsSearch, + outputStyle, + query, + }: { + directives: string[]; + constraints: string[]; + query: string; + embeddingsSearch: string[]; + additionalContext: string[]; + outputStyle: string; + }): OpenAI.Chat.Completions.ChatCompletionMessageParam[] { + return [ + { + role: "system", + content: `You are UbiquityOS, a Telegram-integrated GitHub-first assistant for UbiquityDAO. + + # Directives + ${directives.join("\n- ")} + + # Constraints + ${constraints.join("\n- ")} + + ${embeddingsSearch.length > 0 ? `## Embeddings Search Results\n${embeddingsSearch.join("\n- ")}` : ""} + + ${additionalContext.length > 0 ? `### Additional Context\n${additionalContext.join("\n- ")}` : ""} + + # Output Style + ${outputStyle} + ` + .replace(/ {16}/g, "") + .trim(), + }, + { + role: "user", + content: query, + }, + ]; + } + + async createCompletion({ + directives, + constraints, + additionalContext, + embeddingsSearch, + outputStyle, + query, + model, + }: { + directives: string[]; + constraints: string[]; + additionalContext: string[]; + embeddingsSearch: string[]; + outputStyle: string; + query: string; + model: string; + }): Promise { + const config = PluginContext.getInstance().config; + const res: OpenAI.Chat.Completions.ChatCompletion = await this.client.chat.completions.create({ + model: model, + messages: this.createSystemMessage({ directives, constraints, query, embeddingsSearch, additionalContext, outputStyle }), + temperature: 0.2, + max_completion_tokens: config.maxCompletionTokens, + top_p: 0.5, + frequency_penalty: 0, + presence_penalty: 0, + response_format: { + type: "text", + }, + }); + const answer = res.choices[0].message; + if (answer?.content && res.usage) { + const { prompt_tokens, completion_tokens, total_tokens } = res.usage; + return { + answer: answer.content, + tokenUsage: { input: prompt_tokens, output: completion_tokens, total: total_tokens }, + }; + } + } +} From 60960625ea71f880cc811ccd108ee0d25dec0746 Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Mon, 4 Nov 2024 02:19:23 +0000 Subject: [PATCH 7/7] feat: GPT generated task spec --- .../features/commands/shared/task-creation.ts | 66 ++++++++++++++++++- 1 file changed, 63 insertions(+), 3 deletions(-) diff --git a/src/bot/features/commands/shared/task-creation.ts b/src/bot/features/commands/shared/task-creation.ts index 773cffe..9892fa7 100644 --- a/src/bot/features/commands/shared/task-creation.ts +++ b/src/bot/features/commands/shared/task-creation.ts @@ -23,8 +23,6 @@ feature.command("newtask", logHandle("task-creation"), chatAction("typing"), asy const taskToCreate = ctx.message.reply_to_message.text; - console.log("taskToCreate", taskToCreate); - if (!taskToCreate || taskToCreate.length < 10) { return await ctx.reply("A new task needs substantially more content than that"); } @@ -54,12 +52,74 @@ feature.command("newtask", logHandle("task-creation"), chatAction("typing"), asy return await replyFn("Only admins can create tasks"); } + const directives = [ + "Consume the user's message and begin to transform it into a GitHub task specification", + "Include a relevant short title for opening the task with", + "Include the task's description based on the user's message", + "Include any relevant context or constraints", + "Use a structured approach to writing the task", + "Do so without comment or directive, just the requested 'outputStyle'", + ]; + + const constraints = [ + "Never hallucinate details into the specification", + "Ensure the task is clear and actionable", + "Use GitHub flavoured markdown by default", + "Return the markdown within a code block to maintain formatting on GitHub", + "DO NOT use backticks in the markdown", + ]; + + const additionalContext = [ + "The task will be created via the GitHub app under your name; UbiquityOS", + "The correct repository will be selected by the admin who invoked this intent", + "Your output will be JSON parsed for the 'title' and 'body' keys", + "The user credit will be injected into the footer of your spec, so always leave it blank following a '---' separator", + ]; + + const outputStyle = `{ title: "Task Title", body: "Task Body" }`; + + const llmResponse = await ctx.adapters.ai.createCompletion({ + embeddingsSearch: [], + directives, + constraints, + additionalContext, + outputStyle, + model: "gpt-4o", + query: taskToCreate, + }); + + if (!llmResponse) { + return await ctx.reply("Failed to create task"); + } + + const taskFromLlm = llmResponse.answer; + + let taskDetails; + + try { + taskDetails = JSON.parse(taskFromLlm); + } catch { + return await ctx.reply("Failed to parse task"); + } + + const userCredits = await ctx.adapters.storage.retrieveUserByTelegramId(fromId); + + const username = userCredits?.github_username ?? "Anonymous"; + const chatLink = ctx.chat?.type !== "private" && (await ctx.createChatInviteLink()); + + const chatLinkText = chatLink ? ` [here](${chatLink.invite_link})` : ""; + const fullSpec = `${taskDetails.body}\n\n_Originally created by @${username} via Telegram${chatLinkText}_`; const task = await ctx.octokit.rest.issues.create({ owner, repo, - title: taskToCreate, + title: taskDetails.title, + body: fullSpec, }); + if (!task) { + return await ctx.reply("Failed to create task"); + } + return await ctx.reply(`Task created: ${task.data.html_url}`); });