diff --git a/.cspell.json b/.cspell.json index 83a383f..c78b966 100644 --- a/.cspell.json +++ b/.cspell.json @@ -22,7 +22,11 @@ "knip", "mischeck", "commentbody", - "issuebody" + "issuebody", + "voyageai", + "vectordump", + "payloadobject", + "markdownit" ], "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/.dev.vars.example b/.dev.vars.example index 6ceaeff..2fc8cf8 100644 --- a/.dev.vars.example +++ b/.dev.vars.example @@ -1,3 +1,3 @@ SUPABASE_URL="" SUPABASE_KEY="" -OPENAI_API_KEY="" \ No newline at end of file +VOYAGEAI_API_KEY="" \ No newline at end of file diff --git a/.env.example b/.env.example index 0272714..6e8b18f 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,3 @@ SUPABASE_URL= SUPABASE_KEY= -OPENAI_API_KEY= \ No newline at end of file +VOYAGEAI_API_KEY= \ No newline at end of file diff --git a/.github/workflows/compute.yml b/.github/workflows/compute.yml index f83e80c..8cb2597 100644 --- a/.github/workflows/compute.yml +++ b/.github/workflows/compute.yml @@ -24,7 +24,7 @@ jobs: env: SUPABASE_URL: ${{ secrets.SUPABASE_URL }} SUPABASE_KEY: ${{ secrets.SUPABASE_KEY }} - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + VOYAGEAI_API_KEY: ${{secrets.VOYAGEAI_API_KEY}} steps: - uses: actions/checkout@v4 @@ -43,4 +43,4 @@ jobs: env: SUPABASE_URL: ${{ secrets.SUPABASE_URL }} SUPABASE_KEY: ${{ secrets.SUPABASE_KEY }} - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + VOYAGEAI_API_KEY: ${{secrets.VOYAGEAI_API_KEY}} diff --git a/README.md b/README.md index 0ee0bbe..cac1f27 100644 --- a/README.md +++ b/README.md @@ -7,15 +7,16 @@ This is a plugin for [Ubiquibot](https://github.com/ubiquity/ubiquibot-kernel). To set up the `.dev.vars` file, you will need to provide the following variables: - `SUPABASE_URL`: The URL for your Supabase instance. - `SUPABASE_KEY`: The key for your Supabase instance. -- `OPENAI_API_KEY`: The API key for OpenAI. +- `VOYAGEAI_API_KEY`: The API key for Voyage. ## Usage -- Add the following to your `.ubiquibot.config.yml` file with the appropriate URL: +- Add the following to your `.ubiquibot-config.yml` file with the appropriate URL: ```javascript -plugin: http://127.0.0.1:4000 - runsOn: [ "issue_comment.created", "issue_comment.edited", "issue_comment.deleted" ] + runsOn: [ "issue_comment.created", "issue_comment.edited", "issue_comment.deleted" , "issues.opened", "issues.edited", "issues.deleted"] ``` + ## Testing Locally - Run `yarn install` to install the dependencies. - Run `yarn worker` to start the server. diff --git a/eslint.config.mjs b/eslint.config.mjs index f43237a..f594657 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", "src/types/database.ts"], + ignores: [".github/knip.ts", "src/types/database.ts", "src/adapters/utils/markdown-to-plaintext.ts"], extends: [eslint.configs.recommended, ...tsEslint.configs.recommended, sonarjs.configs.recommended], languageOptions: { parser: tsEslint.parser, diff --git a/manifest.json b/manifest.json index 0e83faf..328a300 100644 --- a/manifest.json +++ b/manifest.json @@ -1,5 +1,5 @@ { "name": "@ubiquity-os/comment-vector-embeddings", "description": "Issue comment plugin for Ubiquibot. It enables the storage, updating, and deletion of issue comment embeddings.", - "ubiquity:listeners": ["issue_comment.created", "issue_comment.edited", "issue_comment.deleted"] + "ubiquity:listeners": ["issue_comment.created", "issue_comment.edited", "issue_comment.deleted", "issues.opened", "issues.edited", "issues.deleted"] } diff --git a/package.json b/package.json index bb43997..6ef92a4 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "knip-ci": "knip --no-exit-code --reporter json --config .github/knip.ts", "prepare": "husky install", "test": "jest --setupFiles dotenv/config --coverage", - "worker": "wrangler dev --env dev --port 4000", + "worker": "wrangler dev --env dev --port 5000", "supabase:generate:local": "supabase gen types typescript --local > src/types/database.ts", "supabase:generate:remote": "cross-env-shell \"supabase gen types typescript --project-id $SUPABASE_PROJECT_ID --schema public > src/types/database.ts\"" }, @@ -35,10 +35,13 @@ "@octokit/webhooks": "13.2.7", "@sinclair/typebox": "0.32.33", "@supabase/supabase-js": "^2.45.2", + "@types/markdown-it": "^14.1.2", "@ubiquity-dao/ubiquibot-logger": "^1.3.0", "dotenv": "16.4.5", - "openai": "^4.56.0", - "typebox-validators": "0.3.5" + "markdown-it": "^14.1.0", + "markdown-it-plain-text": "^0.3.0", + "typebox-validators": "0.3.5", + "voyageai": "^0.0.1-5" }, "devDependencies": { "@commitlint/cli": "19.3.0", diff --git a/src/adapters/index.ts b/src/adapters/index.ts index e5f0346..ca31d27 100644 --- a/src/adapters/index.ts +++ b/src/adapters/index.ts @@ -2,19 +2,21 @@ import { SupabaseClient } from "@supabase/supabase-js"; import { Context } from "../types"; import { Comment } from "./supabase/helpers/comment"; import { SuperSupabase } from "./supabase/helpers/supabase"; -import { SuperOpenAi } from "./openai/helpers/openai"; -import OpenAI from "openai"; -import { Embedding } from "./openai/helpers/embedding"; +import { Embedding as VoyageEmbedding } from "./voyage/helpers/embedding"; +import { SuperVoyage } from "./voyage/helpers/voyage"; +import { VoyageAIClient } from "voyageai"; +import { Issues } from "./supabase/helpers/issues"; -export function createAdapters(supabaseClient: SupabaseClient, openai: OpenAI, context: Context) { +export function createAdapters(supabaseClient: SupabaseClient, voyage: VoyageAIClient, context: Context) { return { supabase: { comment: new Comment(supabaseClient, context), + issue: new Issues(supabaseClient, context), super: new SuperSupabase(supabaseClient, context), }, - openai: { - embedding: new Embedding(openai, context), - super: new SuperOpenAi(openai, context), + voyage: { + embedding: new VoyageEmbedding(voyage, context), + super: new SuperVoyage(voyage, context), }, }; } diff --git a/src/adapters/openai/helpers/embedding.ts b/src/adapters/openai/helpers/embedding.ts deleted file mode 100644 index 6b912dd..0000000 --- a/src/adapters/openai/helpers/embedding.ts +++ /dev/null @@ -1,25 +0,0 @@ -import OpenAI from "openai"; -import { Context } from "../../../types"; -import { SuperOpenAi } from "./openai"; -const VECTOR_SIZE = 3072; - -export class Embedding extends SuperOpenAi { - protected context: Context; - - constructor(client: OpenAI, context: Context) { - super(client, context); - this.context = context; - } - - async createEmbedding(text: string): Promise { - const params: OpenAI.EmbeddingCreateParams = { - model: "text-embedding-3-large", - input: text, - dimensions: VECTOR_SIZE, - }; - const response = await this.client.embeddings.create({ - ...params, - }); - return response.data[0]?.embedding; - } -} diff --git a/src/adapters/openai/helpers/openai.ts b/src/adapters/openai/helpers/openai.ts deleted file mode 100644 index 108a3d2..0000000 --- a/src/adapters/openai/helpers/openai.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { OpenAI } from "openai"; -import { Context } from "../../../types/context"; - -export class SuperOpenAi { - protected client: OpenAI; - protected context: Context; - - constructor(client: OpenAI, context: Context) { - this.client = client; - this.context = context; - } -} diff --git a/src/adapters/supabase/helpers/comment.ts b/src/adapters/supabase/helpers/comment.ts index 7530c10..3fa08b2 100644 --- a/src/adapters/supabase/helpers/comment.ts +++ b/src/adapters/supabase/helpers/comment.ts @@ -1,10 +1,14 @@ import { SupabaseClient } from "@supabase/supabase-js"; import { SuperSupabase } from "./supabase"; import { Context } from "../../../types/context"; +import { markdownToPlainText } from "../../utils/markdown-to-plaintext"; export interface CommentType { - id: number; - body: string; + id: string; + markdown?: string; + author_id: number; + created_at: string; + modified_at: string; embedding: number[]; } @@ -13,9 +17,16 @@ export class Comment extends SuperSupabase { super(supabase, context); } - async createComment(commentBody: string, commentId: number, issueBody: string) { + async createComment( + markdown: string | null, + commentNodeId: string, + authorId: number, + payload: Record | null, + isPrivate: boolean, + issueId: string + ) { //First Check if the comment already exists - const { data, error } = await this.supabase.from("issue_comments").select("*").eq("id", commentId); + const { data, error } = await this.supabase.from("issue_comments").select("*").eq("id", commentNodeId); if (error) { this.context.logger.error("Error creating comment", error); return; @@ -25,10 +36,16 @@ export class Comment extends SuperSupabase { return; } else { //Create the embedding for this comment - const embedding = await this.context.adapters.openai.embedding.createEmbedding(commentBody); + const embedding = await this.context.adapters.voyage.embedding.createEmbedding(markdown); + let plaintext: string | null = markdownToPlainText(markdown || ""); + if (isPrivate) { + markdown = null as string | null; + payload = null as Record | null; + plaintext = null as string | null; + } const { error } = await this.supabase .from("issue_comments") - .insert([{ id: commentId, commentbody: commentBody, issuebody: issueBody, embedding: embedding }]); + .insert([{ id: commentNodeId, markdown, plaintext, author_id: authorId, payload, embedding: embedding, issue_id: issueId }]); if (error) { this.context.logger.error("Error creating comment", error); return; @@ -37,25 +54,34 @@ export class Comment extends SuperSupabase { this.context.logger.info("Comment created successfully"); } - async updateComment(commentBody: string, commentId: number) { + async updateComment(markdown: string | null, commentNodeId: string, payload: Record | null, isPrivate: boolean) { //Create the embedding for this comment - const embedding = Array.from(await this.context.adapters.openai.embedding.createEmbedding(commentBody)); - const { error } = await this.supabase.from("issue_comments").update({ commentbody: commentBody, embedding: embedding }).eq("id", commentId); + const embedding = Array.from(await this.context.adapters.voyage.embedding.createEmbedding(markdown)); + let plaintext: string | null = markdownToPlainText(markdown || ""); + if (isPrivate) { + markdown = null as string | null; + payload = null as Record | null; + plaintext = null as string | null; + } + const { error } = await this.supabase + .from("issue_comments") + .update({ markdown, plaintext, embedding: embedding, payload, modified_at: new Date() }) + .eq("id", commentNodeId); if (error) { this.context.logger.error("Error updating comment", error); } } - async getComment(commentId: number): Promise { - const { data, error } = await this.supabase.from("issue_comments").select("*").eq("id", commentId); + async getComment(commentNodeId: string): Promise { + const { data, error } = await this.supabase.from("issue_comments").select("*").eq("id", commentNodeId); if (error) { this.context.logger.error("Error getting comment", error); } return data; } - async deleteComment(commentId: number) { - const { error } = await this.supabase.from("issue_comments").delete().eq("id", commentId); + async deleteComment(commentNodeId: string) { + const { error } = await this.supabase.from("issue_comments").delete().eq("id", commentNodeId); if (error) { this.context.logger.error("Error deleting comment", error); } diff --git a/src/adapters/supabase/helpers/issues.ts b/src/adapters/supabase/helpers/issues.ts new file mode 100644 index 0000000..063d8a9 --- /dev/null +++ b/src/adapters/supabase/helpers/issues.ts @@ -0,0 +1,88 @@ +import { SupabaseClient } from "@supabase/supabase-js"; +import { SuperSupabase } from "./supabase"; +import { Context } from "../../../types/context"; +import { markdownToPlainText } from "../../utils/markdown-to-plaintext"; + +export interface IssueType { + id: string; + markdown?: string; + author_id: number; + created_at: string; + modified_at: string; + payloadObject: Record | null; + embedding: number[]; +} + +export class Issues extends SuperSupabase { + constructor(supabase: SupabaseClient, context: Context) { + super(supabase, context); + } + + async createIssue(issueNodeId: string, payload: Record | null, isPrivate: boolean, markdown: string | null, authorId: number) { + //First Check if the issue already exists + const { data, error } = await this.supabase.from("issues").select("*").eq("id", issueNodeId); + if (error) { + this.context.logger.error("Error creating issue", error); + return; + } + if (data && data.length > 0) { + this.context.logger.info("Issue already exists"); + return; + } else { + const embedding = await this.context.adapters.voyage.embedding.createEmbedding(markdown); + let plaintext: string | null = markdownToPlainText(markdown || ""); + if (isPrivate) { + payload = null; + markdown = null; + plaintext = null; + } + const { error } = await this.supabase.from("issues").insert([{ id: issueNodeId, payload, markdown, plaintext, author_id: authorId, embedding }]); + if (error) { + this.context.logger.error("Error creating issue", error); + return; + } + } + this.context.logger.info("Issue created successfully"); + } + + async updateIssue(markdown: string | null, issueNodeId: string, payload: Record | null, isPrivate: boolean) { + //Create the embedding for this comment + const embedding = Array.from(await this.context.adapters.voyage.embedding.createEmbedding(markdown)); + let plaintext: string | null = markdownToPlainText(markdown || ""); + if (isPrivate) { + markdown = null as string | null; + payload = null as Record | null; + plaintext = null as string | null; + } + const { error } = await this.supabase + .from("issues") + .update({ markdown, plaintext, embedding: embedding, payload, modified_at: new Date() }) + .eq("id", issueNodeId); + if (error) { + this.context.logger.error("Error updating comment", error); + } + } + + async deleteIssue(issueNodeId: string) { + const { error } = await this.supabase.from("issues").delete().eq("id", issueNodeId); + if (error) { + this.context.logger.error("Error deleting comment", error); + } + } + + async findSimilarIssues(markdown: string, threshold: number): Promise { + const embedding = await this.context.adapters.voyage.embedding.createEmbedding(markdown); + const { data, error } = await this.supabase + .from("issues") + .select("*") + .eq("type", "issue") + .textSearch("embedding", embedding.join(",")) + .order("embedding", { foreignTable: "issues", ascending: false }) + .lte("embedding", threshold); + if (error) { + this.context.logger.error("Error finding similar issues", error); + return []; + } + return data; + } +} diff --git a/src/adapters/utils/markdown-to-plaintext.ts b/src/adapters/utils/markdown-to-plaintext.ts new file mode 100644 index 0000000..93e5787 --- /dev/null +++ b/src/adapters/utils/markdown-to-plaintext.ts @@ -0,0 +1,18 @@ +import markdownit from "markdown-it"; +import plainTextPlugin from "markdown-it-plain-text"; + +/** + * Converts a Markdown string to plain text. + * @param markdown + * @returns + */ +export function markdownToPlainText(markdown: string | null): string | null { + if (!markdown) { + return markdown; + } + const md = markdownit(); + md.use(plainTextPlugin); + md.render(markdown); + //Package markdown-it-plain-text does not have types + return (md as any).plainText; +} diff --git a/src/adapters/voyage/helpers/embedding.ts b/src/adapters/voyage/helpers/embedding.ts new file mode 100644 index 0000000..575543e --- /dev/null +++ b/src/adapters/voyage/helpers/embedding.ts @@ -0,0 +1,25 @@ +import { VoyageAIClient } from "voyageai"; +import { Context } from "../../../types"; +import { SuperVoyage } from "./voyage"; +const VECTOR_SIZE = 1024; + +export class Embedding extends SuperVoyage { + protected context: Context; + + constructor(client: VoyageAIClient, context: Context) { + super(client, context); + this.context = context; + } + + async createEmbedding(text: string | null): Promise { + if (text === null) { + return new Array(VECTOR_SIZE).fill(0); + } else { + const response = await this.client.embed({ + input: text, + model: "voyage-large-2-instruct", + }); + return (response.data && response.data[0]?.embedding) || []; + } + } +} diff --git a/src/adapters/voyage/helpers/voyage.ts b/src/adapters/voyage/helpers/voyage.ts new file mode 100644 index 0000000..c08c0af --- /dev/null +++ b/src/adapters/voyage/helpers/voyage.ts @@ -0,0 +1,12 @@ +import { VoyageAIClient } from "voyageai"; +import { Context } from "../../../types/context"; + +export class SuperVoyage { + protected client: VoyageAIClient; + protected context: Context; + + constructor(client: VoyageAIClient, context: Context) { + this.client = client; + this.context = context; + } +} diff --git a/src/handlers/add-comments.ts b/src/handlers/add-comments.ts index 35bd387..54745a1 100644 --- a/src/handlers/add-comments.ts +++ b/src/handlers/add-comments.ts @@ -1,25 +1,23 @@ import { Context } from "../types"; +import { CommentPayload } from "../types/payload"; export async function addComments(context: Context) { const { logger, - payload, adapters: { supabase }, } = context; + const { payload } = context as { payload: CommentPayload }; + const markdown = payload.comment.body; + const authorId = payload.comment.user?.id || -1; + const nodeId = payload.comment.node_id; + const isPrivate = payload.repository.private; + const issueId = payload.issue.node_id; - const sender = payload.comment.user?.login; - const repo = payload.repository.name; - const issueNumber = payload.issue.number; - const issueBody = payload.issue.body || ""; - const owner = payload.repository.owner.login; - const body = payload.comment.body; - - // Log the payload - logger.info(`Executing addComments:`, { sender, repo, issueNumber, owner }); - - // Add the comment to the database try { - await supabase.comment.createComment(body, payload.comment.id, issueBody); + if (!markdown) { + throw new Error("Comment body is empty"); + } + await supabase.comment.createComment(markdown, nodeId, authorId, payload, isPrivate, issueId); } catch (error) { if (error instanceof Error) { logger.error(`Error creating comment:`, { error: error, stack: error.stack }); @@ -31,5 +29,5 @@ export async function addComments(context: Context) { } logger.ok(`Successfully created comment!`); - logger.verbose(`Exiting addComments`); + logger.debug(`Exiting addComments`); } diff --git a/src/handlers/add-issue.ts b/src/handlers/add-issue.ts new file mode 100644 index 0000000..969a5c2 --- /dev/null +++ b/src/handlers/add-issue.ts @@ -0,0 +1,32 @@ +import { Context } from "../types"; +import { IssuePayload } from "../types/payload"; + +export async function addIssue(context: Context) { + const { + logger, + adapters: { supabase }, + } = context; + const { payload } = context as { payload: IssuePayload }; + const markdown = payload.issue.body + " " + payload.issue.title || null; + const authorId = payload.issue.user?.id || -1; + const nodeId = payload.issue.node_id; + const isPrivate = payload.repository.private; + + try { + if (!markdown) { + throw new Error("Issue body is empty"); + } + await supabase.issue.createIssue(nodeId, payload, isPrivate, markdown, authorId); + } catch (error) { + if (error instanceof Error) { + logger.error(`Error creating issue:`, { error: error, stack: error.stack }); + throw error; + } else { + logger.error(`Error creating issue:`, { err: error, error: new Error() }); + throw error; + } + } + + logger.ok(`Successfully created issue!`); + logger.debug(`Exiting addIssue`); +} diff --git a/src/handlers/delete-comments.ts b/src/handlers/delete-comments.ts index 7e9842f..8c9e394 100644 --- a/src/handlers/delete-comments.ts +++ b/src/handlers/delete-comments.ts @@ -1,23 +1,16 @@ import { Context } from "../types"; +import { CommentPayload } from "../types/payload"; export async function deleteComment(context: Context) { const { logger, - payload, adapters: { supabase }, } = context; + const { payload } = context as { payload: CommentPayload }; + const nodeId = payload.comment.node_id; - const sender = payload.comment.user?.login; - const repo = payload.repository.name; - const issueNumber = payload.issue.number; - const owner = payload.repository.owner.login; - - // Log the payload - logger.debug(`Executing deleteComment:`, { sender, repo, issueNumber, owner }); - - // Add the comment to the database try { - await supabase.comment.deleteComment(payload.comment.id); + await supabase.comment.deleteComment(nodeId); } catch (error) { if (error instanceof Error) { logger.error(`Error deleting comment:`, { error: error, stack: error.stack }); @@ -29,5 +22,5 @@ export async function deleteComment(context: Context) { } logger.ok(`Successfully deleted comment!`); - logger.verbose(`Exiting deleteComments`); + logger.debug(`Exiting deleteComments`); } diff --git a/src/handlers/delete-issue.ts b/src/handlers/delete-issue.ts new file mode 100644 index 0000000..b392c69 --- /dev/null +++ b/src/handlers/delete-issue.ts @@ -0,0 +1,26 @@ +import { Context } from "../types"; +import { IssuePayload } from "../types/payload"; + +export async function deleteIssues(context: Context) { + const { + logger, + adapters: { supabase }, + } = context; + const { payload } = context as { payload: IssuePayload }; + const nodeId = payload.issue.node_id; + + try { + await supabase.issue.deleteIssue(nodeId); + } catch (error) { + if (error instanceof Error) { + logger.error(`Error deleting issue:`, { error: error, stack: error.stack }); + throw error; + } else { + logger.error(`Error deleting issue:`, { err: error, error: new Error() }); + throw error; + } + } + + logger.ok(`Successfully deleted issue!`); + logger.debug(`Exiting deleteIssue`); +} diff --git a/src/handlers/update-comments.ts b/src/handlers/update-comments.ts index b0c3142..b1b9d18 100644 --- a/src/handlers/update-comments.ts +++ b/src/handlers/update-comments.ts @@ -1,24 +1,21 @@ import { Context } from "../types"; +import { CommentPayload } from "../types/payload"; export async function updateComment(context: Context) { const { logger, - payload, adapters: { supabase }, } = context; - - const sender = payload.comment.user?.login; - const repo = payload.repository.name; - const issueNumber = payload.issue.number; - const owner = payload.repository.owner.login; - const body = payload.comment.body; - - // Log the payload - logger.debug(`Executing updateComment:`, { sender, repo, issueNumber, owner }); - + const { payload } = context as { payload: CommentPayload }; + const nodeId = payload.comment.node_id; + const isPrivate = payload.repository.private; + const markdown = payload.comment.body || null; // Fetch the previous comment and update it in the db try { - await supabase.comment.updateComment(body, payload.comment.id); + if (!markdown) { + throw new Error("Comment body is empty"); + } + await supabase.comment.updateComment(markdown, nodeId, payload, isPrivate); } catch (error) { if (error instanceof Error) { logger.error(`Error updating comment:`, { error: error, stack: error.stack }); @@ -30,5 +27,5 @@ export async function updateComment(context: Context) { } logger.ok(`Successfully updated comment!`); - logger.verbose(`Exiting updateComment`); + logger.debug(`Exiting updateComment`); } diff --git a/src/handlers/update-issue.ts b/src/handlers/update-issue.ts new file mode 100644 index 0000000..763b2ba --- /dev/null +++ b/src/handlers/update-issue.ts @@ -0,0 +1,32 @@ +import { Context } from "../types"; +import { IssuePayload } from "../types/payload"; + +export async function updateIssue(context: Context) { + const { + logger, + adapters: { supabase }, + } = context; + const { payload } = context as { payload: IssuePayload }; + const payloadObject = payload; + const nodeId = payload.issue.node_id; + const isPrivate = payload.repository.private; + const markdown = payload.issue.body + " " + payload.issue.title || null; + // Fetch the previous issue and update it in the db + try { + if (!markdown) { + throw new Error("Issue body is empty"); + } + await supabase.issue.updateIssue(markdown, nodeId, payloadObject, isPrivate); + } catch (error) { + if (error instanceof Error) { + logger.error(`Error updating issue:`, { error: error, stack: error.stack }); + throw error; + } else { + logger.error(`Error updating issue:`, { err: error, error: new Error() }); + throw error; + } + } + + logger.ok(`Successfully updated issue!`); + logger.debug(`Exiting updateIssue`); +} diff --git a/src/plugin.ts b/src/plugin.ts index 4197ff6..2f7dad8 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -1,7 +1,7 @@ import { Octokit } from "@octokit/rest"; import { Env, PluginInputs } from "./types"; import { Context } from "./types"; -import { isIssueCommentEvent } from "./types/typeguards"; +import { isIssueCommentEvent, isIssueEvent } from "./types/typeguards"; import { LogLevel, Logs } from "@ubiquity-dao/ubiquibot-logger"; import { Database } from "./types/database"; import { createAdapters } from "./adapters"; @@ -9,7 +9,10 @@ import { createClient } from "@supabase/supabase-js"; import { addComments } from "./handlers/add-comments"; import { updateComment } from "./handlers/update-comments"; import { deleteComment } from "./handlers/delete-comments"; -import OpenAI from "openai"; +import { VoyageAIClient } from "voyageai"; +import { deleteIssues } from "./handlers/delete-issue"; +import { addIssue } from "./handlers/add-issue"; +import { updateIssue } from "./handlers/update-issue"; /** * The main plugin function. Split for easier testing. @@ -25,6 +28,15 @@ export async function runPlugin(context: Context) { case "issue_comment.edited": return await updateComment(context); } + } else if (isIssueEvent(context)) { + switch (eventName) { + case "issues.opened": + return await addIssue(context); + case "issues.deleted": + return await deleteIssues(context); + case "issues.edited": + return await updateIssue(context); + } } else { logger.error(`Unsupported event: ${eventName}`); } @@ -37,8 +49,8 @@ export async function runPlugin(context: Context) { export async function plugin(inputs: PluginInputs, env: Env) { const octokit = new Octokit({ auth: inputs.authToken }); const supabase = createClient(env.SUPABASE_URL, env.SUPABASE_KEY); - const openaiClient = new OpenAI({ - apiKey: env.OPENAI_API_KEY, + const voyageClient = new VoyageAIClient({ + apiKey: env.VOYAGEAI_API_KEY, }); const context: Context = { eventName: inputs.eventName, @@ -49,6 +61,6 @@ export async function plugin(inputs: PluginInputs, env: Env) { logger: new Logs("info" as LogLevel), adapters: {} as ReturnType, }; - context.adapters = createAdapters(supabase, openaiClient, context); - return runPlugin(context); + context.adapters = createAdapters(supabase, voyageClient, context); + return await runPlugin(context); } diff --git a/src/types/context.ts b/src/types/context.ts index d7e3cff..1227abf 100644 --- a/src/types/context.ts +++ b/src/types/context.ts @@ -10,7 +10,13 @@ import { createAdapters } from "../adapters"; * * ubiquity:listeners: ["issue_comment.created", ...] */ -export type SupportedEventsU = "issue_comment.created" | "issue_comment.deleted" | "issue_comment.edited"; +export type SupportedEventsU = + | "issue_comment.created" + | "issue_comment.deleted" + | "issue_comment.edited" + | "issues.opened" + | "issues.edited" + | "issues.deleted"; export type SupportedEvents = { [K in SupportedEventsU]: K extends WebhookEventName ? WebhookEvent : never; diff --git a/src/types/database.ts b/src/types/database.ts index 4f00255..3622f52 100644 --- a/src/types/database.ts +++ b/src/types/database.ts @@ -30,22 +30,28 @@ export type Database = { Tables: { issue_comments: { Row: { - commentbody: string; + author_id: string; + created_at: string; embedding: string; - id: number; - issuebody: string | null; + id: string; + modified_at: string; + plaintext: string | null; }; Insert: { - commentbody: string; + author_id: string; + created_at?: string; embedding: string; - id: number; - issuebody?: string | null; + id: string; + modified_at?: string; + plaintext?: string | null; }; Update: { - commentbody?: string; + author_id?: string; + created_at?: string; embedding?: string; - id?: number; - issuebody?: string | null; + id?: string; + modified_at?: string; + plaintext?: string | null; }; Relationships: []; }; diff --git a/src/types/env.ts b/src/types/env.ts index 6f47748..4c6cc1c 100644 --- a/src/types/env.ts +++ b/src/types/env.ts @@ -13,7 +13,7 @@ import { StandardValidator } from "typebox-validators"; export const envSchema = T.Object({ SUPABASE_URL: T.String(), SUPABASE_KEY: T.String(), - OPENAI_API_KEY: T.String(), + VOYAGEAI_API_KEY: T.String(), }); export const envValidator = new StandardValidator(envSchema); diff --git a/src/types/payload.ts b/src/types/payload.ts new file mode 100644 index 0000000..395fa09 --- /dev/null +++ b/src/types/payload.ts @@ -0,0 +1,3 @@ +import { EmitterWebhookEvent as WebhookEvent } from "@octokit/webhooks"; +export type CommentPayload = WebhookEvent<"issue_comment">["payload"]; +export type IssuePayload = WebhookEvent<"issues">["payload"]; diff --git a/src/types/typeguards.ts b/src/types/typeguards.ts index 266c856..01a6c26 100644 --- a/src/types/typeguards.ts +++ b/src/types/typeguards.ts @@ -14,3 +14,12 @@ import { Context } from "./context"; export function isIssueCommentEvent(context: Context): context is Context<"issue_comment.created" | "issue_comment.deleted" | "issue_comment.edited"> { return context.eventName === "issue_comment.created" || context.eventName === "issue_comment.deleted" || context.eventName === "issue_comment.edited"; } + +/** + * Restricts the scope of `context` to the `issues.opened`, `issues.edited`, and `issues.deleted` payloads. + * + * @param context The context object. + */ +export function isIssueEvent(context: Context): context is Context<"issues.opened" | "issues.edited" | "issues.deleted"> { + return context.eventName === "issues.opened" || context.eventName === "issues.edited" || context.eventName === "issues.deleted"; +} diff --git a/supabase/migrations/20240908231044_issue_comments.sql b/supabase/migrations/20240908231044_issue_comments.sql new file mode 100644 index 0000000..51afb87 --- /dev/null +++ b/supabase/migrations/20240908231044_issue_comments.sql @@ -0,0 +1,15 @@ +-- Create the extension if it doesn't exist +CREATE EXTENSION IF NOT EXISTS vector; + +-- Drop the old issue_comments table +DROP TABLE IF EXISTS issue_comments; + +-- Create the issue_comments table +CREATE TABLE IF NOT EXISTS issue_comments ( + id VARCHAR PRIMARY KEY, + created_at TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp NOT NULL, + modified_at TIMESTAMP WITH TIME ZONE NOT NULL, + author_id VARCHAR NOT NULL, + plaintext TEXT NOT NULL, + embedding VECTOR(3072) NOT NULL +); \ No newline at end of file diff --git a/supabase/migrations/20240908235818_issue_comments.sql b/supabase/migrations/20240908235818_issue_comments.sql new file mode 100644 index 0000000..11eb77a --- /dev/null +++ b/supabase/migrations/20240908235818_issue_comments.sql @@ -0,0 +1,2 @@ +ALTER TABLE issue_comments +ALTER COLUMN modified_at SET DEFAULT current_timestamp; \ No newline at end of file diff --git a/supabase/migrations/20240909072603_issue_comments.sql b/supabase/migrations/20240909072603_issue_comments.sql new file mode 100644 index 0000000..93de8ff --- /dev/null +++ b/supabase/migrations/20240909072603_issue_comments.sql @@ -0,0 +1,2 @@ +ALTER TABLE issue_comments +ALTER COLUMN plaintext DROP NOT NULL; \ No newline at end of file diff --git a/supabase/migrations/20240911023641_issue_comments.sql b/supabase/migrations/20240911023641_issue_comments.sql new file mode 100644 index 0000000..4ef319b --- /dev/null +++ b/supabase/migrations/20240911023641_issue_comments.sql @@ -0,0 +1,2 @@ +ALTER TABLE issue_comments +ADD COLUMN commentObject jsonb; \ No newline at end of file diff --git a/supabase/migrations/20240911104515_issue_comments.sql b/supabase/migrations/20240911104515_issue_comments.sql new file mode 100644 index 0000000..b0b2904 --- /dev/null +++ b/supabase/migrations/20240911104515_issue_comments.sql @@ -0,0 +1,5 @@ +ALTER TABLE issue_comments +DROP COLUMN embedding; + +ALTER TABLE issue_comments +ADD COLUMN embedding Vector(1024) NOT NULL; \ No newline at end of file diff --git a/supabase/migrations/20240911233824_issue_comments.sql b/supabase/migrations/20240911233824_issue_comments.sql new file mode 100644 index 0000000..c6ef4eb --- /dev/null +++ b/supabase/migrations/20240911233824_issue_comments.sql @@ -0,0 +1,8 @@ +ALTER TABLE issue_comments +RENAME COLUMN commentObject TO payloadObject; + +ALTER TABLE issue_comments +ADD COLUMN type Text NOT NULL; + +ALTER TABLE issue_comments +RENAME TO vectorDump; diff --git a/supabase/migrations/20240912225853_issue_comments.sql b/supabase/migrations/20240912225853_issue_comments.sql new file mode 100644 index 0000000..0d71cab --- /dev/null +++ b/supabase/migrations/20240912225853_issue_comments.sql @@ -0,0 +1,23 @@ +ALTER TABLE vectordump +RENAME TO issue_comments; + +CREATE TABLE IF NOT EXISTS issues ( + id VARCHAR primary key, + plaintext text, + embedding Vector(1024) not null, + payload jsonb, + author_id VARCHAR not null, + created_at timestamptz not null default now(), + modified_at timestamptz not null default now() +); + +ALTER TABLE issue_comments +ADD COLUMN issue_id VARCHAR +REFERENCES issues(id) +ON DELETE CASCADE; + +ALTER TABLE issue_comments +DROP COLUMN type; + +ALTER TABLE issue_comments +RENAME COLUMN payloadobject TO payload; \ No newline at end of file diff --git a/supabase/migrations/20240913070225_issue_comments.sql b/supabase/migrations/20240913070225_issue_comments.sql new file mode 100644 index 0000000..09daf65 --- /dev/null +++ b/supabase/migrations/20240913070225_issue_comments.sql @@ -0,0 +1,5 @@ +ALTER TABLE issue_comments +ADD COLUMN markdown TEXT; + +ALTER TABLE issues +ADD COLUMN markdown TEXT; \ No newline at end of file diff --git a/tests/__mocks__/adapter.ts b/tests/__mocks__/adapter.ts index 4c7a797..d1f634c 100644 --- a/tests/__mocks__/adapter.ts +++ b/tests/__mocks__/adapter.ts @@ -1,54 +1,71 @@ import { Context } from "../../src/types"; import { Comment } from "../../src/adapters/supabase/helpers/comment"; -import { Embedding } from "../../src/adapters/openai/helpers/embedding"; import { STRINGS } from "./strings"; export interface CommentMock { - id: number; - commentbody: string; - issuebody: string; + id: string; + plaintext: string | null; + author_id: number; + payload?: Record | null; + type?: string; + issue_id?: string; embedding: number[]; } export function createMockAdapters(context: Context) { - const commentMap: Map = new Map(); + const commentMap: Map = new Map(); return { supabase: { comment: { - createComment: jest.fn(async (commentBody: string, commentId: number, issueBody: string) => { - if (commentMap.has(commentId)) { - throw new Error("Comment already exists"); + createComment: jest.fn( + async ( + plaintext: string | null, + commentNodeId: string, + authorId: number, + payload: Record | null, + isPrivate: boolean, + issueId: string + ) => { + if (commentMap.has(commentNodeId)) { + throw new Error("Comment already exists"); + } + const embedding = await context.adapters.voyage.embedding.createEmbedding(plaintext); + if (isPrivate) { + plaintext = null; + } + commentMap.set(commentNodeId, { id: commentNodeId, plaintext, author_id: authorId, embedding, issue_id: issueId }); } - const embedding = await context.adapters.openai.embedding.createEmbedding(commentBody); - commentMap.set(commentId, { id: commentId, commentbody: commentBody, issuebody: issueBody, embedding }); - }), - updateComment: jest.fn(async (commentBody: string, commentId: number) => { - if (!commentMap.has(commentId)) { + ), + updateComment: jest.fn(async (plaintext: string | null, commentNodeId: string, payload: Record | null, isPrivate: boolean) => { + if (!commentMap.has(commentNodeId)) { throw new Error(STRINGS.COMMENT_DOES_NOT_EXIST); } - const originalComment = commentMap.get(commentId); + const originalComment = commentMap.get(commentNodeId); if (!originalComment) { throw new Error(STRINGS.COMMENT_DOES_NOT_EXIST); } - const { id, issuebody } = originalComment; - const embedding = await context.adapters.openai.embedding.createEmbedding(commentBody); - commentMap.set(commentId, { id, commentbody: commentBody, issuebody, embedding }); + const { id, author_id } = originalComment; + const embedding = await context.adapters.voyage.embedding.createEmbedding(plaintext); + if (isPrivate) { + plaintext = null; + } + commentMap.set(commentNodeId, { id, plaintext, author_id, embedding }); }), - deleteComment: jest.fn(async (commentId: number) => { - if (!commentMap.has(commentId)) { + deleteComment: jest.fn(async (commentNodeId: string) => { + if (!commentMap.has(commentNodeId)) { throw new Error(STRINGS.COMMENT_DOES_NOT_EXIST); } - commentMap.delete(commentId); + commentMap.delete(commentNodeId); }), - getComment: jest.fn(async (commentId: number) => { - if (!commentMap.has(commentId)) { + getComment: jest.fn(async (commentNodeId: string) => { + if (!commentMap.has(commentNodeId)) { throw new Error(STRINGS.COMMENT_DOES_NOT_EXIST); } - return commentMap.get(commentId); + return commentMap.get(commentNodeId); }), } as unknown as Comment, }, - openai: { + voyage: { embedding: { createEmbedding: jest.fn(async (text: string) => { if (text && text.length > 0) { @@ -56,7 +73,7 @@ export function createMockAdapters(context: Context) { } return new Array(3072).fill(0); }), - } as unknown as Embedding, + } as unknown as number[], }, }; } diff --git a/tests/__mocks__/db.ts b/tests/__mocks__/db.ts index f7bd2b6..1681106 100644 --- a/tests/__mocks__/db.ts +++ b/tests/__mocks__/db.ts @@ -7,36 +7,82 @@ import { factory, nullable, primaryKey } from "@mswjs/data"; export const db = factory({ users: { id: primaryKey(Number), - name: String, login: String, + avatar_url: nullable(String), // Add any additional fields based on the schema }, - issue: { + repo: { id: primaryKey(Number), - assignees: Array, + name: String, + full_name: String, // Assuming full_name is needed for repo + private: Boolean, + owner: { + login: String, + id: Number, + avatar_url: nullable(String), + }, html_url: String, - repository_url: String, - state: String, + description: nullable(String), + fork: Boolean, + url: String, + forks_url: String, + keys_url: String, + collaborators_url: String, + teams_url: String, + hooks_url: String, + issue_events_url: String, + events_url: String, + assignees_url: String, + branches_url: String, + tags_url: String, + blobs_url: String, + git_tags_url: String, + git_refs_url: String, + trees_url: String, + statuses_url: String, + languages_url: String, + stargazers_url: String, + contributors_url: String, + subscribers_url: String, + subscription_url: String, + commits_url: String, + git_commits_url: String, + comments_url: String, + issue_comment_url: String, + contents_url: String, + compare_url: String, + merges_url: String, + archive_url: String, + downloads_url: String, + issues_url: String, + pulls_url: String, + milestones_url: String, + notifications_url: String, + labels_url: String, + releases_url: String, + deployments_url: String, + }, + issue: { + id: primaryKey(Number), + number: Number, + title: String, + body: String, + user: { + login: String, + id: Number, + }, owner: String, repo: String, - labels: Array, author_association: String, - body: nullable(String), - closed_at: nullable(Date), - created_at: nullable(Date), + created_at: Date, + updated_at: Date, comments: Number, - comments_url: String, - events_url: String, - labels_url: String, + labels: Array, + state: String, locked: Boolean, - node_id: String, - title: String, - number: Number, - updated_at: Date, - url: String, - user: nullable(Object), - milestone: nullable(Object), assignee: nullable({ - avatar_url: String, + login: String, + id: Number, + avatar_url: nullable(String), email: nullable(String), events_url: String, followers_url: String, @@ -44,8 +90,6 @@ export const db = factory({ gists_url: String, gravatar_id: nullable(String), html_url: String, - id: Number, - login: String, name: nullable(String), node_id: String, organizations_url: String, @@ -58,27 +102,42 @@ export const db = factory({ type: String, url: String, }), - }, - repo: { - id: primaryKey(Number), - html_url: String, - name: String, - labels: Array, - owner: { - login: String, - id: Number, + milestone: nullable({ + title: String, + description: nullable(String), + due_on: nullable(Date), + number: Number, + state: String, + }), + reactions: { + url: String, + total_count: Number, + "+1": Number, + "-1": Number, + laugh: Number, + hooray: Number, + confused: Number, + heart: Number, + rocket: Number, + eyes: Number, }, - issues: Array, + timeline_url: String, + performed_via_github_app: nullable(Object), + state_reason: nullable(String), }, issueComments: { id: primaryKey(Number), body: String, created_at: Date, updated_at: Date, + node_id: String, issue_number: Number, user: { login: String, id: Number, }, + author_association: String, + html_url: String, + issue_url: String, }, }); diff --git a/tests/__mocks__/handlers.ts b/tests/__mocks__/handlers.ts index f1c494a..0019673 100644 --- a/tests/__mocks__/handlers.ts +++ b/tests/__mocks__/handlers.ts @@ -1,6 +1,5 @@ import { http, HttpResponse } from "msw"; import { db } from "./db"; -import issueTemplate from "./issue-template"; /** * Intercepts the routes and returns a custom payload */ @@ -31,13 +30,6 @@ export const handlers = [ } return HttpResponse.json(item); }), - // create issue - http.post("https://api.github.com/repos/:owner/:repo/issues", () => { - const id = db.issue.count() + 1; - const newItem = { ...issueTemplate, id }; - db.issue.create(newItem); - return HttpResponse.json(newItem); - }), // create comment http.post("https://api.github.com/repos/:owner/:repo/issues/:issue_number/comments", async ({ params: { issue_number: issueNumber }, request }) => { const { body } = await getValue(request.body); diff --git a/tests/__mocks__/helpers.ts b/tests/__mocks__/helpers.ts index b4c5b14..236ac8e 100644 --- a/tests/__mocks__/helpers.ts +++ b/tests/__mocks__/helpers.ts @@ -1,5 +1,4 @@ import { db } from "./db"; -import issueTemplate from "./issue-template"; import { STRINGS } from "./strings"; import usersGet from "./users-get.json"; @@ -12,34 +11,93 @@ import usersGet from "./users-get.json"; * Here is where you create issues, commits, pull requests, etc. */ export async function setupTests() { + // Insert users for (const item of usersGet) { - db.users.create(item); + db.users.create({ + login: item.login, + id: item.id, + }); } + // Insert repository db.repo.create({ id: 1, name: STRINGS.TEST_REPO, + full_name: `${STRINGS.USER_1}/${STRINGS.TEST_REPO}`, + private: false, owner: { login: STRINGS.USER_1, id: 1, + avatar_url: "", }, - issues: [], }); + // Insert issues db.issue.create({ - ...issueTemplate, + id: 1, + number: 1, + title: "First Issue", + body: "This is the body of the first issue.", + user: { + login: STRINGS.USER_1, + id: 1, + }, + author_association: "OWNER", + created_at: new Date().toISOString(), + updated_at: new Date().toISOString(), + comments: 0, + labels: [], + state: "open", + locked: false, + reactions: { + url: "", + total_count: 0, + "+1": 0, + "-1": 0, + laugh: 0, + hooray: 0, + confused: 0, + heart: 0, + rocket: 0, + eyes: 0, + }, + timeline_url: "", }); db.issue.create({ - ...issueTemplate, id: 2, number: 2, + title: "Second Issue", + body: "This is the body of the second issue.", + user: { + login: STRINGS.USER_1, + id: 1, + }, + author_association: "OWNER", + created_at: new Date().toISOString(), + updated_at: new Date().toISOString(), + comments: 0, labels: [], + state: "open", + locked: false, + reactions: { + url: "", + total_count: 0, + "+1": 0, + "-1": 0, + laugh: 0, + hooray: 0, + confused: 0, + heart: 0, + rocket: 0, + eyes: 0, + }, + timeline_url: "", }); } -export function createComment(comment: string, commentId: number) { - const isComment = db.issueComments.findFirst({ +export function createComment(comment: string, commentId: number, nodeId: string) { + const existingComment = db.issueComments.findFirst({ where: { id: { equals: commentId, @@ -47,7 +105,7 @@ export function createComment(comment: string, commentId: number) { }, }); - if (isComment) { + if (existingComment) { db.issueComments.update({ where: { id: { @@ -56,6 +114,7 @@ export function createComment(comment: string, commentId: number) { }, data: { body: comment, + updated_at: new Date().toISOString(), }, }); } else { @@ -63,10 +122,14 @@ export function createComment(comment: string, commentId: number) { id: commentId, body: comment, issue_number: 1, + node_id: nodeId, user: { login: STRINGS.USER_1, id: 1, }, + created_at: new Date().toISOString(), + updated_at: new Date().toISOString(), + author_association: "OWNER", }); } } diff --git a/tests/main.test.ts b/tests/main.test.ts index c78ec3d..40eeb7f 100644 --- a/tests/main.test.ts +++ b/tests/main.test.ts @@ -4,7 +4,7 @@ import { drop } from "@mswjs/data"; import { db } from "./__mocks__/db"; import { server } from "./__mocks__/node"; import { expect, describe, beforeAll, beforeEach, afterAll, afterEach, it } from "@jest/globals"; -import { Context } from "../src/types/context"; +import { Context, SupportedEvents } from "../src/types/context"; import { Octokit } from "@octokit/rest"; import { STRINGS } from "./__mocks__/strings"; import { createComment, setupTests } from "./__mocks__/helpers"; @@ -18,7 +18,6 @@ import { CommentMock, createMockAdapters } from "./__mocks__/adapter"; dotenv.config(); jest.requireActual("@octokit/rest"); jest.requireActual("@supabase/supabase-js"); -jest.requireActual("openai"); const octokit = new Octokit(); beforeAll(() => { @@ -41,51 +40,51 @@ describe("Plugin tests", () => { const response = await worker.fetch(new Request("http://localhost/manifest.json"), { SUPABASE_KEY: "test", SUPABASE_URL: "test", - OPENAI_API_KEY: "test", + VOYAGEAI_API_KEY: "test", }); const content = await response.json(); expect(content).toEqual(manifest); }); it("When a comment is created it should add it to the database", async () => { - const { context } = createContext(); + const { context } = createContext(STRINGS.HELLO_WORLD, 1, 1, 1, 1, "sasasCreate"); await runPlugin(context); const supabase = context.adapters.supabase; + const commentObject = null; try { - const issueBody = context.payload.issue.body || ""; - await supabase.comment.createComment(STRINGS.HELLO_WORLD, 1, issueBody); + await supabase.comment.createComment(STRINGS.HELLO_WORLD, "sasasCreate", 1, commentObject, false, "sasasCreateIssue"); throw new Error("Expected method to reject."); } catch (error) { if (error instanceof Error) { expect(error.message).toBe("Comment already exists"); } } - const comment = (await supabase.comment.getComment(1)) as unknown as CommentMock; + const comment = (await supabase.comment.getComment("sasasCreate")) as unknown as CommentMock; expect(comment).toBeDefined(); - expect(comment?.commentbody).toBeDefined(); - expect(comment?.commentbody).toBe(STRINGS.HELLO_WORLD); + expect(comment?.plaintext).toBeDefined(); + expect(comment?.plaintext).toBe(STRINGS.HELLO_WORLD); }); it("When a comment is updated it should update the database", async () => { - const { context } = createContext("Updated Message", 1, 1, 1, 1, "issue_comment.edited"); + const { context } = createContext("Updated Message", 1, 1, 1, 1, "sasasUpdate", "issue_comment.edited"); const supabase = context.adapters.supabase; - const issueBody = context.payload.issue.body || ""; - await supabase.comment.createComment(STRINGS.HELLO_WORLD, 1, issueBody); + const commentObject = null; + await supabase.comment.createComment(STRINGS.HELLO_WORLD, "sasasUpdate", 1, commentObject, false, "sasasUpdateIssue"); await runPlugin(context); - const comment = (await supabase.comment.getComment(1)) as unknown as CommentMock; + const comment = (await supabase.comment.getComment("sasasUpdate")) as unknown as CommentMock; expect(comment).toBeDefined(); - expect(comment?.commentbody).toBeDefined(); - expect(comment?.commentbody).toBe("Updated Message"); + expect(comment?.plaintext).toBeDefined(); + expect(comment?.plaintext).toBe("Updated Message"); }); it("When a comment is deleted it should delete it from the database", async () => { - const { context } = createContext("Text Message", 1, 1, 1, 1, "issue_comment.deleted"); + const { context } = createContext("Text Message", 1, 1, 1, 1, "sasasDelete", "issue_comment.deleted"); const supabase = context.adapters.supabase; - const issueBody = context.payload.issue.body || ""; - await supabase.comment.createComment(STRINGS.HELLO_WORLD, 1, issueBody); + const commentObject = null; + await supabase.comment.createComment(STRINGS.HELLO_WORLD, "sasasDelete", 1, commentObject, false, "sasasDeleteIssue"); await runPlugin(context); try { - await supabase.comment.getComment(1); + await supabase.comment.getComment("sasasDelete"); throw new Error("Expected method to reject."); } catch (error) { if (error instanceof Error) { @@ -109,14 +108,17 @@ function createContext( payloadSenderId: number = 1, commentId: number = 1, issueOne: number = 1, + nodeId: string = "sasas", eventName: Context["eventName"] = "issue_comment.created" ) { const repo = db.repo.findFirst({ where: { id: { equals: repoId } } }) as unknown as Context["payload"]["repository"]; const sender = db.users.findFirst({ where: { id: { equals: payloadSenderId } } }) as unknown as Context["payload"]["sender"]; const issue1 = db.issue.findFirst({ where: { id: { equals: issueOne } } }) as unknown as Context["payload"]["issue"]; - createComment(commentBody, commentId); // create it first then pull it from the DB and feed it to _createContext - const comment = db.issueComments.findFirst({ where: { id: { equals: commentId } } }) as unknown as Context["payload"]["comment"]; + createComment(commentBody, commentId, nodeId); // create it first then pull it from the DB and feed it to _createContext + const comment = db.issueComments.findFirst({ + where: { id: { equals: commentId } }, + }) as unknown as unknown as SupportedEvents["issue_comment.created"]["payload"]["comment"]; const context = createContextInner(repo, sender, issue1, comment, eventName); context.adapters = createMockAdapters(context) as unknown as Context["adapters"]; @@ -147,7 +149,7 @@ function createContextInner( repo: Context["payload"]["repository"], sender: Context["payload"]["sender"], issue: Context["payload"]["issue"], - comment: Context["payload"]["comment"], + comment: SupportedEvents["issue_comment.created"]["payload"]["comment"], eventName: Context["eventName"] = "issue_comment.created" ): Context { return { diff --git a/yarn.lock b/yarn.lock index 78b6afa..8439328 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1961,16 +1961,34 @@ expect "^29.0.0" pretty-format "^29.0.0" +"@types/linkify-it@^5": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-5.0.0.tgz#21413001973106cda1c3a9b91eedd4ccd5469d76" + integrity sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q== + "@types/lodash@^4.14.172": version "4.17.4" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.4.tgz#0303b64958ee070059e3a7184048a55159fe20b7" integrity sha512-wYCP26ZLxaT3R39kiN2+HcJ4kTd3U1waI/cY7ivWYqFP6pW3ZNpvi6Wd6PHZx7T/t8z0vlkXMg3QYLa7DZ/IJQ== +"@types/markdown-it@^14.1.2": + version "14.1.2" + resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-14.1.2.tgz#57f2532a0800067d9b934f3521429a2e8bfb4c61" + integrity sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog== + dependencies: + "@types/linkify-it" "^5" + "@types/mdurl" "^2" + "@types/md5@^2.3.0": version "2.3.5" resolved "https://registry.yarnpkg.com/@types/md5/-/md5-2.3.5.tgz#481cef0a896e3a5dcbfc5a8a8b02c05958af48a5" integrity sha512-/i42wjYNgE6wf0j2bcTX6kuowmdL/6PE4IVitMpm2eYKBUuYCprdcWVK+xEF0gcV6ufMCRhtxmReGfc6hIK7Jw== +"@types/mdurl@^2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-2.0.0.tgz#d43878b5b20222682163ae6f897b20447233bdfd" + integrity sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg== + "@types/mute-stream@^0.0.4": version "0.0.4" resolved "https://registry.yarnpkg.com/@types/mute-stream/-/mute-stream-0.0.4.tgz#77208e56a08767af6c5e1237be8888e2f255c478" @@ -1978,14 +1996,6 @@ 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" @@ -2007,13 +2017,6 @@ dependencies: undici-types "~5.26.4" -"@types/node@^18.11.18": - version "18.19.46" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.46.tgz#51801396c01153e0626e36f43386e83bc768b072" - integrity sha512-vnRgMS7W6cKa1/0G3/DTtQYpVrZ8c0Xm6UkLaVFrb9jtcVC3okokW09Ki1Qdrj9ISokszD69nY4WDLRlvHlhAA== - 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" @@ -2191,13 +2194,6 @@ agent-base@^7.0.2: dependencies: debug "^4.3.4" -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" @@ -2443,6 +2439,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + before-after-hook@^2.2.0: version "2.2.3" resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.3.tgz#c51e809c81a4e354084422b9b26bad88249c517c" @@ -2519,6 +2520,14 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" @@ -3210,6 +3219,11 @@ emoji-regex@^9.2.2: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== +entities@^4.4.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + env-paths@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" @@ -3535,6 +3549,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== + execa@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" @@ -3721,11 +3740,6 @@ foreground-child@^3.1.0: cross-spawn "^7.0.0" signal-exit "^4.0.1" -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.0" resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" @@ -3735,13 +3749,10 @@ form-data@^4.0.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" +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== formdata-polyfill@^4.0.10: version "4.0.10" @@ -4055,13 +4066,6 @@ 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.0.11: version "9.0.11" resolved "https://registry.yarnpkg.com/husky/-/husky-9.0.11.tgz#fc91df4c756050de41b3e478b2158b87c1e79af9" @@ -4072,6 +4076,11 @@ identity-function@^1.0.0: resolved "https://registry.yarnpkg.com/identity-function/-/identity-function-1.0.0.tgz#bea1159f0985239be3ca348edf40ce2f0dd2c21d" integrity sha512-kNrgUK0qI+9qLTBidsH85HjDLpZfrrS0ElquKKe/fJFdB3D7VeKdXXEvOPDUHSHOzdZKCAAaQIWWyp0l2yq6pw== +ieee754@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + ignore@^5.1.4, ignore@^5.1.8, ignore@^5.2.0, ignore@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" @@ -4821,6 +4830,11 @@ jiti@^1.21.0: 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@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -4946,6 +4960,13 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== +linkify-it@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-5.0.0.tgz#9ef238bfa6dc70bd8e7f9572b52d369af569b421" + integrity sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ== + dependencies: + uc.micro "^2.0.0" + lint-staged@15.2.7: version "15.2.7" resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-15.2.7.tgz#97867e29ed632820c0fb90be06cd9ed384025649" @@ -5126,6 +5147,23 @@ map-obj@^2.0.0: resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-2.0.0.tgz#a65cd29087a92598b8791257a523e021222ac1f9" integrity sha512-TzQSV2DiMYgoF5RycneKVUzIa9bQsj/B3tTgsE3dOGqlzHnGIDaC7XBE7grnA+8kZPnfqSGFe95VHc2oc0VFUQ== +markdown-it-plain-text@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/markdown-it-plain-text/-/markdown-it-plain-text-0.3.0.tgz#374abfcc1c95ae3d7a5e09365558d43184e1e7db" + integrity sha512-JT5x7oXZaTY6lwxsnxaIAT4hm5Q2Mi8dZoCaCNg3s7JXmDxY84D90OtV0US436UvN4zOUHoac4ExceTogydOLw== + +markdown-it@^14.1.0: + version "14.1.0" + resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-14.1.0.tgz#3c3c5992883c633db4714ccb4d7b5935d98b7d45" + integrity sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg== + dependencies: + argparse "^2.0.1" + entities "^4.4.0" + linkify-it "^5.0.0" + mdurl "^2.0.0" + punycode.js "^2.3.1" + uc.micro "^2.1.0" + md5@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f" @@ -5135,6 +5173,11 @@ md5@^2.3.0: crypt "0.0.2" is-buffer "~1.1.6" +mdurl@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-2.0.0.tgz#80676ec0433025dd3e17ee983d0fe8de5a2237e0" + integrity sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w== + memorystream@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" @@ -5267,11 +5310,6 @@ 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.0.0: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - msw@^2.0.8: version "2.3.1" resolved "https://registry.yarnpkg.com/msw/-/msw-2.3.1.tgz#bfc73e256ffc2c74ec4381b604abb258df35f32b" @@ -5320,7 +5358,7 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== -node-domexception@1.0.0, node-domexception@^1.0.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== @@ -5330,7 +5368,7 @@ node-fetch-native@^1.6.4: resolved "https://registry.yarnpkg.com/node-fetch-native/-/node-fetch-native-1.6.4.tgz#679fc8fd8111266d47d7e72c379f1bed9acff06e" integrity sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ== -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== @@ -5461,19 +5499,6 @@ onetime@^6.0.0: dependencies: mimic-fn "^4.0.0" -openai@^4.56.0: - version "4.56.0" - resolved "https://registry.yarnpkg.com/openai/-/openai-4.56.0.tgz#07d3982544cabd5781127288a8dfcceb7319a4cf" - integrity sha512-zcag97+3bG890MNNa0DQD9dGmmTWL8unJdNkulZzWRXrl+QeD+YkBI4H58rJcwErxqGK6a0jVPZ4ReJjhDGcmw== - 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" @@ -5752,6 +5777,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" @@ -5760,6 +5790,11 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.5" +punycode.js@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode.js/-/punycode.js-2.3.1.tgz#6b53e56ad75588234e79f4affa90972c7dd8cdb7" + integrity sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA== + punycode@^2.1.0: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" @@ -5770,6 +5805,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" + queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" @@ -5803,6 +5845,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" @@ -6288,7 +6341,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== @@ -6605,6 +6658,11 @@ typescript@5.4.5: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.5.tgz#42ccef2c571fdbd0f6718b1d1f5e6e5ef006f611" integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ== +uc.micro@^2.0.0, uc.micro@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-2.1.0.tgz#f8d3f7d0ec4c3dea35a7e3c8efa4cb8b45c9e7ee" + integrity sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A== + ufo@^1.5.3: version "1.5.3" resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.5.3.tgz#3325bd3c977b6c6cd3160bf4ff52989adc9d3344" @@ -6676,6 +6734,11 @@ uri-js@^4.2.2, uri-js@^4.4.1: 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== + util-deprecate@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -6708,6 +6771,19 @@ vlq@^0.2.1: resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.3.tgz#8f3e4328cf63b1540c0d67e1b2778386f8975b26" integrity sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow== +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.11: version "1.0.11" resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.11.tgz#0822a000e7d4dc083312580d7575fe9e3ba2e2bf" @@ -6732,11 +6808,6 @@ 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== - web-streams-polyfill@^3.0.3: version "3.3.3" resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz#2073b91a2fdb1fbfbd401e7de0ac9f8214cecb4b"