From 64a30c9a28ea5e7df513acfaeff440d64c853850 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 01/18] 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 9236cd2f91326b31d001a2619309a3f080bb2496 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 02/18] 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 170872165d2c8644f4aac2f30a8db5bd9a011880 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 03/18] 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 32f4e875c3fba9965d4c1aa16206b9479550021b 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 04/18] 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 9470bcff027fe97f395efb8924d987c7175ec53e Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Wed, 6 Nov 2024 00:37:06 +0000 Subject: [PATCH 05/18] chore: deps, format, remove logs --- .cspell.json | 12 ++++- .github/workflows/compute.yml | 2 +- .github/workflows/update-configuration.yml | 4 +- eslint.config.mjs | 2 +- package.json | 3 +- src/handlers/telegram-webhook.ts | 9 ---- src/types/plugin-context-single.ts | 1 - src/types/telegram-bot-single.ts | 9 ---- yarn.lock | 60 +++++++++++++++++++++- 9 files changed, 75 insertions(+), 27 deletions(-) diff --git a/.cspell.json b/.cspell.json index 956c53e..2dba52b 100644 --- a/.cspell.json +++ b/.cspell.json @@ -41,7 +41,17 @@ "Superbase", "SUPABASE", "CODEOWNER", - "nosniff" + "nosniff", + "voyageai", + "OPENROUTER", + "reranked", + "rerank", + "Reranked", + "ftse", + "Reranking", + "VOYAGEAI", + "supergroup", + "ubiquityos" ], "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/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, diff --git a/package.json b/package.json index 5dd185f..8a058d7 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,8 @@ "octokit": "^4.0.2", "openai": "^4.70.2", "telegram": "^2.24.11", - "typebox-validators": "0.3.5" + "typebox-validators": "0.3.5", + "voyageai": "^0.0.1-5" }, "devDependencies": { "@cloudflare/workers-types": "^4.20240529.0", diff --git a/src/handlers/telegram-webhook.ts b/src/handlers/telegram-webhook.ts index 286adcb..5efed82 100644 --- a/src/handlers/telegram-webhook.ts +++ b/src/handlers/telegram-webhook.ts @@ -4,8 +4,6 @@ import { logger } from "../utils/logger"; export async function handleTelegramWebhook(request: Request, env: Env): Promise { const failures: unknown[] = []; - logger.info("Handling telegram webhook request", { request }); - // Initialize bot instance const botInstance = await initializeBotInstance(env, failures); @@ -30,7 +28,6 @@ export async function handleTelegramWebhook(request: Request, env: Env): Promise async function initializeBotInstance(env: Env, failures: unknown[]) { try { const botInstance = await TelegramBotSingleton.initialize(env); - logger.info("Initialized TelegramBotSingleton"); return botInstance; } catch (er) { const errorInfo = { @@ -46,13 +43,11 @@ async function initializeBotInstance(env: Env, failures: unknown[]) { function getServerFromBot(botInstance: TelegramBotSingleton | null, failures: unknown[]) { try { - logger.info("Getting server from bot"); const server = botInstance?.getServer(); const bot = botInstance?.getBot(); if (!server || !bot) { throw new Error("Server or bot is undefined"); } - logger.info("Got server from bot"); return { server, bot }; } catch (er) { const errorInfo = { @@ -76,9 +71,7 @@ async function makeServerRequest( if (!server) { throw new Error("Server is null"); } - logger.info("Making hono server request"); const res = await server.fetch(request, env); - logger.info("Hono server request made", { res }); return res; } catch (er) { const errorInfo = { @@ -101,7 +94,6 @@ async function readResponseBody(res: Response, failures: unknown[]): Promise Date: Wed, 6 Nov 2024 00:37:56 +0000 Subject: [PATCH 06/18] feat: embeddings adapter --- src/adapters/index.ts | 12 +-- src/adapters/supabase/embeddings.ts | 136 ++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+), 6 deletions(-) create mode 100644 src/adapters/supabase/embeddings.ts diff --git a/src/adapters/index.ts b/src/adapters/index.ts index 09a0608..8664f23 100644 --- a/src/adapters/index.ts +++ b/src/adapters/index.ts @@ -2,6 +2,8 @@ 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"; +import { Embeddings } from "./supabase/embeddings"; +import { VoyageAIClient } from "voyageai"; export interface Storage { userSnapshot(chatId: number, userIds: number[]): Promise; @@ -19,12 +21,10 @@ export interface Storage { } export function createAdapters(ctx: Context) { - const { - config: { shouldUseGithubStorage }, - env: { OPENAI_API_KEY }, - } = ctx; + const sessionManager = SessionManagerFactory.createSessionManager(ctx); return { - storage: SessionManagerFactory.createSessionManager(shouldUseGithubStorage, ctx).storage, - ai: new Completions(OPENAI_API_KEY), + storage: sessionManager.storage, + ai: new Completions(ctx), + embeddings: new Embeddings(sessionManager.getClient(), new VoyageAIClient({ apiKey: ctx.env.VOYAGEAI_API_KEY })), }; } diff --git a/src/adapters/supabase/embeddings.ts b/src/adapters/supabase/embeddings.ts new file mode 100644 index 0000000..639d5f2 --- /dev/null +++ b/src/adapters/supabase/embeddings.ts @@ -0,0 +1,136 @@ +import { SupabaseClient } from "@supabase/supabase-js"; +import { logger } from "../../utils/logger"; +import { VoyageAIClient } from "voyageai"; +const VECTOR_SIZE = 1024; + +export interface CommentType { + id: string; + plaintext: string; + markdown?: string; + author_id: number; + created_at: string; + modified_at: string; + embedding: number[]; +} + +export interface CommentSimilaritySearchResult { + comment_id: string; + comment_plaintext: string; + comment_issue_id: string; + similarity: number; + text_similarity: number; +} + +export interface IssueSimilaritySearchResult { + issue_id: string; + issue_plaintext: string; + similarity: number; + text_similarity: number; +} + +export interface DatabaseIssue { + id: string; + markdown?: string; + plaintext?: string; + payload?: Record; + author_id: number; + created_at: string; + modified_at: string; + embedding: number[]; +} + +export class Embeddings { + protected supabase: SupabaseClient; + protected voyage: VoyageAIClient; + + constructor(supabase: SupabaseClient | void, client: VoyageAIClient) { + if (!supabase) { + throw new Error("Supabase client is required to use Embeddings"); + } + this.supabase = supabase; + this.voyage = client; + } + + async getIssue(issueNodeId: string): Promise { + const { data, error } = await this.supabase.from("issues").select("*").eq("id", issueNodeId).returns(); + if (error) { + logger.error("Error getting issue", { error }); + return null; + } + return data; + } + + async getComment(commentNodeId: string): Promise { + const { data, error } = await this.supabase.from("issue_comments").select("*").eq("id", commentNodeId); + if (error) { + logger.error("Error getting comment", { error }); + } + return data; + } + + async findSimilarIssues(plaintext: string, threshold: number): Promise { + const embedding = await this.createEmbedding({ text: plaintext, prompt: "This is a query for the stored documents:" }); + plaintext = plaintext.replace(/'/g, "''").replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/%/g, "\\%").replace(/_/g, "\\_"); + const { data, error } = await this.supabase.rpc("find_similar_issue_ftse", { + current_id: "", + query_text: plaintext, + query_embedding: embedding, + threshold: threshold, + max_results: 10, + }); + if (error) { + logger.error("Error finding similar issues", { error }); + throw new Error("Error finding similar issues"); + } + return data; + } + + async findSimilarComments(query: string, threshold: number): Promise { + const embedding = await this.createEmbedding({ text: query, prompt: "This is a query for the stored documents:" }); + query = query.replace(/'/g, "''").replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/%/g, "\\%").replace(/_/g, "\\_"); + logger.info(`Query: ${query}`); + const { data, error } = await this.supabase.rpc("find_similar_comments", { + current_id: "", + query_text: query, + query_embedding: embedding, + threshold: threshold, + max_results: 10, + }); + if (error) { + logger.error("Error finding similar comments", { error }); + throw new Error("Error finding similar comments"); + } + return data; + } + + async createEmbedding(input: { text?: string; prompt?: string } = {}): Promise { + const { text = null, prompt = null } = input; + if (text === null) { + return new Array(VECTOR_SIZE).fill(0); + } else { + const response = await this.voyage.embed({ + input: prompt ? `${prompt} ${text}` : text, + model: "voyage-large-2-instruct", + }); + return response.data?.[0]?.embedding || []; + } + } + + async reRankResults(results: string[], query: string, topK: number = 5): Promise { + let response; + try { + response = await this.voyage.rerank({ + query, + documents: results, + model: "rerank-2", + returnDocuments: true, + topK, + }); + } catch (e: unknown) { + logger.error("Reranking failed!", { e }); + return results; + } + const rerankedResults = response.data || []; + return rerankedResults.map((result) => result.document).filter((document): document is string => document !== undefined); + } +} From f3f075e03437e2b3361de5fcd50f33ce02b00e78 Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Wed, 6 Nov 2024 00:39:42 +0000 Subject: [PATCH 07/18] chore: sessionManager.getClient --- src/bot/mtproto-api/bot/session/github-session.ts | 4 ++++ src/bot/mtproto-api/bot/session/session-manager.ts | 6 ++++-- src/bot/mtproto-api/bot/session/supabase-session.ts | 7 +++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/bot/mtproto-api/bot/session/github-session.ts b/src/bot/mtproto-api/bot/session/github-session.ts index 1f7aa52..f6d2b58 100644 --- a/src/bot/mtproto-api/bot/session/github-session.ts +++ b/src/bot/mtproto-api/bot/session/github-session.ts @@ -20,6 +20,10 @@ export class GitHubSession extends StringSession implements SessionManager { this.session = session; } + getClient() { + return; + } + async saveSession(): Promise { if (!this.session) { throw new Error("No session found. Please run the SMS Login script first."); diff --git a/src/bot/mtproto-api/bot/session/session-manager.ts b/src/bot/mtproto-api/bot/session/session-manager.ts index ca03e7c..b66d683 100644 --- a/src/bot/mtproto-api/bot/session/session-manager.ts +++ b/src/bot/mtproto-api/bot/session/session-manager.ts @@ -4,6 +4,7 @@ import { SuperbaseStorage } from "../../../../adapters/supabase/supabase"; import { GitHubSession } from "./github-session"; import { SupabaseSession } from "./supabase-session"; import { StringSession } from "telegram/sessions"; +import { SupabaseClient } from "@supabase/supabase-js"; export interface SessionManager extends StringSession { storage: GithubStorage | SuperbaseStorage; @@ -13,6 +14,7 @@ export interface SessionManager extends StringSession { loadSession(): Promise; getSession(): Promise; deleteSession(): Promise; + getClient(): SupabaseClient | void; } export class SessionManagerFactory { @@ -21,12 +23,12 @@ export class SessionManagerFactory { // eslint-disable-next-line sonarjs/public-static-readonly static storage: GithubStorage | SuperbaseStorage; - static createSessionManager(shouldUseGithubStorage: boolean, context: Context, session?: string): SessionManager { + static createSessionManager(context: Context, session?: string): SessionManager { if (this.sessionManager) { return this.sessionManager; } - if (shouldUseGithubStorage) { + if (context.config.shouldUseGithubStorage) { this.sessionManager = new GitHubSession(context, session); } else { this.sessionManager = new SupabaseSession(context, session); diff --git a/src/bot/mtproto-api/bot/session/supabase-session.ts b/src/bot/mtproto-api/bot/session/supabase-session.ts index 444a96a..7c74b08 100644 --- a/src/bot/mtproto-api/bot/session/supabase-session.ts +++ b/src/bot/mtproto-api/bot/session/supabase-session.ts @@ -23,6 +23,13 @@ export class SupabaseSession extends StringSession implements SessionManager { this.context = context; } + /** + * Returns the Supabase client. + */ + getClient() { + return this.supabase; + } + async saveSession(): Promise { await this.supabase?.from("tg-bot-sessions").insert([{ session_data: super.save() }]); } From 43fc425f648a3034f10b36132e13bf7ddb48be45 Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Wed, 6 Nov 2024 00:40:31 +0000 Subject: [PATCH 08/18] chore: new env and inputs --- src/types/env.ts | 4 +++- src/types/plugin-inputs.ts | 18 +++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/types/env.ts b/src/types/env.ts index 202dcc9..4a2d1f7 100644 --- a/src/types/env.ts +++ b/src/types/env.ts @@ -93,7 +93,9 @@ 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(), + OPENAI_API_KEY: T.Optional(T.String()), + OPENROUTER_API_KEY: T.Optional(T.String()), + VOYAGEAI_API_KEY: T.String(), }); export type Env = StaticDecode; diff --git a/src/types/plugin-inputs.ts b/src/types/plugin-inputs.ts index 9affabd..ecfac86 100644 --- a/src/types/plugin-inputs.ts +++ b/src/types/plugin-inputs.ts @@ -20,7 +20,23 @@ 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 }), + aiConfig: T.Union( + [ + T.Object({ + kind: T.Literal("OpenAi"), + model: T.String({ default: "openai/o1-mini" }), + baseUrl: T.String({ default: "https://api.openai.com/v1" }), + maxCompletionTokens: T.Number({ default: 5000 }), + }), + T.Object({ + kind: T.Literal("OpenRouter"), + model: T.String({ default: "openai/o1-mini" }), + baseUrl: T.String({ default: "https://openrouter.ai/api/v1" }), + maxCompletionTokens: T.Number({ default: 5000 }), + }), + ], + { default: { kind: "OpenAi", model: "openai/o1-mini", baseUrl: "https://api.openai.com/v1" }, maxCompletionTokens: 5000 } + ), }); export const pluginSettingsValidator = new StandardValidator(pluginSettingsSchema); From fee5ae72031fd7f73d36ad02e63c4d2d36eb0413 Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Wed, 6 Nov 2024 00:44:32 +0000 Subject: [PATCH 09/18] chore: update ai adapter --- src/adapters/openai/openai.ts | 52 +++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/src/adapters/openai/openai.ts b/src/adapters/openai/openai.ts index 5f13615..1c5fcdb 100644 --- a/src/adapters/openai/openai.ts +++ b/src/adapters/openai/openai.ts @@ -1,5 +1,7 @@ import OpenAI from "openai"; import { PluginContext } from "../../types/plugin-context-single"; +import { logger } from "../../utils/logger"; +import { Context } from "../../types"; export interface ResponseFromLlm { answer: string; @@ -13,8 +15,23 @@ export interface ResponseFromLlm { export class Completions { protected client: OpenAI; - constructor(apiKey: string) { - this.client = new OpenAI({ apiKey: apiKey }); + constructor(context: Context) { + const { + config: { + aiConfig: { baseUrl, kind }, + }, + env, + } = context; + const key = kind === "OpenAi" ? env.OPENAI_API_KEY : env.OPENROUTER_API_KEY; + + if (!key) { + throw new Error(`Plugin is configured to use ${kind} but ${kind === "OpenAi" ? "OPENAI_API_KEY" : "OPENROUTER_API_KEY"} is not set in the environment`); + } + + this.client = new OpenAI({ + baseURL: baseUrl, + apiKey: kind === "OpenAi" ? env.OPENAI_API_KEY : env.OPENROUTER_API_KEY, + }); } createSystemMessage({ @@ -60,15 +77,7 @@ export class Completions { ]; } - async createCompletion({ - directives, - constraints, - additionalContext, - embeddingsSearch, - outputStyle, - query, - model, - }: { + async createCompletion(params: { directives: string[]; constraints: string[]; additionalContext: string[]; @@ -76,13 +85,15 @@ export class Completions { outputStyle: string; query: string; model: string; - }): Promise { + }): Promise { const config = PluginContext.getInstance().config; + const ctxWindow = this.createSystemMessage(params); + logger.info("ctxWindow:\n\n", { ctxWindow }); const res: OpenAI.Chat.Completions.ChatCompletion = await this.client.chat.completions.create({ - model: model, - messages: this.createSystemMessage({ directives, constraints, query, embeddingsSearch, additionalContext, outputStyle }), + model: params.model, + messages: ctxWindow, temperature: 0.2, - max_completion_tokens: config.maxCompletionTokens, + max_completion_tokens: config.aiConfig.maxCompletionTokens, top_p: 0.5, frequency_penalty: 0, presence_penalty: 0, @@ -91,12 +102,11 @@ export class Completions { }, }); 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 }, - }; + if (answer?.content) { + return answer.content; } + + logger.error("No answer found", { res }); + return `There was an error processing your request. Please try again later.`; } } From 49df118171672a4a96d9a97f52b0a03f495822f0 Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Wed, 6 Nov 2024 00:49:00 +0000 Subject: [PATCH 10/18] feat: ask command --- .../features/commands/shared/ask-command.ts | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 src/bot/features/commands/shared/ask-command.ts diff --git a/src/bot/features/commands/shared/ask-command.ts b/src/bot/features/commands/shared/ask-command.ts new file mode 100644 index 0000000..c23da30 --- /dev/null +++ b/src/bot/features/commands/shared/ask-command.ts @@ -0,0 +1,80 @@ +import { chatAction } from "@grammyjs/auto-chat-action"; +import { Composer } from "grammy"; +import { GrammyContext } from "../../../helpers/grammy-context"; +import { logHandle } from "../../../helpers/logging"; +import { logger } from "../../../../utils/logger"; +import { PluginContext } from "../../../../types/plugin-context-single"; + +const composer = new Composer(); + +const feature = composer.chatType(["group", "private", "supergroup", "channel"]); +export interface CommentSimilaritySearchResult { + comment_id: string; + comment_plaintext: string; + comment_issue_id: string; + similarity: number; + text_similarity: number; +} + +export interface IssueSimilaritySearchResult { + issue_id: string; + issue_plaintext: string; + similarity: number; + text_similarity: number; +} + +feature.command("ubiquityos", logHandle("command-ubiquityos"), chatAction("typing"), async (ctx) => { + const { + adapters: { ai, embeddings }, + } = ctx; + const directives = [ + "Extract Relevant Information: Identify key pieces of information, even if they are incomplete, from the available corpus.", + "Apply Knowledge: Use the extracted information and relevant documentation to construct an informed response.", + "Draft Response: Compile the gathered insights into a coherent and concise response, ensuring it's clear and directly addresses the user's query.", + "Review and Refine: Check for accuracy and completeness, filling any gaps with logical assumptions where necessary.", + ]; + + const constraints = [ + "Ensure the response is crafted from the corpus provided, without introducing information outside of what's available or relevant to the query.", + "Consider edge cases where the corpus might lack explicit answers, and justify responses with logical reasoning based on the existing information.", + "Replies MUST be in Markdown V1 format.", + ]; + + const outputStyle = "Concise and coherent responses in paragraphs that directly address the user's question."; + + const similarityThreshold = 0.9; + const question = ctx.message?.text.replace("/ubiquityos", "").trim(); + + if (!question) { + return ctx.reply("Please provide a question to ask UbiquityOS."); + } + + const similarText = await Promise.all([ + embeddings.findSimilarComments(question, 1 - similarityThreshold), + embeddings.findSimilarIssues(question, 1 - similarityThreshold), + ]).then(([comments, issues]) => { + return [ + ...(comments?.map((comment: CommentSimilaritySearchResult) => comment.comment_plaintext) || []), + ...(issues?.map((issue: IssueSimilaritySearchResult) => issue.issue_plaintext) || []), + ]; + }); + logger.info("Similar Text:\n\n", { similarText }); + const rerankedText = similarText.length > 0 ? await embeddings.reRankResults(similarText, question) : []; + logger.info("Reranked Text:\n\n", { rerankedText: rerankedText }); + return ctx.reply( + await ai.createCompletion({ + directives, + constraints, + query: question, + embeddingsSearch: rerankedText, + additionalContext: [], + outputStyle, + model: PluginContext.getInstance().config.aiConfig.model, + }), + { + parse_mode: "Markdown", + } + ); +}); + +export { composer as askFeature }; From e0e4f1f26eea52f52ce3b7d82095395f9277231c Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Wed, 6 Nov 2024 00:50:06 +0000 Subject: [PATCH 11/18] chore: install new command, increase bot reply timeout --- src/bot/index.ts | 7 +++++++ src/bot/setcommands.ts | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/src/bot/index.ts b/src/bot/index.ts index 3b73bcd..d2e4c5a 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 { askFeature } from "./features/commands/shared/ask-command"; interface Dependencies { config: Context["env"]; @@ -45,6 +46,9 @@ export async function createBot(token: string, dependencies: Dependencies, optio const bot = new TelegramBot(token, { ...options.botConfig, ContextConstructor: await createContextConstructor(dependencies), + client: { + timeoutSeconds: 20, + }, }); // Error handling @@ -89,6 +93,9 @@ export async function createBot(token: string, dependencies: Dependencies, optio // Group commands bot.use(banCommand); + // shared commands + bot.use(askFeature); + // Unhandled command handler bot.use(unhandledFeature); diff --git a/src/bot/setcommands.ts b/src/bot/setcommands.ts index 1e790ec..477fe19 100644 --- a/src/bot/setcommands.ts +++ b/src/bot/setcommands.ts @@ -90,6 +90,10 @@ function getPrivateChatCommands(): BotCommand[] { command: "wallet", description: "Register your wallet address", }, + { + command: "ubiquityos", + description: "Ask UbiquityOS a question", + }, ]; } @@ -112,6 +116,10 @@ function getGroupChatCommands(): BotCommand[] { command: "ban", description: "Ban a user", }, + { + command: "ubiquityos", + description: "Ask UbiquityOS a question", + }, ]; } From 82bc1fed44f908f75cbca14c8ee753690403d380 Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Wed, 6 Nov 2024 00:53:37 +0000 Subject: [PATCH 12/18] chore: knip --- src/adapters/openai/openai.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/adapters/openai/openai.ts b/src/adapters/openai/openai.ts index 1c5fcdb..662d904 100644 --- a/src/adapters/openai/openai.ts +++ b/src/adapters/openai/openai.ts @@ -3,15 +3,6 @@ import { PluginContext } from "../../types/plugin-context-single"; import { logger } from "../../utils/logger"; import { Context } from "../../types"; -export interface ResponseFromLlm { - answer: string; - tokenUsage: { - input: number; - output: number; - total: number; - }; -} - export class Completions { protected client: OpenAI; From 4e38f0206c367017b9623635a569f3de72a7d719 Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Wed, 6 Nov 2024 01:05:41 +0000 Subject: [PATCH 13/18] chore: simThreshold from config --- src/bot/features/commands/shared/ask-command.ts | 5 +++-- src/types/plugin-inputs.ts | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/bot/features/commands/shared/ask-command.ts b/src/bot/features/commands/shared/ask-command.ts index c23da30..3399f7a 100644 --- a/src/bot/features/commands/shared/ask-command.ts +++ b/src/bot/features/commands/shared/ask-command.ts @@ -42,13 +42,14 @@ feature.command("ubiquityos", logHandle("command-ubiquityos"), chatAction("typin const outputStyle = "Concise and coherent responses in paragraphs that directly address the user's question."; - const similarityThreshold = 0.9; const question = ctx.message?.text.replace("/ubiquityos", "").trim(); if (!question) { return ctx.reply("Please provide a question to ask UbiquityOS."); } + const { similarityThreshold, model } = PluginContext.getInstance().config.aiConfig; + const similarText = await Promise.all([ embeddings.findSimilarComments(question, 1 - similarityThreshold), embeddings.findSimilarIssues(question, 1 - similarityThreshold), @@ -69,7 +70,7 @@ feature.command("ubiquityos", logHandle("command-ubiquityos"), chatAction("typin embeddingsSearch: rerankedText, additionalContext: [], outputStyle, - model: PluginContext.getInstance().config.aiConfig.model, + model, }), { parse_mode: "Markdown", diff --git a/src/types/plugin-inputs.ts b/src/types/plugin-inputs.ts index ecfac86..aa3b857 100644 --- a/src/types/plugin-inputs.ts +++ b/src/types/plugin-inputs.ts @@ -27,12 +27,14 @@ export const pluginSettingsSchema = T.Object({ model: T.String({ default: "openai/o1-mini" }), baseUrl: T.String({ default: "https://api.openai.com/v1" }), maxCompletionTokens: T.Number({ default: 5000 }), + similarityThreshold: T.Number({ default: 0.9 }), }), T.Object({ kind: T.Literal("OpenRouter"), model: T.String({ default: "openai/o1-mini" }), baseUrl: T.String({ default: "https://openrouter.ai/api/v1" }), maxCompletionTokens: T.Number({ default: 5000 }), + similarityThreshold: T.Number({ default: 0.9 }), }), ], { default: { kind: "OpenAi", model: "openai/o1-mini", baseUrl: "https://api.openai.com/v1" }, maxCompletionTokens: 5000 } From d45913167f81a4aadc3e8d66b121cf529fccbf7a Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Wed, 6 Nov 2024 01:09:16 +0000 Subject: [PATCH 14/18] chore: use apiKey var --- src/adapters/openai/openai.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/adapters/openai/openai.ts b/src/adapters/openai/openai.ts index 662d904..21c526b 100644 --- a/src/adapters/openai/openai.ts +++ b/src/adapters/openai/openai.ts @@ -13,15 +13,15 @@ export class Completions { }, env, } = context; - const key = kind === "OpenAi" ? env.OPENAI_API_KEY : env.OPENROUTER_API_KEY; + const apiKey = kind === "OpenAi" ? env.OPENAI_API_KEY : env.OPENROUTER_API_KEY; - if (!key) { + if (!apiKey) { throw new Error(`Plugin is configured to use ${kind} but ${kind === "OpenAi" ? "OPENAI_API_KEY" : "OPENROUTER_API_KEY"} is not set in the environment`); } this.client = new OpenAI({ baseURL: baseUrl, - apiKey: kind === "OpenAi" ? env.OPENAI_API_KEY : env.OPENROUTER_API_KEY, + apiKey, }); } From 892a2a2ca5225ad4dc0c0c55646164239be83c7b Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Wed, 6 Nov 2024 01:18:21 +0000 Subject: [PATCH 15/18] chore: cleanup types --- src/adapters/openai/openai.ts | 5 ++- src/adapters/supabase/embeddings.ts | 45 +++---------------- .../features/commands/shared/ask-command.ts | 21 +++------ src/types/ai.ts | 25 +++++++++++ 4 files changed, 39 insertions(+), 57 deletions(-) create mode 100644 src/types/ai.ts diff --git a/src/adapters/openai/openai.ts b/src/adapters/openai/openai.ts index 21c526b..0ab0598 100644 --- a/src/adapters/openai/openai.ts +++ b/src/adapters/openai/openai.ts @@ -8,10 +8,10 @@ export class Completions { constructor(context: Context) { const { + env, config: { aiConfig: { baseUrl, kind }, }, - env, } = context; const apiKey = kind === "OpenAi" ? env.OPENAI_API_KEY : env.OPENROUTER_API_KEY; @@ -79,7 +79,9 @@ export class Completions { }): Promise { const config = PluginContext.getInstance().config; const ctxWindow = this.createSystemMessage(params); + logger.info("ctxWindow:\n\n", { ctxWindow }); + const res: OpenAI.Chat.Completions.ChatCompletion = await this.client.chat.completions.create({ model: params.model, messages: ctxWindow, @@ -92,6 +94,7 @@ export class Completions { type: "text", }, }); + const answer = res.choices[0].message; if (answer?.content) { return answer.content; diff --git a/src/adapters/supabase/embeddings.ts b/src/adapters/supabase/embeddings.ts index 639d5f2..210de0c 100644 --- a/src/adapters/supabase/embeddings.ts +++ b/src/adapters/supabase/embeddings.ts @@ -1,43 +1,7 @@ import { SupabaseClient } from "@supabase/supabase-js"; import { logger } from "../../utils/logger"; import { VoyageAIClient } from "voyageai"; -const VECTOR_SIZE = 1024; - -export interface CommentType { - id: string; - plaintext: string; - markdown?: string; - author_id: number; - created_at: string; - modified_at: string; - embedding: number[]; -} - -export interface CommentSimilaritySearchResult { - comment_id: string; - comment_plaintext: string; - comment_issue_id: string; - similarity: number; - text_similarity: number; -} - -export interface IssueSimilaritySearchResult { - issue_id: string; - issue_plaintext: string; - similarity: number; - text_similarity: number; -} - -export interface DatabaseIssue { - id: string; - markdown?: string; - plaintext?: string; - payload?: Record; - author_id: number; - created_at: string; - modified_at: string; - embedding: number[]; -} +import { CommentSimilaritySearchResult, DatabaseItem, IssueSimilaritySearchResult } from "../../types/ai"; export class Embeddings { protected supabase: SupabaseClient; @@ -51,8 +15,8 @@ export class Embeddings { this.voyage = client; } - async getIssue(issueNodeId: string): Promise { - const { data, error } = await this.supabase.from("issues").select("*").eq("id", issueNodeId).returns(); + async getIssue(issueNodeId: string): Promise { + const { data, error } = await this.supabase.from("issues").select("*").eq("id", issueNodeId).returns(); if (error) { logger.error("Error getting issue", { error }); return null; @@ -60,7 +24,7 @@ export class Embeddings { return data; } - async getComment(commentNodeId: string): Promise { + async getComment(commentNodeId: string): Promise { const { data, error } = await this.supabase.from("issue_comments").select("*").eq("id", commentNodeId); if (error) { logger.error("Error getting comment", { error }); @@ -104,6 +68,7 @@ export class Embeddings { } async createEmbedding(input: { text?: string; prompt?: string } = {}): Promise { + const VECTOR_SIZE = 1024; const { text = null, prompt = null } = input; if (text === null) { return new Array(VECTOR_SIZE).fill(0); diff --git a/src/bot/features/commands/shared/ask-command.ts b/src/bot/features/commands/shared/ask-command.ts index 3399f7a..38a8c5f 100644 --- a/src/bot/features/commands/shared/ask-command.ts +++ b/src/bot/features/commands/shared/ask-command.ts @@ -4,29 +4,16 @@ import { GrammyContext } from "../../../helpers/grammy-context"; import { logHandle } from "../../../helpers/logging"; import { logger } from "../../../../utils/logger"; import { PluginContext } from "../../../../types/plugin-context-single"; +import { CommentSimilaritySearchResult, IssueSimilaritySearchResult } from "../../../../types/ai"; const composer = new Composer(); - const feature = composer.chatType(["group", "private", "supergroup", "channel"]); -export interface CommentSimilaritySearchResult { - comment_id: string; - comment_plaintext: string; - comment_issue_id: string; - similarity: number; - text_similarity: number; -} - -export interface IssueSimilaritySearchResult { - issue_id: string; - issue_plaintext: string; - similarity: number; - text_similarity: number; -} feature.command("ubiquityos", logHandle("command-ubiquityos"), chatAction("typing"), async (ctx) => { const { adapters: { ai, embeddings }, } = ctx; + const directives = [ "Extract Relevant Information: Identify key pieces of information, even if they are incomplete, from the available corpus.", "Apply Knowledge: Use the extracted information and relevant documentation to construct an informed response.", @@ -59,9 +46,11 @@ feature.command("ubiquityos", logHandle("command-ubiquityos"), chatAction("typin ...(issues?.map((issue: IssueSimilaritySearchResult) => issue.issue_plaintext) || []), ]; }); + logger.info("Similar Text:\n\n", { similarText }); const rerankedText = similarText.length > 0 ? await embeddings.reRankResults(similarText, question) : []; - logger.info("Reranked Text:\n\n", { rerankedText: rerankedText }); + logger.info("Reranked Text:\n\n", { rerankedText }); + return ctx.reply( await ai.createCompletion({ directives, diff --git a/src/types/ai.ts b/src/types/ai.ts new file mode 100644 index 0000000..fbecbcd --- /dev/null +++ b/src/types/ai.ts @@ -0,0 +1,25 @@ +export type DatabaseItem = { + id: string; + markdown?: string; + plaintext: string; + author_id: number; + payload?: Record; + created_at: string; + modified_at: string; + embedding: number[]; +}; + +export type CommentSimilaritySearchResult = { + comment_id: string; + comment_plaintext: string; + comment_issue_id: string; + similarity: number; + text_similarity: number; +}; + +export type IssueSimilaritySearchResult = { + issue_id: string; + issue_plaintext: string; + similarity: number; + text_similarity: number; +}; From d5f382710852f6e5f7e5990c51c89cdad73a8aaa Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Wed, 27 Nov 2024 21:22:40 +0000 Subject: [PATCH 16/18] chore: small updates after merge --- .cspell.json | 26 ++++--------------- src/adapters/openai/openai.ts | 6 +---- .../features/commands/shared/ask-command.ts | 1 - .../features/commands/shared/task-creation.ts | 8 +++--- src/bot/index.ts | 2 +- src/types/plugin-inputs.ts | 20 +++++++------- yarn.lock | 7 ----- 7 files changed, 19 insertions(+), 51 deletions(-) diff --git a/.cspell.json b/.cspell.json index 554641b..92d4db5 100644 --- a/.cspell.json +++ b/.cspell.json @@ -1,13 +1,7 @@ { "$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json", "version": "0.2", - "ignorePaths": [ - "**/*.json", - "**/*.css", - "node_modules", - "**/*.log", - "src/adapters/github/storage-layer.ts" - ], + "ignorePaths": ["**/*.json", "**/*.css", "node_modules", "**/*.log", "src/adapters/github/storage-layer.ts"], "useGitignore": true, "language": "en", "words": [ @@ -61,17 +55,7 @@ "newtask", "supergroup" ], - "dictionaries": [ - "typescript", - "node", - "software-terms" - ], - "import": [ - "@cspell/dict-typescript/cspell-ext.json", - "@cspell/dict-node/cspell-ext.json", - "@cspell/dict-software-terms" - ], - "ignoreRegExpList": [ - "[0-9a-fA-F]{6}" - ] -} \ No newline at end of file + "dictionaries": ["typescript", "node", "software-terms"], + "import": ["@cspell/dict-typescript/cspell-ext.json", "@cspell/dict-node/cspell-ext.json", "@cspell/dict-software-terms"], + "ignoreRegExpList": ["[0-9a-fA-F]{6}"] +} diff --git a/src/adapters/openai/openai.ts b/src/adapters/openai/openai.ts index d8891f6..b727930 100644 --- a/src/adapters/openai/openai.ts +++ b/src/adapters/openai/openai.ts @@ -51,7 +51,7 @@ export class Completions { }): OpenAI.Chat.Completions.ChatCompletionMessageParam[] { return [ { - role: "system", + role: "user", content: `You are UbiquityOS, a Telegram-integrated GitHub-first assistant for UbiquityDAO. # Directives @@ -94,11 +94,7 @@ export class Completions { const res: OpenAI.Chat.Completions.ChatCompletion = await this.client.chat.completions.create({ model: params.model, messages: ctxWindow, - temperature: 0.2, max_completion_tokens: config.aiConfig.maxCompletionTokens, - top_p: 0.5, - frequency_penalty: 0, - presence_penalty: 0, response_format: { type: "text", }, diff --git a/src/bot/features/commands/shared/ask-command.ts b/src/bot/features/commands/shared/ask-command.ts index 38a8c5f..8e98ab2 100644 --- a/src/bot/features/commands/shared/ask-command.ts +++ b/src/bot/features/commands/shared/ask-command.ts @@ -36,7 +36,6 @@ feature.command("ubiquityos", logHandle("command-ubiquityos"), chatAction("typin } const { similarityThreshold, model } = PluginContext.getInstance().config.aiConfig; - const similarText = await Promise.all([ embeddings.findSimilarComments(question, 1 - similarityThreshold), embeddings.findSimilarIssues(question, 1 - similarityThreshold), diff --git a/src/bot/features/commands/shared/task-creation.ts b/src/bot/features/commands/shared/task-creation.ts index b539065..7c1831d 100644 --- a/src/bot/features/commands/shared/task-creation.ts +++ b/src/bot/features/commands/shared/task-creation.ts @@ -134,7 +134,7 @@ async function createTask(taskToCreate: string, ctx: GrammyContext, { owner, rep const outputStyle = `{ "title": "Task Title", "body": "Task Body" }`; - const llmResponse = await ctx.adapters.ai.createCompletion({ + const taskFromLlm = await ctx.adapters.ai.createCompletion({ embeddingsSearch: [], directives, constraints, @@ -144,12 +144,10 @@ async function createTask(taskToCreate: string, ctx: GrammyContext, { owner, rep query: taskToCreate, }); - if (!llmResponse) { + if (!taskFromLlm) { return await ctx.reply("Failed to create task"); } - const taskFromLlm = llmResponse.answer; - let taskDetails; try { @@ -185,7 +183,7 @@ async function createTask(taskToCreate: string, ctx: GrammyContext, { owner, rep return await ctx.reply("Failed to create task"); } - return await ctx.reply(`${fullSpec}\n\n [View on GitHub](${task.data.html_url})`); + return await ctx.reply(`${fullSpec}\n\n ${owner}/${repo}`); } export { composer as newTaskFeature }; diff --git a/src/bot/index.ts b/src/bot/index.ts index ed04fae..e8c464e 100644 --- a/src/bot/index.ts +++ b/src/bot/index.ts @@ -48,7 +48,7 @@ export async function createBot(token: string, dependencies: Dependencies, optio ...options.botConfig, ContextConstructor: await createContextConstructor(dependencies), client: { - timeoutSeconds: 20, + timeoutSeconds: 500, }, }); diff --git a/src/types/plugin-inputs.ts b/src/types/plugin-inputs.ts index 742a754..71d400b 100644 --- a/src/types/plugin-inputs.ts +++ b/src/types/plugin-inputs.ts @@ -24,21 +24,19 @@ export const pluginSettingsSchema = T.Object({ aiConfig: T.Union( [ T.Object({ - kind: T.Literal("OpenAi"), - model: T.String({ default: "openai/o1-mini" }), - baseUrl: T.String({ default: "https://api.openai.com/v1" }), - maxCompletionTokens: T.Number({ default: 5000 }), - similarityThreshold: T.Number({ default: 0.9 }), + kind: T.Literal("OpenAi", { description: "The API provider you wish to use.", examples: ["OpenAi", "OpenRouter"] }), + model: T.String({ default: "o1-mini", description: "The model to use.", examples: ["o1-mini", "gpt-4o"] }), + baseUrl: T.String({ default: "https://api.openai.com/v1", description: "The base URL of the API.", examples: ["https://api.openai.com/v1", "https://api.openai.com/v2"] }), + similarityThreshold: T.Number({ default: 0.9, description: "The similarity threshold for when fetching embeddings-based context." }), }), T.Object({ - kind: T.Literal("OpenRouter"), - model: T.String({ default: "openai/o1-mini" }), - baseUrl: T.String({ default: "https://openrouter.ai/api/v1" }), - maxCompletionTokens: T.Number({ default: 5000 }), - similarityThreshold: T.Number({ default: 0.9 }), + kind: T.Literal("OpenRouter", { description: "The API provider you wish to use.", examples: ["OpenAi", "OpenRouter"] }), + model: T.String({ default: "openai/o1-mini", description: "The model to use.", examples: ["openai/o1-mini", "openai/gpt-4o"] }), + baseUrl: T.String({ default: "https://openrouter.ai/api/v1", description: "The base URL of the API.", examples: ["https://openrouter.ai/api/v1", "https://openrouter.ai/api/v2"] }), + similarityThreshold: T.Number({ default: 0.9, description: "The similarity threshold for when fetching embeddings-based context." }), }), ], - { default: { kind: "OpenAi", model: "openai/o1-mini", baseUrl: "https://api.openai.com/v1" }, maxCompletionTokens: 5000 } + { default: { kind: "OpenAi", model: "o1-mini", baseUrl: "https://api.openai.com/v1" } } ), }); diff --git a/yarn.lock b/yarn.lock index a6ebdc9..845da7c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5401,14 +5401,11 @@ formdata-node@^4.3.2: node-domexception "1.0.0" web-streams-polyfill "4.0.0-beta.3" -<<<<<<< HEAD formdata-node@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/formdata-node/-/formdata-node-6.0.3.tgz#48f8e2206ae2befded82af621ef015f08168dc6d" integrity sha512-8e1++BCiTzUno9v5IZ2J6bv4RU+3UKDmqWUQD0MIMVCd9AdhWkO1gw57oo1mNEX1dMq2EGI+FbWz4B92pscSQg== -======= ->>>>>>> upstream/development fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -7165,11 +7162,7 @@ node-domexception@1.0.0: resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== -<<<<<<< HEAD node-fetch@2.7.0, node-fetch@^2.6.7, node-fetch@^2.7.0: -======= -node-fetch@^2.6.7, node-fetch@^2.7.0: ->>>>>>> upstream/development 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== From 35b07d1e4d8b96598b4e680edf88b2a90eddc15f Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Wed, 27 Nov 2024 21:50:06 +0000 Subject: [PATCH 17/18] chore: update webhook timeout, param desc and examples --- package.json | 3 ++- src/adapters/openai/openai.ts | 3 --- src/bot/features/commands/shared/ask-command.ts | 2 +- src/server/index.ts | 1 + src/types/plugin-inputs.ts | 12 ++++++++++-- tests/main.test.ts | 6 +++--- yarn.lock | 16 ++++++++++++++++ 7 files changed, 33 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index cbce46e..ce082a0 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "knip": "knip --config .github/knip.ts", "knip-ci": "knip --no-exit-code --reporter json --config .github/knip.ts", "prepare": "husky install", - "test": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest --setupFiles dotenv/config --coverage", + "test": "cross-env NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest --setupFiles dotenv/config --coverage", "worker": "wrangler dev --env dev --port 3000", "deploy": "wrangler deploy --env dev", "sms-auth": "npx tsx src/bot/mtproto-api/bot/scripts/sms-auth/sms-auth.ts", @@ -66,6 +66,7 @@ "@mswjs/data": "0.16.1", "@types/jest": "^29.5.12", "@types/node": "20.14.5", + "cross-env": "^7.0.3", "cspell": "8.14.2", "eslint": "9.9.1", "eslint-config-prettier": "9.1.0", diff --git a/src/adapters/openai/openai.ts b/src/adapters/openai/openai.ts index b727930..98ea1b6 100644 --- a/src/adapters/openai/openai.ts +++ b/src/adapters/openai/openai.ts @@ -1,5 +1,4 @@ import OpenAI from "openai"; -import { PluginContext } from "../../types/plugin-context-single"; import { logger } from "../../utils/logger"; import { Context } from "../../types"; @@ -86,7 +85,6 @@ export class Completions { query: string; model: string; }): Promise { - const config = PluginContext.getInstance().config; const ctxWindow = this.createSystemMessage(params); logger.info("ctxWindow:\n\n", { ctxWindow }); @@ -94,7 +92,6 @@ export class Completions { const res: OpenAI.Chat.Completions.ChatCompletion = await this.client.chat.completions.create({ model: params.model, messages: ctxWindow, - max_completion_tokens: config.aiConfig.maxCompletionTokens, response_format: { type: "text", }, diff --git a/src/bot/features/commands/shared/ask-command.ts b/src/bot/features/commands/shared/ask-command.ts index 8e98ab2..4cc9d2d 100644 --- a/src/bot/features/commands/shared/ask-command.ts +++ b/src/bot/features/commands/shared/ask-command.ts @@ -24,7 +24,7 @@ feature.command("ubiquityos", logHandle("command-ubiquityos"), chatAction("typin const constraints = [ "Ensure the response is crafted from the corpus provided, without introducing information outside of what's available or relevant to the query.", "Consider edge cases where the corpus might lack explicit answers, and justify responses with logical reasoning based on the existing information.", - "Replies MUST be in Markdown V1 format.", + "Replies MUST be in Markdown V1 format but do not wrap in code blocks.", ]; const outputStyle = "Concise and coherent responses in paragraphs that directly address the user's question."; diff --git a/src/server/index.ts b/src/server/index.ts index a92ff30..07a1e55 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -84,6 +84,7 @@ export function createServer(dependencies: Dependencies) { }, webhookCallback(bot, "hono", { secretToken: TELEGRAM_BOT_WEBHOOK_SECRET, + timeoutMilliseconds: 60_000, }) ); diff --git a/src/types/plugin-inputs.ts b/src/types/plugin-inputs.ts index 71d400b..0ff13c1 100644 --- a/src/types/plugin-inputs.ts +++ b/src/types/plugin-inputs.ts @@ -26,13 +26,21 @@ export const pluginSettingsSchema = T.Object({ T.Object({ kind: T.Literal("OpenAi", { description: "The API provider you wish to use.", examples: ["OpenAi", "OpenRouter"] }), model: T.String({ default: "o1-mini", description: "The model to use.", examples: ["o1-mini", "gpt-4o"] }), - baseUrl: T.String({ default: "https://api.openai.com/v1", description: "The base URL of the API.", examples: ["https://api.openai.com/v1", "https://api.openai.com/v2"] }), + baseUrl: T.String({ + default: "https://api.openai.com/v1", + description: "The base URL of the API.", + examples: ["https://api.openai.com/v1", "https://api.openai.com/v2"], + }), similarityThreshold: T.Number({ default: 0.9, description: "The similarity threshold for when fetching embeddings-based context." }), }), T.Object({ kind: T.Literal("OpenRouter", { description: "The API provider you wish to use.", examples: ["OpenAi", "OpenRouter"] }), model: T.String({ default: "openai/o1-mini", description: "The model to use.", examples: ["openai/o1-mini", "openai/gpt-4o"] }), - baseUrl: T.String({ default: "https://openrouter.ai/api/v1", description: "The base URL of the API.", examples: ["https://openrouter.ai/api/v1", "https://openrouter.ai/api/v2"] }), + baseUrl: T.String({ + default: "https://openrouter.ai/api/v1", + description: "The base URL of the API.", + examples: ["https://openrouter.ai/api/v1", "https://openrouter.ai/api/v2"], + }), similarityThreshold: T.Number({ default: 0.9, description: "The similarity threshold for when fetching embeddings-based context." }), }), ], diff --git a/tests/main.test.ts b/tests/main.test.ts index b3f51eb..a2e944b 100644 --- a/tests/main.test.ts +++ b/tests/main.test.ts @@ -3,9 +3,10 @@ import { db } from "./__mocks__/db"; import { server } from "./__mocks__/node"; import { expect, describe, beforeAll, beforeEach, afterAll, afterEach, it, jest } from "@jest/globals"; import { setupTests } from "./__mocks__/helpers"; -import manifest from "../manifest.json"; import dotenv from "dotenv"; import { Env } from "../src/types"; +import manifest from "../manifest.json"; +import worker from "../src/worker"; dotenv.config(); @@ -25,12 +26,11 @@ describe("Plugin tests", () => { }); it("Should serve the manifest file", async () => { - const { default: worker } = await import("../src/worker"); const response = await worker.fetch(new Request("http://localhost/manifest.json"), {} as Env); const body = await response.json(); expect(response.status).toBe(200); expect(body).toEqual(manifest); - }); + }, 10000); }); /** diff --git a/yarn.lock b/yarn.lock index 845da7c..274e6b2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4150,6 +4150,13 @@ create-jest@^29.7.0: jest-util "^29.7.0" prompts "^2.0.1" +cross-env@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf" + integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw== + dependencies: + cross-spawn "^7.0.1" + cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -4161,6 +4168,15 @@ cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" +cross-spawn@^7.0.1: + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" From ec20f06e29031e5c1f7c7a3424f2eeec9f2ff6b8 Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Wed, 27 Nov 2024 21:52:40 +0000 Subject: [PATCH 18/18] chore: replace response for dev on /newtask --- src/bot/features/commands/shared/task-creation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bot/features/commands/shared/task-creation.ts b/src/bot/features/commands/shared/task-creation.ts index 7c1831d..cf46a5b 100644 --- a/src/bot/features/commands/shared/task-creation.ts +++ b/src/bot/features/commands/shared/task-creation.ts @@ -183,7 +183,7 @@ async function createTask(taskToCreate: string, ctx: GrammyContext, { owner, rep return await ctx.reply("Failed to create task"); } - return await ctx.reply(`${fullSpec}\n\n ${owner}/${repo}`); + return await ctx.reply(`${fullSpec}\n\n [View on GitHub](${task.data.html_url})`); } export { composer as newTaskFeature };