diff --git a/.github/workflows/proposalPolice.yml b/.github/actions/javascript/proposalPoliceComment/action.yml similarity index 51% rename from .github/workflows/proposalPolice.yml rename to .github/actions/javascript/proposalPoliceComment/action.yml index e69beee2590a..f3e1474e4f04 100644 --- a/.github/workflows/proposalPolice.yml +++ b/.github/actions/javascript/proposalPoliceComment/action.yml @@ -1,7 +1,7 @@ name: ProposalPoliceā„¢ - Issue Comment Workflow on: issue_comment: - types: [created, edited] + types: [created] jobs: process-issue-comment: @@ -12,11 +12,11 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v2 with: - node-version: "16" + node-version: "20" - name: Install dependencies run: npm install - working-directory: .github/scripts/proposal-police + working-directory: ../../../ # Checks if the comment is created and follows the template - name: Run issue new comment script @@ -27,15 +27,4 @@ jobs: OPENAI_ASSISTANT_ID: ${{ secrets.OPENAI_ASSISTANT_ID }} ISSUE: ${{ toJson(github.event.issue) }} COMMENT: ${{ toJson(github.event.comment) }} - run: node .github/scripts/proposal-police/issue-comment.js - - # Checks if the comment is edited and if proposal template is followed - - name: Run issue comment edited script - if: github.event.action == 'edited' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - OPENAI_ASSISTANT_ID: ${{ secrets.OPENAI_ASSISTANT_ID }} - ISSUE: ${{ toJson(github.event.issue) }} - COMMENT: ${{ toJson(github.event.comment) }} - run: node .github/scripts/proposal-police/issue-comment-edit.js + run: ./index.js diff --git a/.github/actions/javascript/proposalPoliceComment/proposalPoliceComment.ts b/.github/actions/javascript/proposalPoliceComment/proposalPoliceComment.ts new file mode 100644 index 000000000000..d06e2d35f673 --- /dev/null +++ b/.github/actions/javascript/proposalPoliceComment/proposalPoliceComment.ts @@ -0,0 +1,121 @@ +// Import GitHub toolkit and Octokit REST client +import {context, getOctokit} from '@actions/github'; +import InitOpenAI from 'openai'; +import type {GitHubType} from '@github/libs/GithubUtils'; + +// @ts-ignore - process is not imported +const OpenAI = new InitOpenAI({apiKey: process.env.OPENAI_API_KEY}); + +async function handleIssueCommentCreated(octokit: InstanceType, labelNames: string[]) { + const payload = context.payload; + // @ts-ignore - process is not imported + const OPENAI_ASSISTANT_ID = process.env.OPENAI_ASSISTANT_ID; + + // check if the issue is opened and the has all passed labels + if (payload.issue?.state === 'open' && labelNames.every((labelName: string) => payload.issue?.labels.some((issueLabel: {name: string}) => issueLabel.name === labelName))) { + if (!OPENAI_ASSISTANT_ID) { + console.log('OPENAI_ASSISTANT_ID missing from the environment variables'); + return; + } + + // 1, check if comment is proposal and if proposal template is followed + const content = `I NEED HELP WITH CASE (1.), CHECK IF COMMENT IS PROPOSAL AND IF TEMPLATE IS FOLLOWED AS PER INSTRUCTIONS. IT IS MANDATORY THAT YOU RESPOND ONLY WITH "NO_ACTION" IN CASE THE COMMENT IS NOT A PROPOSAL. Comment content: ${payload.comment?.body}`; + + // create thread with first user message and run it + const createAndRunResponse = await OpenAI.beta.threads.createAndRun({ + assistant_id: OPENAI_ASSISTANT_ID ?? '', + thread: {messages: [{role: 'user', content}]}, + }); + + // count calls for debug purposes + let count = 0; + // poll for run completion + const intervalID = setInterval(() => { + OpenAI.beta.threads.runs + .retrieve(createAndRunResponse.thread_id, createAndRunResponse.id) + .then((run) => { + // return if run is not completed + if (run.status !== 'completed') { + return; + } + + // get assistant response + OpenAI.beta.threads.messages + .list(createAndRunResponse.thread_id) + .then((threadMessages) => { + // list thread messages content + threadMessages.data.forEach((message, index) => { + // @ts-ignore - we do have text value in content[0] but typescript doesn't know that + // this is a 'openai' package type issue + let assistantResponse = message.content?.[index]?.text?.value; + console.log('issue_comment.created - assistantResponse', assistantResponse); + + if (!assistantResponse) { + return console.log('issue_comment.created - assistantResponse is empty'); + } + + // check if assistant response is either NO_ACTION or "NO_ACTION" strings + // as sometimes the assistant response varies + const isNoAction = assistantResponse === 'NO_ACTION' || assistantResponse === '"NO_ACTION"'; + // if assistant response is NO_ACTION or message role is 'user', do nothing + if (isNoAction || threadMessages.data?.[index]?.role === 'user') { + if (threadMessages.data?.[index]?.role === 'user') { + return; + } + return console.log('issue_comment.created - NO_ACTION'); + } + + // if the assistant responded with no action but there's some context in the response + if (assistantResponse.includes('[NO_ACTION]')) { + // extract the text after [NO_ACTION] from assistantResponse since this is a + // bot related action keyword + const noActionContext = assistantResponse.split('[NO_ACTION] ')?.[1]?.replace('"', ''); + console.log('issue_comment.created - [NO_ACTION] w/ context: ', noActionContext); + return; + } + // replace {user} from response template with @username + assistantResponse = assistantResponse.replace('{user}', `@${payload.comment?.user.login}`); + // replace {proposalLink} from response template with the link to the comment + assistantResponse = assistantResponse.replace('{proposalLink}', payload.comment?.html_url); + + // remove any double quotes from the final comment because sometimes the assistant's + // response contains double quotes / sometimes it doesn't + assistantResponse = assistantResponse.replace('"', ''); + // create a comment with the assistant's response + console.log('issue_comment.created - proposal-police posts comment'); + return octokit.issues.createComment({ + ...context.repo, + issue_number: payload.issue?.number as number, + body: assistantResponse, + }); + }); + }) + .catch((err) => console.log('threads.messages.list - err', err)); + + // stop polling + clearInterval(intervalID); + }) + .catch((err) => console.log('threads.runs.retrieve - err', err)); + + // increment count for every threads.runs.retrieve call + count++; + console.log('threads.runs.retrieve - called:', count); + }, 1500); + } + + // return so that the script doesn't hang + return false; +} + +// Main function to process the workflow event +async function run() { + // @ts-ignore - process is not imported + const octokit: InstanceType = getOctokit(process.env.GITHUB_TOKEN); + await handleIssueCommentCreated(octokit, ['Help Wanted']); +} + +run().catch((error) => { + console.error(error); + // @ts-ignore - process is not imported + process.exit(1); +}); diff --git a/.github/actions/javascript/proposalPoliceCommentEdit/action.yml b/.github/actions/javascript/proposalPoliceCommentEdit/action.yml new file mode 100644 index 000000000000..e8cc92cd2027 --- /dev/null +++ b/.github/actions/javascript/proposalPoliceCommentEdit/action.yml @@ -0,0 +1,30 @@ +name: ProposalPoliceā„¢ - Issue Comment Edit Workflow +on: + issue_comment: + types: [edited] + +jobs: + process-issue-comment: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Setup Node.js + uses: actions/setup-node@v2 + with: + node-version: "20" + + - name: Install dependencies + run: npm install + working-directory: ../../../ + + # Checks if the comment is edited and if proposal template is followed + - name: Run issue comment edited script + if: github.event.action == 'edited' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + OPENAI_ASSISTANT_ID: ${{ secrets.OPENAI_ASSISTANT_ID }} + ISSUE: ${{ toJson(github.event.issue) }} + COMMENT: ${{ toJson(github.event.comment) }} + run: ./index.js diff --git a/.github/actions/javascript/proposalPoliceCommentEdit/proposalPoliceCommentEdit.ts b/.github/actions/javascript/proposalPoliceCommentEdit/proposalPoliceCommentEdit.ts new file mode 100644 index 000000000000..1d5fe462430a --- /dev/null +++ b/.github/actions/javascript/proposalPoliceCommentEdit/proposalPoliceCommentEdit.ts @@ -0,0 +1,117 @@ +// Import GitHub toolkit and Octokit REST client +import {context, getOctokit} from '@actions/github'; +import InitOpenAI from 'openai'; +import type {GitHubType} from '@github/libs/GithubUtils'; + +// @ts-ignore - process is not imported +const OpenAI = new InitOpenAI({apiKey: process.env.OPENAI_API_KEY}); + +async function handleIssueCommentEdited(octokit: InstanceType, labelNames: string[]) { + const payload = context.payload; + // @ts-ignore - process is not imported + const OPENAI_ASSISTANT_ID = process.env.OPENAI_ASSISTANT_ID; + + // check if the issue is opened and the has all passed labels + if (payload.issue?.state === 'open' && labelNames.every((labelName: string) => payload.issue?.labels.some((issueLabel: {name: string}) => issueLabel.name === labelName))) { + if (!OPENAI_ASSISTANT_ID) { + console.log('OPENAI_ASSISTANT_ID missing from the environment variables'); + return; + } + + // You need to adapt this part to fit the Edit Use Case as in the original function + const content = `I NEED HELP WITH CASE (2.) WHEN A USER THAT POSTED AN INITIAL PROPOSAL OR COMMENT (UNEDITED) THEN EDITS THE COMMENT - WE NEED TO CLASSIFY THE COMMENT BASED IN THE GIVEN INSTRUCTIONS AND IF TEMPLATE IS FOLLOWED AS PER INSTRUCTIONS. IT IS MANDATORY THAT YOU RESPOND ONLY WITH "NO_ACTION" IN CASE THE COMMENT IS NOT A PROPOSAL. \n\nPrevious comment content: ${payload.changes.body?.from}.\n\nEdited comment content: ${payload.comment?.body}`; + + // create thread with first user message and run it + const createAndRunResponse = await OpenAI.beta.threads.createAndRun({ + assistant_id: OPENAI_ASSISTANT_ID ?? '', + thread: {messages: [{role: 'user', content}]}, + }); + + // count calls for debug purposes + let count = 0; + // poll for run completion + const intervalID = setInterval(() => { + OpenAI.beta.threads.runs + .retrieve(createAndRunResponse.thread_id, createAndRunResponse.id) + .then((run) => { + // return if run is not completed yet + if (run.status !== 'completed') { + console.log('issue_comment.edited - run pending completion'); + return; + } + + // get assistant response + OpenAI.beta.threads.messages + .list(createAndRunResponse.thread_id) + .then((threadMessages) => { + // list thread messages content + threadMessages.data.forEach((message, index) => { + // @ts-ignore - we do have text value in content[0] but typescript doesn't know that + // this is a 'openai' package type issue + let assistantResponse = message.content?.[index]?.text?.value; + console.log('issue_comment.edited - assistantResponse', assistantResponse); + + if (!assistantResponse) { + return console.log('issue_comment.edited - assistantResponse is empty'); + } + + // check if assistant response is either NO_ACTION or "NO_ACTION" strings + // as sometimes the assistant response varies + const isNoAction = assistantResponse === 'NO_ACTION' || assistantResponse === '"NO_ACTION"'; + // if assistant response is NO_ACTION or message role is 'user', do nothing + if (isNoAction || threadMessages.data?.[index]?.role === 'user') { + if (threadMessages.data?.[index]?.role === 'user') { + return; + } + return console.log('issue_comment.edited - NO_ACTION'); + } + + // edit comment if assistant detected substantial changes and if the comment was not edited already by the bot + if (assistantResponse.includes('[EDIT_COMMENT]') && !payload.comment?.body.includes('Edited by **proposal-police**')) { + // extract the text after [EDIT_COMMENT] from assistantResponse since this is a + // bot related action keyword + let extractedNotice = assistantResponse.split('[EDIT_COMMENT] ')?.[1]?.replace('"', ''); + // format the github's updated_at like: 2024-01-24 13:15:24 UTC not 2024-01-28 18:18:28.000 UTC + const date = new Date(payload.comment?.updated_at); + const formattedDate = date.toISOString()?.split('.')?.[0]?.replace('T', ' ') + ' UTC'; + extractedNotice = extractedNotice.replace('{updated_timestamp}', formattedDate); + + console.log(`issue_comment.edited - proposal-police edits comment: ${payload.comment?.id}`); + return octokit.issues.updateComment({ + ...context.repo, + comment_id: payload.comment?.id as number, + body: `${extractedNotice}\n\n` + payload.comment?.body, + }); + } + + return false; + }); + }) + .catch((err) => console.log('threads.messages.list - err', err)); + + // stop polling + clearInterval(intervalID); + }) + .catch((err) => console.log('threads.runs.retrieve - err', err)); + + // increment count for every threads.runs.retrieve call + count++; + console.log('threads.runs.retrieve - called:', count); + }, 1500); + } + + // return so that the script doesn't hang + return false; +} + +async function run() { + // @ts-ignore - process is not imported + const octokit: InstanceType = getOctokit(process.env.GITHUB_TOKEN); + await handleIssueCommentEdited(octokit, ['Help Wanted']); +} + +run().catch((error) => { + console.error(error); + // @ts-ignore - process is not imported + process.exit(1); +}); diff --git a/.github/libs/GithubUtils.ts b/.github/libs/GithubUtils.ts index f445fc368559..99302f0ee765 100644 --- a/.github/libs/GithubUtils.ts +++ b/.github/libs/GithubUtils.ts @@ -1,7 +1,8 @@ /* eslint-disable @typescript-eslint/naming-convention, import/no-import-module-exports */ import * as core from '@actions/core'; import {getOctokitOptions, GitHub} from '@actions/github/lib/utils'; -import type {Octokit as OctokitCore} from '@octokit/core'; +import type {Octokit, Octokit as OctokitCore} from '@octokit/core'; +import type {Constructor} from '@octokit/core/dist-types/types'; import type {graphql} from '@octokit/graphql/dist-types/types'; import type {components as OctokitComponents} from '@octokit/openapi-types/types'; import type {PaginateInterface} from '@octokit/plugin-paginate-rest'; @@ -534,4 +535,15 @@ export default GithubUtils; // This is a temporary solution to allow the use of the GithubUtils class in both TypeScript and JavaScript. // Once all the files that import GithubUtils are migrated to TypeScript, this can be removed. +declare const GitHubType: (new (...args: unknown[]) => Record) & { + new (...args: unknown[]): Record; + plugins: unknown[]; +} & typeof Octokit & + Constructor< + RestEndpointMethods & { + paginate: PaginateInterface; + } + >; + +export {GitHubType}; export type {ListForRepoMethod, InternalOctokit, CreateCommentResponse, StagingDeployCashData}; diff --git a/.github/scripts/buildActions.sh b/.github/scripts/buildActions.sh index 30c284a776be..b533c404447d 100755 --- a/.github/scripts/buildActions.sh +++ b/.github/scripts/buildActions.sh @@ -27,6 +27,8 @@ declare -r GITHUB_ACTIONS=( "$ACTIONS_DIR/validateReassureOutput/validateReassureOutput.ts" "$ACTIONS_DIR/getGraphiteString/getGraphiteString.ts" "$ACTIONS_DIR/getArtifactInfo/getArtifactInfo.ts" + "$ACTIONS_DIR/proposalPoliceComment/proposalPoliceComment.ts" + "$ACTIONS_DIR/proposalPoliceCommentEdit/proposalPoliceCommentEdit.ts" ) # This will be inserted at the top of all compiled files as a warning to devs. diff --git a/.github/scripts/proposal-police/issue-comment-edit.js b/.github/scripts/proposal-police/issue-comment-edit.js deleted file mode 100644 index fd17903b8f76..000000000000 --- a/.github/scripts/proposal-police/issue-comment-edit.js +++ /dev/null @@ -1,116 +0,0 @@ -// Import GitHub toolkit and Octokit REST client -const { context, getOctokit } = require('@actions/github'); -const InitOpenAI = require('openai'); -const _ = require('underscore'); - -const OpenAI = new InitOpenAI({apiKey: process.env.OPENAI_API_KEY}); - -/** - * Handles the case when somebody edits a comment on an issue to check whether it's a proposal and what kind of changes were made. - * @param {*} octokit - GitHub REST client - * @param {*} labelNames - String array of label names to check for, ex. ['Help Wanted', "External"] - * @returns {Promise} - */ -async function handleIssueCommentEdited(octokit, labelNames) { - const payload = context.payload; - const OPENAI_ASSISTANT_ID = process.env.OPENAI_ASSISTANT_ID; - - // check if the issue is opened and the has all passed labels - if ( - payload.issue.state === 'open' && - _.every(labelNames, labelName => _.some(payload.issue.labels, issueLabel => issueLabel.name === labelName)) - ) { - if (!OPENAI_ASSISTANT_ID) { - console.log('OPENAI_ASSISTANT_ID missing from the environment variables'); - return; - } - - // You need to adapt this part to fit the Edit Use Case as in the original function - const content = `I NEED HELP WITH CASE (2.) WHEN A USER THAT POSTED AN INITIAL PROPOSAL OR COMMENT (UNEDITED) THEN EDITS THE COMMENT - WE NEED TO CLASSIFY THE COMMENT BASED IN THE GIVEN INSTRUCTIONS AND IF TEMPLATE IS FOLLOWED AS PER INSTRUCTIONS. IT IS MANDATORY THAT YOU RESPOND ONLY WITH "NO_ACTION" IN CASE THE COMMENT IS NOT A PROPOSAL. \n\nPrevious comment content: ${payload.changes.body.from}.\n\nEdited comment content: ${payload.comment.body}`; - - // create thread with first user message and run it - const createAndRunResponse = await OpenAI.beta.threads.createAndRun({ - assistant_id: OPENAI_ASSISTANT_ID || '', - thread: {messages: [{ role: "user", content }],}, - }); - - // count calls for debug purposes - let count = 0; - // poll for run completion - const intervalID = setInterval(() => { - OpenAI.beta.threads.runs.retrieve(createAndRunResponse.thread_id, createAndRunResponse.id).then(threadRun => { - // return if run is not completed yet - if (threadRun.status !== "completed") { - console.log('issue_comment.edited - run pending completion'); - return; - } - - // get assistant response - OpenAI.beta.threads.messages.list(createAndRunResponse.thread_id).then(threadMessages => { - // list thread messages content - threadMessages.data.forEach((message, index) => { - // @ts-ignore - we do have text value in content[0] but typescript doesn't know that - // this is a 'openai' package type issue - const assistantResponse = message.content[index].text.value; - console.log('issue_comment.edited - assistantResponse', assistantResponse); - - if (!assistantResponse) { - return console.log('issue_comment.edited - assistantResponse is empty'); - } - - // check if assistant response is either NO_ACTION or "NO_ACTION" strings - // as sometimes the assistant response varies - const isNoAction = assistantResponse === 'NO_ACTION' || assistantResponse === '"NO_ACTION"'; - // if assistant response is NO_ACTION or message role is 'user', do nothing - if (isNoAction || threadMessages.data[index].role === 'user') { - if (threadMessages.data[index].role === 'user') { - return; - } - return console.log('issue_comment.edited - NO_ACTION'); - } - - // edit comment if assistant detected substantial changes and if the comment was not edited already by the bot - if (assistantResponse.includes('[EDIT_COMMENT]') && !payload.comment.body.includes('Edited by **proposal-police**')) { - // extract the text after [EDIT_COMMENT] from assistantResponse since this is a - // bot related action keyword - let extractedNotice = assistantResponse.split('[EDIT_COMMENT] ')[1].replace('"', ''); - // format the github's updated_at like: 2024-01-24 13:15:24 UTC not 2024-01-28 18:18:28.000 UTC - const date = new Date(payload.comment.updated_at); - const formattedDate = `${date.toISOString().split('.')[0].replace('T', ' ') } UTC`; - extractedNotice = extractedNotice.replace('{updated_timestamp}', formattedDate); - - console.log(`issue_comment.edited - proposal-police edits comment: ${payload.comment.id}`); - return octokit.issues.updateComment({ - ...context.repo, - comment_id: payload.comment.id, - body: `${extractedNotice}\n\n${ payload.comment.body}`, - }); - } - - return false; - }); - }).catch(err => console.log('threads.messages.list - err', err)); - - // stop polling - clearInterval(intervalID); - }).catch(err => console.log('threads.runs.retrieve - err', err)); - - // increment count for every threads.runs.retrieve call - count++; - console.log('threads.runs.retrieve - called:', count); - }, 1500); - } - - // return so that the script doesn't hang - return false; -} - -async function run() { - const octokit = getOctokit(process.env.GITHUB_TOKEN); - await handleIssueCommentEdited(octokit, ['Help Wanted']); -} - -run().catch(error => { - console.error(error); - process.exit(1); -}); diff --git a/.github/scripts/proposal-police/issue-comment.js b/.github/scripts/proposal-police/issue-comment.js deleted file mode 100644 index a8dab4a95a84..000000000000 --- a/.github/scripts/proposal-police/issue-comment.js +++ /dev/null @@ -1,120 +0,0 @@ -// Import GitHub toolkit and Octokit REST client -const { context, getOctokit } = require('@actions/github'); -const InitOpenAI = require('openai'); -const _ = require('underscore'); - -const OpenAI = new InitOpenAI({apiKey: process.env.OPENAI_API_KEY}); - -/** - * Handles the case when somebody edits a comment on an issue to check whether it's a proposal and what kind of changes were made. - * @param {*} octokit - GitHub REST client - * @param {*} labelNames - String array of label names to check for, ex. ['Help Wanted', "External"] - * @returns {Promise} - */ -async function handleIssueCommentCreated(octokit, labelNames) { - const payload = context.payload; - const OPENAI_ASSISTANT_ID = process.env.OPENAI_ASSISTANT_ID; - - // check if the issue is opened and the has all passed labels - if ( - payload.issue.state === 'open' && - _.every(labelNames, labelName => _.some(payload.issue.labels, issueLabel => issueLabel.name === labelName)) - ) { - if (!OPENAI_ASSISTANT_ID) { - console.log('OPENAI_ASSISTANT_ID missing from the environment variables'); - return; - } - - // 1, check if comment is proposal and if proposal template is followed - const content = `I NEED HELP WITH CASE (1.), CHECK IF COMMENT IS PROPOSAL AND IF TEMPLATE IS FOLLOWED AS PER INSTRUCTIONS. IT IS MANDATORY THAT YOU RESPOND ONLY WITH "NO_ACTION" IN CASE THE COMMENT IS NOT A PROPOSAL. Comment content: ${payload.comment.body}`; - - // create thread with first user message and run it - const createAndRunResponse = await OpenAI.beta.threads.createAndRun({ - assistant_id: OPENAI_ASSISTANT_ID || '', - thread: {messages: [{ role: "user", content }],}, - }); - - // count calls for debug purposes - let count = 0; - // poll for run completion - const intervalID = setInterval(() => { - OpenAI.beta.threads.runs.retrieve(createAndRunResponse.thread_id, createAndRunResponse.id).then(threadRun => { - // return if run is not completed - if (threadRun.status !== "completed") { - return; - } - - // get assistant response - OpenAI.beta.threads.messages.list(createAndRunResponse.thread_id).then(threadMessages => { - // list thread messages content - threadMessages.data.forEach((message, index) => { - // @ts-ignore - we do have text value in content[0] but typescript doesn't know that - // this is a 'openai' package type issue - let assistantResponse = message.content[index].text.value; - console.log('issue_comment.created - assistantResponse', assistantResponse); - - if (!assistantResponse) { - return console.log('issue_comment.created - assistantResponse is empty'); - } - - // check if assistant response is either NO_ACTION or "NO_ACTION" strings - // as sometimes the assistant response varies - const isNoAction = assistantResponse === 'NO_ACTION' || assistantResponse === '"NO_ACTION"'; - // if assistant response is NO_ACTION or message role is 'user', do nothing - if (isNoAction || threadMessages.data[index].role === 'user') { - if (threadMessages.data[index].role === 'user') { - return; - } - return console.log('issue_comment.created - NO_ACTION'); - } - - // if the assistant responded with no action but there's some context in the response - if (assistantResponse.includes('[NO_ACTION]')) { - // extract the text after [NO_ACTION] from assistantResponse since this is a - // bot related action keyword - const noActionContext = assistantResponse.split('[NO_ACTION] ')[1].replace('"', ''); - console.log('issue_comment.created - [NO_ACTION] w/ context: ', noActionContext); - return; - } - // replace {user} from response template with @username - assistantResponse = assistantResponse.replace('{user}', `@${payload.comment.user.login}`); - // replace {proposalLink} from response template with the link to the comment - assistantResponse = assistantResponse.replace('{proposalLink}', payload.comment.html_url); - - // remove any double quotes from the final comment because sometimes the assistant's - // response contains double quotes / sometimes it doesn't - assistantResponse = assistantResponse.replace('"', ''); - // create a comment with the assistant's response - console.log('issue_comment.created - proposal-police posts comment'); - return octokit.issues.createComment({ - ...context.repo, - issue_number: payload.issue.number, - body: assistantResponse - }); - }); - }).catch(err => console.log('threads.messages.list - err', err)); - - // stop polling - clearInterval(intervalID); - }).catch(err => console.log('threads.runs.retrieve - err', err)); - - // increment count for every threads.runs.retrieve call - count++; - console.log('threads.runs.retrieve - called:', count); - }, 1500); - } - - // return so that the script doesn't hang - return false; -} - -// Main function to process the workflow event -async function run() { - const octokit = getOctokit(process.env.GITHUB_TOKEN); - await handleIssueCommentCreated(octokit, ['Help Wanted']); -} - -run().catch(error => { - console.error(error); - process.exit(1); -}); diff --git a/.github/scripts/proposal-police/package.json b/.github/scripts/proposal-police/package.json deleted file mode 100644 index 700e1d576c27..000000000000 --- a/.github/scripts/proposal-police/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "github-action", - "version": "1.0.0", - "dependencies": { - "@actions/core": "^1.2.6", - "@actions/github": "^4.0.0", - "@octokit/rest": "^18.0.0", - "openai": "^4.24.7" - } -} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 00e65ef8964b..51690a1e3ed1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -154,6 +154,7 @@ "@octokit/core": "4.0.4", "@octokit/plugin-paginate-rest": "3.1.0", "@octokit/plugin-throttling": "4.1.0", + "@octokit/rest": "^18.0.0", "@react-native-community/eslint-config": "3.0.0", "@react-native/babel-preset": "^0.73.21", "@react-native/metro-config": "^0.73.5", @@ -222,6 +223,7 @@ "jest-transformer-svg": "^2.0.1", "memfs": "^4.6.0", "onchange": "^7.1.0", + "openai": "^4.24.7", "patch-package": "^8.0.0", "portfinder": "^1.0.28", "prettier": "^2.8.8", @@ -7517,6 +7519,15 @@ "@octokit/core": ">=4" } }, + "node_modules/@octokit/plugin-request-log": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz", + "integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==", + "dev": true, + "peerDependencies": { + "@octokit/core": ">=3" + } + }, "node_modules/@octokit/plugin-rest-endpoint-methods": { "version": "5.16.2", "dev": true, @@ -7599,6 +7610,101 @@ "@octokit/openapi-types": "^18.0.0" } }, + "node_modules/@octokit/rest": { + "version": "18.12.0", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.12.0.tgz", + "integrity": "sha512-gDPiOHlyGavxr72y0guQEhLsemgVjwRePayJ+FcKc2SJqKUbxbkvf5kAZEWA/MKvsfYlQAMVzNJE3ezQcxMJ2Q==", + "dev": true, + "dependencies": { + "@octokit/core": "^3.5.1", + "@octokit/plugin-paginate-rest": "^2.16.8", + "@octokit/plugin-request-log": "^1.0.4", + "@octokit/plugin-rest-endpoint-methods": "^5.12.0" + } + }, + "node_modules/@octokit/rest/node_modules/@octokit/auth-token": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz", + "integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==", + "dev": true, + "dependencies": { + "@octokit/types": "^6.0.3" + } + }, + "node_modules/@octokit/rest/node_modules/@octokit/core": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz", + "integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==", + "dev": true, + "dependencies": { + "@octokit/auth-token": "^2.4.4", + "@octokit/graphql": "^4.5.8", + "@octokit/request": "^5.6.3", + "@octokit/request-error": "^2.0.5", + "@octokit/types": "^6.0.3", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/rest/node_modules/@octokit/endpoint": { + "version": "6.0.12", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz", + "integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==", + "dev": true, + "dependencies": { + "@octokit/types": "^6.0.3", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/rest/node_modules/@octokit/graphql": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz", + "integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==", + "dev": true, + "dependencies": { + "@octokit/request": "^5.6.0", + "@octokit/types": "^6.0.3", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/rest/node_modules/@octokit/plugin-paginate-rest": { + "version": "2.21.3", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.21.3.tgz", + "integrity": "sha512-aCZTEf0y2h3OLbrgKkrfFdjRL6eSOo8komneVQJnYecAxIej7Bafor2xhuDJOIFau4pk0i/P28/XgtbyPF0ZHw==", + "dev": true, + "dependencies": { + "@octokit/types": "^6.40.0" + }, + "peerDependencies": { + "@octokit/core": ">=2" + } + }, + "node_modules/@octokit/rest/node_modules/@octokit/request": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz", + "integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==", + "dev": true, + "dependencies": { + "@octokit/endpoint": "^6.0.1", + "@octokit/request-error": "^2.1.0", + "@octokit/types": "^6.16.1", + "is-plain-object": "^5.0.0", + "node-fetch": "^2.6.7", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/rest/node_modules/@octokit/request-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", + "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", + "dev": true, + "dependencies": { + "@octokit/types": "^6.0.3", + "deprecation": "^2.0.0", + "once": "^1.4.0" + } + }, "node_modules/@octokit/types": { "version": "6.41.0", "dev": true, @@ -12489,6 +12595,30 @@ "undici-types": "~5.26.4" } }, + "node_modules/@types/node-fetch": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz", + "integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==", + "dev": true, + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/node-fetch/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/@types/normalize-package-data": { "version": "2.4.4", "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", @@ -13788,6 +13918,18 @@ "node": ">= 6.0.0" } }, + "node_modules/agentkeepalive": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", + "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", + "dev": true, + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/aggregate-error": { "version": "3.1.0", "license": "MIT", @@ -21345,6 +21487,34 @@ "node": ">= 6" } }, + "node_modules/form-data-encoder": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", + "dev": true + }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "dev": true, + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, + "node_modules/formdata-node/node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, "node_modules/forwarded": { "version": "0.2.0", "license": "MIT", @@ -22428,6 +22598,15 @@ "node": ">=10.17.0" } }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dev": true, + "dependencies": { + "ms": "^2.0.0" + } + }, "node_modules/husky": { "version": "1.3.1", "dev": true, @@ -28592,6 +28771,25 @@ "node": ">= 0.10.5" } }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, "node_modules/node-fetch": { "version": "2.6.7", "license": "MIT", @@ -29285,6 +29483,34 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/openai": { + "version": "4.38.5", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.38.5.tgz", + "integrity": "sha512-Ym5GJL98ZhLJJ7enBx53jjG3vwN/fsB+Ozh46nnRZZS9W1NiYqbwkJ+sXd3dkCIiWIgcyyOPL2Zr8SQAzbpj3g==", + "dev": true, + "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", + "web-streams-polyfill": "^3.2.1" + }, + "bin": { + "openai": "bin/cli" + } + }, + "node_modules/openai/node_modules/@types/node": { + "version": "18.19.31", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.31.tgz", + "integrity": "sha512-ArgCD39YpyyrtFKIqMDvjz79jto5fcI/SVUs2HwB+f0dAzq68yqOdyaSivLiLugSziTpNXLQrVb7RZFmdZzbhA==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, "node_modules/opencollective-postinstall": { "version": "2.0.3", "license": "MIT", diff --git a/package.json b/package.json index 064c902fac49..91e51017f206 100644 --- a/package.json +++ b/package.json @@ -188,6 +188,8 @@ "devDependencies": { "@actions/core": "1.10.0", "@actions/github": "5.1.1", + "@octokit/rest": "^18.0.0", + "openai": "^4.24.7", "@babel/core": "^7.20.0", "@babel/parser": "^7.22.16", "@babel/plugin-proposal-class-properties": "^7.12.1",