From 9ff1783dcf73deaa26f69d5d89b202f25e68af28 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Mon, 24 Feb 2025 00:03:58 -0800 Subject: [PATCH] feat(github): add github read pr & write comment tools to the github block, tested & works --- blocks/blocks/github.ts | 129 ++++++++++++++++++++++++++++++++++------ tools/github/comment.ts | 122 +++++++++++++++++++++++++++++++++++++ tools/github/pr.ts | 86 +++++++++++++++++++++++++++ tools/github/types.ts | 65 ++++++++++++++++++++ tools/index.ts | 4 ++ 5 files changed, 387 insertions(+), 19 deletions(-) create mode 100644 tools/github/comment.ts create mode 100644 tools/github/pr.ts create mode 100644 tools/github/types.ts diff --git a/blocks/blocks/github.ts b/blocks/blocks/github.ts index 9ad0c82608..9a5b54fc05 100644 --- a/blocks/blocks/github.ts +++ b/blocks/blocks/github.ts @@ -1,15 +1,29 @@ -import { RepoInfoResponse } from '@/tools/github/repo' -import { GithubIcon } from '../../components/icons' +import { GithubIcon } from '@/components/icons' +import { CreateCommentResponse, PullRequestResponse } from '@/tools/github/types' import { BlockConfig } from '../types' -export const GitHubBlock: BlockConfig = { +type GitHubResponse = PullRequestResponse | CreateCommentResponse + +export const GitHubBlock: BlockConfig = { type: 'github', name: 'GitHub', - description: 'Fetch GitHub repository', + description: 'Interact with GitHub repositories and PRs', category: 'tools', bgColor: '#181C1E', icon: GithubIcon, subBlocks: [ + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + layout: 'full', + options: [ + { label: 'Get PR details', id: 'github_pr' }, + { label: 'Create PR comment', id: 'github_comment' }, + { label: 'Get repository info', id: 'github_repoinfo' }, + ], + value: () => 'github_pr', + }, { id: 'owner', title: 'Repository Owner', @@ -25,15 +39,20 @@ export const GitHubBlock: BlockConfig = { placeholder: 'e.g., vscode', }, { - id: 'action', - title: 'Action', - type: 'dropdown', + id: 'pullNumber', + title: 'Pull Request Number', + type: 'short-input', + layout: 'half', + placeholder: 'e.g., 123', + condition: { field: 'operation', value: 'github_pr' }, + }, + { + id: 'body', + title: 'Comment', + type: 'long-input', layout: 'full', - options: [ - { label: 'Get general info', id: 'general_info' }, - { label: 'Get pull requests', id: 'pull_requests' }, - ], - value: () => 'general_info', + placeholder: 'Enter comment text', + condition: { field: 'operation', value: 'github_comment' }, }, { id: 'apiKey', @@ -43,23 +62,95 @@ export const GitHubBlock: BlockConfig = { placeholder: 'Enter GitHub Token', password: true, }, + { + id: 'commentType', + title: 'Comment Type', + type: 'dropdown', + layout: 'half', + options: [ + { label: 'General PR Comment', id: 'pr_comment' }, + { label: 'File-specific Comment', id: 'file_comment' }, + ], + condition: { field: 'operation', value: 'github_comment' }, + }, + { + id: 'path', + title: 'File Path', + type: 'short-input', + layout: 'half', + placeholder: 'e.g., src/main.ts', + condition: { + field: 'operation', + value: 'github_comment', + and: { + field: 'commentType', + value: 'file_comment', + }, + }, + }, + { + id: 'line', + title: 'Line Number', + type: 'short-input', + layout: 'half', + placeholder: 'e.g., 42', + condition: { + field: 'operation', + value: 'github_comment', + and: { + field: 'commentType', + value: 'file_comment', + }, + }, + }, ], tools: { - access: ['github_repoinfo'], + access: ['github_pr', 'github_comment', 'github_repoinfo'], + config: { + tool: (params) => { + switch (params.operation) { + case 'github_pr': + return 'github_pr' + case 'github_comment': + return 'github_comment' + case 'github_repoinfo': + default: + return 'github_repoinfo' + } + }, + }, }, inputs: { + operation: { type: 'string', required: true }, owner: { type: 'string', required: true }, repo: { type: 'string', required: true }, + pullNumber: { type: 'number', required: false }, + body: { type: 'string', required: false }, + apiKey: { type: 'string', required: true }, + commentType: { type: 'string', required: false }, + path: { type: 'string', required: false }, + line: { type: 'number', required: false }, + side: { type: 'string', required: false }, + commitId: { type: 'string', required: false }, }, outputs: { response: { type: { - name: 'string', - description: 'string', - stars: 'number', - forks: 'number', - openIssues: 'number', - language: 'string', + body: 'string', + html_url: 'string', + created_at: 'string', + updated_at: 'string', + number: 'number', + title: 'string', + state: 'string', + diff_url: 'string', + files: 'any', + comments: 'any', + id: 'number', + path: 'any', + line: 'any', + side: 'any', + commit_id: 'any', }, }, }, diff --git a/tools/github/comment.ts b/tools/github/comment.ts new file mode 100644 index 0000000000..608c7501e6 --- /dev/null +++ b/tools/github/comment.ts @@ -0,0 +1,122 @@ +import { ToolConfig } from '../types' +import { CreateCommentParams, CreateCommentResponse } from './types' + +export const commentTool: ToolConfig = { + id: 'github_comment', + name: 'GitHub PR Commenter', + description: 'Create comments on GitHub PRs', + version: '1.0.0', + + params: { + owner: { + type: 'string', + required: true, + description: 'Repository owner', + }, + repo: { + type: 'string', + required: true, + description: 'Repository name', + }, + pullNumber: { + type: 'number', + required: true, + description: 'Pull request number', + }, + body: { + type: 'string', + required: true, + description: 'Comment content', + }, + path: { + type: 'string', + required: false, + description: 'File path for review comment', + }, + position: { + type: 'number', + required: false, + description: 'Line number for review comment', + }, + apiKey: { + type: 'string', + required: true, + requiredForToolCall: true, + description: 'GitHub API token', + }, + commentType: { + type: 'string', + required: false, + description: 'Type of comment (pr_comment or file_comment)', + }, + line: { + type: 'number', + required: false, + description: 'Line number for review comment', + }, + side: { + type: 'string', + required: false, + description: 'Side of the diff (LEFT or RIGHT)', + default: 'RIGHT', + }, + commitId: { + type: 'string', + required: false, + description: 'The SHA of the commit to comment on', + }, + }, + + request: { + url: (params) => { + if (params.path) { + return `https://api.github.com/repos/${params.owner}/${params.repo}/pulls/${params.pullNumber}/comments` + } else { + return `https://api.github.com/repos/${params.owner}/${params.repo}/pulls/${params.pullNumber}/reviews` + } + }, + method: 'POST', + headers: (params) => ({ + Accept: 'application/vnd.github.v3+json', + Authorization: `Bearer ${params.apiKey}`, + 'X-GitHub-Api-Version': '2022-11-28', + }), + body: (params) => { + if (params.commentType === 'file_comment') { + return { + body: params.body, + commit_id: params.commitId, + path: params.path, + line: params.line || params.position, + side: params.side || 'RIGHT', + } + } + return { + body: params.body, + event: 'COMMENT', + } + }, + }, + + transformResponse: async (response) => { + const data = await response.json() + return { + success: true, + output: { + id: data.id, + body: data.body, + path: data.path, + line: data.line || data.position, + side: data.side, + commit_id: data.commit_id, + created_at: data.created_at, + updated_at: data.updated_at, + html_url: data.html_url, + }, + } + }, + + transformError: (error) => { + return error instanceof Error ? error.message : 'Failed to create comment' + }, +} diff --git a/tools/github/pr.ts b/tools/github/pr.ts new file mode 100644 index 0000000000..46e91fff9d --- /dev/null +++ b/tools/github/pr.ts @@ -0,0 +1,86 @@ +import { ToolConfig } from '../types' +import { PROperationParams, PullRequestResponse } from './types' + +export const prTool: ToolConfig = { + id: 'github_pr', + name: 'GitHub PR Reader', + description: 'Fetch PR details including diff and files changed', + version: '1.0.0', + + params: { + owner: { + type: 'string', + required: true, + description: 'Repository owner', + }, + repo: { + type: 'string', + required: true, + description: 'Repository name', + }, + pullNumber: { + type: 'number', + required: true, + description: 'Pull request number', + }, + apiKey: { + type: 'string', + required: true, + requiredForToolCall: true, + description: 'GitHub API token', + }, + }, + + request: { + url: (params) => + `https://api.github.com/repos/${params.owner}/${params.repo}/pulls/${params.pullNumber}`, + method: 'GET', + headers: (params) => ({ + Accept: 'application/vnd.github.v3+json', + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response) => { + const pr = await response.json() + + // Fetch the PR diff + const diffResponse = await fetch(pr.diff_url) + const diff = await diffResponse.text() + + // Fetch files changed + const filesResponse = await fetch( + `https://api.github.com/repos/${pr.base.repo.owner.login}/${pr.base.repo.name}/pulls/${pr.number}/files` + ) + const files = await filesResponse.json() + + return { + success: true, + output: { + number: pr.number, + title: pr.title, + body: pr.body || '', + state: pr.state, + html_url: pr.html_url, + diff_url: pr.diff_url, + created_at: pr.created_at, + updated_at: pr.updated_at, + diff, + files: files.map((file: any) => ({ + filename: file.filename, + additions: file.additions, + deletions: file.deletions, + changes: file.changes, + patch: file.patch, + blob_url: file.blob_url, + raw_url: file.raw_url, + status: file.status, + })), + }, + } + }, + + transformError: (error) => { + return error instanceof Error ? error.message : 'Failed to fetch PR details' + }, +} diff --git a/tools/github/types.ts b/tools/github/types.ts new file mode 100644 index 0000000000..906af62d97 --- /dev/null +++ b/tools/github/types.ts @@ -0,0 +1,65 @@ +import { ToolResponse } from '../types' + +export interface PullRequestResponse extends ToolResponse { + output: { + number: number + title: string + body: string + state: string + html_url: string + diff_url: string + created_at: string + updated_at: string + files?: { + filename: string + additions: number + deletions: number + changes: number + patch?: string + blob_url: string + raw_url: string + status: string + }[] + comments?: { + id: number + body: string + path?: string + line?: number + commit_id: string + created_at: string + updated_at: string + html_url: string + }[] + } +} + +export interface CreateCommentResponse extends ToolResponse { + output: { + id: number + body: string + path?: string + line?: number + side?: string + commit_id?: string + created_at: string + updated_at: string + html_url: string + } +} + +export interface PROperationParams { + owner: string + repo: string + pullNumber: number + apiKey: string +} + +export interface CreateCommentParams extends PROperationParams { + body: string + path?: string + position?: number + line?: number // Add line number support + side?: string // Add side parameter + commitId?: string + commentType?: 'pr_comment' | 'file_comment' // Add comment type +} diff --git a/tools/index.ts b/tools/index.ts index 30925f2738..43c8fdb7cf 100644 --- a/tools/index.ts +++ b/tools/index.ts @@ -4,6 +4,8 @@ import { chatTool as deepseekChat } from './deepseek/chat' import { reasonerTool as deepseekReasoner } from './deepseek/reasoner' import { scrapeTool } from './firecrawl/scrape' import { functionExecuteTool as functionExecute } from './function/execute' +import { commentTool } from './github/comment' +import { prTool } from './github/pr' import { repoInfoTool } from './github/repo' import { gmailReadTool } from './gmail/read' import { gmailSearchTool } from './gmail/search' @@ -68,6 +70,8 @@ export const tools: Record = { pinecone_generate_embeddings: pineconeGenerateEmbeddingsTool, pinecone_search_text: pineconeSearchTextTool, pinecone_upsert_text: pineconeUpsertTextTool, + github_pr: prTool, + github_comment: commentTool, } // Get a tool by its ID