diff --git a/.cspell.json b/.cspell.json index 014b2cf..92d4db5 100644 --- a/.cspell.json +++ b/.cspell.json @@ -42,6 +42,16 @@ "SUPABASE", "CODEOWNER", "nosniff", + "voyageai", + "OPENROUTER", + "reranked", + "rerank", + "Reranked", + "ftse", + "Reranking", + "VOYAGEAI", + "supergroup", + "ubiquityos", "newtask", "supergroup" ], diff --git a/package.json b/package.json index e94c13e..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", @@ -51,7 +51,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", @@ -65,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/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/openai/openai.ts b/src/adapters/openai/openai.ts index 941d0ca..98ea1b6 100644 --- a/src/adapters/openai/openai.ts +++ b/src/adapters/openai/openai.ts @@ -1,4 +1,6 @@ import OpenAI from "openai"; +import { logger } from "../../utils/logger"; +import { Context } from "../../types"; export interface ResponseFromLlm { answer: string; @@ -12,8 +14,23 @@ export interface ResponseFromLlm { export class Completions { protected client: OpenAI; - constructor(apiKey: string) { - this.client = new OpenAI({ apiKey: apiKey }); + constructor(context: Context) { + const { + env, + config: { + aiConfig: { baseUrl, kind }, + }, + } = context; + const apiKey = kind === "OpenAi" ? env.OPENAI_API_KEY : env.OPENROUTER_API_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, + }); } createSystemMessage({ @@ -33,7 +50,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 @@ -59,15 +76,7 @@ export class Completions { ]; } - async createCompletion({ - directives, - constraints, - additionalContext, - embeddingsSearch, - outputStyle, - query, - model, - }: { + async createCompletion(params: { directives: string[]; constraints: string[]; additionalContext: string[]; @@ -75,25 +84,25 @@ export class Completions { outputStyle: string; query: string; model: string; - }): Promise { + }): Promise { + 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 }), - temperature: 0.2, - top_p: 0.5, - frequency_penalty: 0, - presence_penalty: 0, + model: params.model, + messages: ctxWindow, 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 }, - }; + if (answer?.content) { + return answer.content; } + + logger.error("No answer found", { res }); + return `There was an error processing your request. Please try again later.`; } } diff --git a/src/adapters/supabase/embeddings.ts b/src/adapters/supabase/embeddings.ts new file mode 100644 index 0000000..210de0c --- /dev/null +++ b/src/adapters/supabase/embeddings.ts @@ -0,0 +1,101 @@ +import { SupabaseClient } from "@supabase/supabase-js"; +import { logger } from "../../utils/logger"; +import { VoyageAIClient } from "voyageai"; +import { CommentSimilaritySearchResult, DatabaseItem, IssueSimilaritySearchResult } from "../../types/ai"; + +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 VECTOR_SIZE = 1024; + 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); + } +} 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..4cc9d2d --- /dev/null +++ b/src/bot/features/commands/shared/ask-command.ts @@ -0,0 +1,69 @@ +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"; +import { CommentSimilaritySearchResult, IssueSimilaritySearchResult } from "../../../../types/ai"; + +const composer = new Composer(); +const feature = composer.chatType(["group", "private", "supergroup", "channel"]); + +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 but do not wrap in code blocks.", + ]; + + const outputStyle = "Concise and coherent responses in paragraphs that directly address the user's question."; + + 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), + ]).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 }); + + return ctx.reply( + await ai.createCompletion({ + directives, + constraints, + query: question, + embeddingsSearch: rerankedText, + additionalContext: [], + outputStyle, + model, + }), + { + parse_mode: "Markdown", + } + ); +}); + +export { composer as askFeature }; diff --git a/src/bot/features/commands/shared/task-creation.ts b/src/bot/features/commands/shared/task-creation.ts index b539065..cf46a5b 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 { diff --git a/src/bot/index.ts b/src/bot/index.ts index 3855c0c..e8c464e 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"; import { newTaskFeature } from "./features/commands/shared/task-creation"; interface Dependencies { @@ -46,6 +47,9 @@ export async function createBot(token: string, dependencies: Dependencies, optio const bot = new TelegramBot(token, { ...options.botConfig, ContextConstructor: await createContextConstructor(dependencies), + client: { + timeoutSeconds: 500, + }, }); // Error handling @@ -81,7 +85,6 @@ 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); @@ -91,6 +94,10 @@ export async function createBot(token: string, dependencies: Dependencies, optio // Group commands bot.use(banCommand); + // shared commands + bot.use(askFeature); + bot.use(newTaskFeature); + // Unhandled command handler bot.use(unhandledFeature); 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() }]); } diff --git a/src/bot/setcommands.ts b/src/bot/setcommands.ts index 6b854fa..9364a13 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", + }, ]; } @@ -116,6 +120,10 @@ function getGroupChatCommands(): BotCommand[] { command: "ban", description: "Ban a user", }, + { + command: "ubiquityos", + description: "Ask UbiquityOS a question", + }, { command: "newtask", description: "Create a new task", diff --git a/src/handlers/telegram-webhook.ts b/src/handlers/telegram-webhook.ts index 35083e7..888ecdc 100644 --- a/src/handlers/telegram-webhook.ts +++ b/src/handlers/telegram-webhook.ts @@ -25,7 +25,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 = { 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/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; +}; 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-context-single.ts b/src/types/plugin-context-single.ts index f4d7dc7..24ac074 100644 --- a/src/types/plugin-context-single.ts +++ b/src/types/plugin-context-single.ts @@ -134,7 +134,6 @@ export class PluginContext { eventName: this.inputs.eventName, payload: this.inputs.eventPayload, config: this.config, - // if we have a token coming from GitHub we'll use it instead of the storage app. octokit: !this.inputs.authToken ? octokit : this.getGitHubEventOctokit(), env: this.env, logger, diff --git a/src/types/plugin-inputs.ts b/src/types/plugin-inputs.ts index 37269f5..0ff13c1 100644 --- a/src/types/plugin-inputs.ts +++ b/src/types/plugin-inputs.ts @@ -21,6 +21,31 @@ export const pluginSettingsSchema = T.Object({ shouldUseGithubStorage: T.Boolean({ default: false, description: "Activates the GitHub storage module." }), storageOwner: T.String({ default: "ubiquity-os-marketplace", description: "Determines where the storage location of this plugin should be." }), fuzzySearchThreshold: T.Number({ default: 0.2, description: "The threshold for fuzzy search when invoking the `/newtask` command (0 is a perfect match)." }), + aiConfig: T.Union( + [ + 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"], + }), + 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"], + }), + similarityThreshold: T.Number({ default: 0.9, description: "The similarity threshold for when fetching embeddings-based context." }), + }), + ], + { default: { kind: "OpenAi", model: "o1-mini", baseUrl: "https://api.openai.com/v1" } } + ), }); export const pluginSettingsValidator = new StandardValidator(pluginSettingsSchema); diff --git a/src/types/telegram-bot-single.ts b/src/types/telegram-bot-single.ts index ca53856..8d8f459 100644 --- a/src/types/telegram-bot-single.ts +++ b/src/types/telegram-bot-single.ts @@ -38,39 +38,30 @@ export class TelegramBotSingleton { if (!TelegramBotSingleton._instance) { TelegramBotSingleton._instance = new TelegramBotSingleton(); try { - logger.info("Creating bot"); TelegramBotSingleton._bot = await createBot(TELEGRAM_BOT_TOKEN, { config: env, logger, octokit, }); - - logger.info("Bot created"); } catch (er) { logger.error("Error initializing TelegramBotSingleton", { er }); } try { - logger.info("Setting webhook"); await TelegramBotSingleton._bot.api.setWebhook(TELEGRAM_BOT_WEBHOOK, { allowed_updates: ALLOWED_UPDATES, secret_token: TELEGRAM_BOT_WEBHOOK_SECRET, }); - - logger.info("Webhook set"); } catch (er) { logger.error("Error setting webhook in TelegramBotSingleton", { er }); } try { - logger.info("Creating server"); TelegramBotSingleton._server = createServer({ bot: TelegramBotSingleton._bot, env, logger, }); - - logger.info("Server created"); } catch (er) { logger.error("Error initializing server in TelegramBotSingleton", { er }); } 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 bc3d9b1..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" @@ -5157,6 +5173,11 @@ eventemitter3@^5.0.1: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== +events@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + eventsource@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-2.0.2.tgz#76dfcc02930fb2ff339520b6d290da573a9e8508" @@ -5396,6 +5417,11 @@ formdata-node@^4.3.2: node-domexception "1.0.0" web-streams-polyfill "4.0.0-beta.3" +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== + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -6584,6 +6610,11 @@ jiti@^1.19.1, jiti@^1.21.6: resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.6.tgz#6c7f7398dd4b3142767f9a168af2f317a428d268" integrity sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w== +js-base64@3.7.2: + version "3.7.2" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-3.7.2.tgz#816d11d81a8aff241603d19ce5761e13e41d7745" + integrity sha512-NnRs6dsyqUXejqk/yv2aiXlAvOs56sLkX6nUdeaNezI5LFFLlsZjOThmwnrcwh5ZZRwZlCMnVAY3CvhIhoVEKQ== + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -7147,7 +7178,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== -node-fetch@^2.6.7, node-fetch@^2.7.0: +node-fetch@2.7.0, 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== @@ -7647,6 +7678,11 @@ printable-characters@^1.0.42: resolved "https://registry.yarnpkg.com/printable-characters/-/printable-characters-1.0.42.tgz#3f18e977a9bd8eb37fcc4ff5659d7be90868b3d8" integrity sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ== +process@^0.11.10: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== + prompts@^2.0.1: version "2.4.2" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" @@ -7679,6 +7715,13 @@ pure-rand@^6.0.0: resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2" integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== +qs@6.11.2: + version "6.11.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9" + integrity sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA== + dependencies: + side-channel "^1.0.4" + querystringify@^2.1.1: version "2.2.0" resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" @@ -7717,6 +7760,17 @@ readable-stream@^3.4.0: string_decoder "^1.1.1" util-deprecate "^1.0.1" +readable-stream@^4.5.2: + version "4.5.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.5.2.tgz#9e7fc4c45099baeed934bff6eb97ba6cf2729e09" + integrity sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g== + dependencies: + abort-controller "^3.0.0" + buffer "^6.0.3" + events "^3.3.0" + process "^0.11.10" + string_decoder "^1.3.0" + readdirp@~3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" @@ -8384,7 +8438,7 @@ string.prototype.trimstart@^1.0.8: define-properties "^1.2.1" es-object-atoms "^1.0.0" -string_decoder@^1.1.1: +string_decoder@^1.1.1, string_decoder@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== @@ -8832,6 +8886,11 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +url-join@4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7" + integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA== + url-parse@^1.5.3: version "1.5.10" resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" @@ -8879,6 +8938,19 @@ validator@^13.11.0: resolved "https://registry.yarnpkg.com/validator/-/validator-13.12.0.tgz#7d78e76ba85504da3fee4fd1922b385914d4b35f" integrity sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg== +voyageai@^0.0.1-5: + version "0.0.1-5" + resolved "https://registry.yarnpkg.com/voyageai/-/voyageai-0.0.1-5.tgz#e0457d991784900c16e4cdf095654f195d62fdf2" + integrity sha512-IuXSXM3l9J3NIq+MLHXacG/yhswpEgWIu9eBqoFqMRnFiDx00dLL62OWg6WqVSipddZLwFeWH1Kaj56x5eqhOQ== + dependencies: + form-data "^4.0.0" + formdata-node "^6.0.3" + js-base64 "3.7.2" + node-fetch "2.7.0" + qs "6.11.2" + readable-stream "^4.5.2" + url-join "4.0.1" + vscode-languageserver-textdocument@^1.0.12: version "1.0.12" resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz#457ee04271ab38998a093c68c2342f53f6e4a631"