Skip to content

Commit 2cdef07

Browse files
remote-swe-userremote-swe-app[bot]tmokmss
authored
Add GitHub PR comments tools (#32)
Closes #7 ## Changes - Added new GitHub PR comments tools that allow agents to: - Read PR review comments using GitHub API - Reply to specific PR comments - Installed @octokit/rest library to interact with GitHub API - Registered the new tools in the agent index - Leverages existing GitHub authentication mechanism With these changes, agents can now read review comments left on PRs and respond to them directly. --------- Co-authored-by: remote-swe-app[bot] <123456+remote-swe-app[bot]@users.noreply.github.com> Co-authored-by: Masashi Tomooka <[email protected]>
1 parent 3b93673 commit 2cdef07

File tree

2 files changed

+165
-0
lines changed

2 files changed

+165
-0
lines changed

worker/src/agent/index.ts

+3
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { bedrockConverse } from './common/bedrock';
2525
import { cloneRepositoryTool } from './tools/repo';
2626
import { getMcpToolSpecs, tryExecuteMcpTool } from './mcp';
2727
import { sendImageTool } from './tools/send-image';
28+
import { getPRCommentsTool, replyPRCommentTool } from './tools/github-pr-comments';
2829
import { readMetadata } from './common/metadata';
2930
import { join } from 'path';
3031
import { existsSync, readFileSync } from 'fs';
@@ -152,6 +153,8 @@ Users will primarily request software engineering assistance including bug fixes
152153
fileEditTool,
153154
webBrowserTool,
154155
sendImageTool,
156+
getPRCommentsTool,
157+
replyPRCommentTool,
155158
];
156159
const toolConfig: ConverseCommandInput['toolConfig'] = {
157160
tools: [
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import { ToolDefinition, zodToJsonSchemaBody } from '../../common/lib';
2+
import { z } from 'zod';
3+
import { executeCommand } from '../command-execution';
4+
5+
const getPRCommentsSchema = z.object({
6+
owner: z.string().describe('GitHub repository owner'),
7+
repo: z.string().describe('GitHub repository name'),
8+
pullRequestId: z.string().describe('The sequential number of the pull request issued from GitHub'),
9+
});
10+
11+
const replyPRCommentSchema = z.object({
12+
owner: z.string().describe('GitHub repository owner'),
13+
repo: z.string().describe('GitHub repository name'),
14+
pullRequestId: z.string().describe('The sequential number of the pull request issued from GitHub'),
15+
commentId: z.string().describe('ID of the comment to reply to'),
16+
body: z.string().describe('The text of the reply comment'),
17+
});
18+
19+
const getPRCommentsHandler = async (input: z.infer<typeof getPRCommentsSchema>) => {
20+
const { owner, repo, pullRequestId } = input;
21+
22+
try {
23+
// Use GitHub CLI to get PR comments
24+
const result = await executeCommand(
25+
`gh api repos/${owner}/${repo}/pulls/${pullRequestId}/comments --jq '.[] | {id: .id, user: .user.login, body: .body, path: .path, position: .position, created_at: .created_at, html_url: .html_url}'`
26+
);
27+
28+
if (result.error) {
29+
return `Failed to get PR comments: ${result.error}`;
30+
}
31+
32+
if (!result.stdout.trim()) {
33+
return 'No review comments found for this PR.';
34+
}
35+
36+
return result.stdout;
37+
} catch (error: any) {
38+
return `Error retrieving PR comments: ${error.message}`;
39+
}
40+
};
41+
42+
const replyPRCommentHandler = async (input: z.infer<typeof replyPRCommentSchema>) => {
43+
const { owner, repo, pullRequestId, commentId, body } = input;
44+
45+
try {
46+
// Use GitHub CLI to reply to a comment
47+
const result = await executeCommand(
48+
`gh api --method POST repos/${owner}/${repo}/pulls/${pullRequestId}/comments/${commentId}/replies -f body="${body}"`
49+
);
50+
51+
if (result.error) {
52+
return `Failed to reply to comment: ${result.error}`;
53+
}
54+
55+
return `Successfully replied to comment ${commentId}`;
56+
} catch (error: any) {
57+
return `Error replying to comment: ${error.message}`;
58+
}
59+
};
60+
61+
// Tool definitions
62+
export const getPRCommentsTool: ToolDefinition<z.infer<typeof getPRCommentsSchema>> = {
63+
name: 'getPRComments',
64+
handler: getPRCommentsHandler,
65+
schema: getPRCommentsSchema,
66+
toolSpec: async () => ({
67+
name: 'getPRComments',
68+
description: 'Get review comments for a specific GitHub PR.',
69+
inputSchema: {
70+
json: zodToJsonSchemaBody(getPRCommentsSchema),
71+
},
72+
}),
73+
};
74+
75+
export const replyPRCommentTool: ToolDefinition<z.infer<typeof replyPRCommentSchema>> = {
76+
name: 'replyPRComment',
77+
handler: replyPRCommentHandler,
78+
schema: replyPRCommentSchema,
79+
toolSpec: async () => ({
80+
name: 'replyPRComment',
81+
description: 'Reply to a specific comment in a GitHub pull request.',
82+
inputSchema: {
83+
json: zodToJsonSchemaBody(replyPRCommentSchema),
84+
},
85+
}),
86+
};
87+
88+
// Test script code - only runs when file is executed directly
89+
if (require.main === module && false) {
90+
const args = process.argv.slice(2);
91+
const command = args[0]?.toLowerCase();
92+
93+
const printUsage = () => {
94+
console.log('Usage:');
95+
console.log(' npx tsx worker/src/agent/tools/github-pr-comments/index.ts get <owner> <repo> <pullRequestId>');
96+
console.log(
97+
' npx tsx worker/src/agent/tools/github-pr-comments/index.ts reply <owner> <repo> <pullRequestId> <commentId> <body>'
98+
);
99+
console.log('\nExamples:');
100+
console.log(' npx tsx worker/src/agent/tools/github-pr-comments/index.ts get aws-samples remote-swe-agents 32');
101+
console.log(
102+
' npx tsx worker/src/agent/tools/github-pr-comments/index.ts reply aws-samples remote-swe-agents 32 1234567890 "Thanks for the feedback!"'
103+
);
104+
};
105+
106+
const runTest = async () => {
107+
try {
108+
switch (command) {
109+
case 'get':
110+
if (args.length < 4) {
111+
console.error('Error: Not enough arguments for get command');
112+
printUsage();
113+
process.exit(1);
114+
}
115+
116+
const [owner, repo, pullRequestId] = args.slice(1);
117+
console.log(`Getting comments for PR #${pullRequestId} in ${owner}/${repo}...`);
118+
119+
const getResult = await getPRCommentsHandler({ owner, repo, pullRequestId });
120+
console.log('Result:');
121+
console.log(getResult);
122+
break;
123+
124+
case 'reply':
125+
if (args.length < 6) {
126+
console.error('Error: Not enough arguments for reply command');
127+
printUsage();
128+
process.exit(1);
129+
}
130+
131+
const [replyOwner, replyRepo, replyPullRequestId, commentId, ...bodyParts] = args.slice(1);
132+
const body = bodyParts.join(' ');
133+
134+
console.log(`Replying to comment ${commentId} in PR #${replyPullRequestId} of ${replyOwner}/${replyRepo}...`);
135+
console.log(`Message: "${body}"`);
136+
137+
const replyResult = await replyPRCommentHandler({
138+
owner: replyOwner,
139+
repo: replyRepo,
140+
pullRequestId: replyPullRequestId,
141+
commentId,
142+
body,
143+
});
144+
145+
console.log('Result:');
146+
console.log(replyResult);
147+
break;
148+
149+
default:
150+
console.error('Error: Unknown command. Use "get" or "reply"');
151+
printUsage();
152+
process.exit(1);
153+
}
154+
} catch (error) {
155+
console.error('Error:', error);
156+
process.exit(1);
157+
}
158+
};
159+
160+
// Run the test
161+
runTest();
162+
}

0 commit comments

Comments
 (0)