@@ -3,20 +3,21 @@ import * as github from '@actions/github';
33import { execaSync } from 'execa' ;
44import * as fs from 'fs' ;
55import { genContentsString } from './contents.js' ;
6- import { eventNames } from 'process' ;
76
87// --- Type Definitions ---
98
109export type AgentEvent =
1110 | { type : 'issuesOpened' , github : GitHubEventIssuesOpened }
1211 | { type : 'issueCommentCreated' , github : GitHubEventIssueCommentCreated }
1312 | { type : 'pullRequestCommentCreated' , github : GitHubEventPullRequestCommentCreated }
13+ | { type : 'pullRequestReviewCommentCreated' , github : GitHubEventPullRequestReviewCommentCreated }
1414 ;
1515
1616export type GitHubEvent =
1717 | GitHubEventIssuesOpened
1818 | GitHubEventIssueCommentCreated
19- | GitHubEventPullRequestCommentCreated ;
19+ | GitHubEventPullRequestCommentCreated
20+ | GitHubEventPullRequestReviewCommentCreated ;
2021
2122export type GitHubEventIssuesOpened = {
2223 action : 'opened' ;
@@ -35,6 +36,23 @@ export type GitHubEventPullRequestCommentCreated = {
3536 comment : GithubComment ;
3637}
3738
39+ export type GitHubEventPullRequestReviewCommentCreated = {
40+ action : 'created' ;
41+ pull_request : {
42+ number : number ;
43+ title ?: string ;
44+ body ?: string ;
45+ } ;
46+ comment : {
47+ id : number ;
48+ body : string ;
49+ path : string ;
50+ in_reply_to_id ?: number ;
51+ position ?: number ;
52+ line ?: number ;
53+ } ;
54+ }
55+
3856export type GithubComment = {
3957 id : number ;
4058 body : string ;
@@ -84,9 +102,9 @@ export async function cloneRepository(
84102
85103 // Determine branch to clone
86104 let branchToClone : string ;
87- if ( event . type === 'pullRequestCommentCreated' ) {
105+ if ( event . type === 'pullRequestCommentCreated' || event . type === 'pullRequestReviewCommentCreated' ) {
88106 // For PR comments, clone the PR's head branch
89- const prNumber = event . github . issue . number ;
107+ const prNumber = event . type === 'pullRequestCommentCreated' ? event . github . issue . number : event . github . pull_request . number ;
90108 try {
91109 const prData = await octokit . rest . pulls . get ( { ...repo , pull_number : prNumber } ) ;
92110 branchToClone = prData . data . head . ref ;
@@ -136,6 +154,10 @@ export function getEventType(payload: any): AgentEvent | null {
136154 if ( payload . action === 'created' && payload . issue && payload . issue . pull_request && payload . comment ) {
137155 return { type : 'pullRequestCommentCreated' , github : payload } ;
138156 }
157+ // Check for Pull Request Review Comment (comment on a specific line of code)
158+ if ( payload . action === 'created' && payload . pull_request && payload . comment && payload . comment . path ) {
159+ return { type : 'pullRequestReviewCommentCreated' , github : payload } ;
160+ }
139161 return null ;
140162}
141163
@@ -157,14 +179,22 @@ export async function addEyeReaction(
157179 content : 'eyes'
158180 } ) ;
159181 core . info ( `Added eye reaction to issue #${ event . issue . number } ` ) ;
160- } else if ( event . action === 'created' && 'comment' in event ) {
161- // Add eye reaction to comment
182+ } else if ( event . action === 'created' && 'comment' in event && 'issue' in event ) {
183+ // Add eye reaction to comment on issue or PR conversation
162184 await octokit . rest . reactions . createForIssueComment ( {
163185 ...repo ,
164186 comment_id : event . comment . id ,
165187 content : 'eyes'
166188 } ) ;
167189 core . info ( `Added eye reaction to comment on issue/PR #${ event . issue . number } ` ) ;
190+ } else if ( event . action === 'created' && 'comment' in event && 'pull_request' in event ) {
191+ // Add eye reaction to PR review comment
192+ await octokit . rest . reactions . createForPullRequestReviewComment ( {
193+ ...repo ,
194+ comment_id : event . comment . id ,
195+ content : 'eyes'
196+ } ) ;
197+ core . info ( `Added eye reaction to review comment on PR #${ event . pull_request . number } ` ) ;
168198 }
169199 } catch ( error ) {
170200 core . warning ( `Failed to add reaction: ${ error instanceof Error ? error . message : error } ` ) ;
@@ -178,7 +208,7 @@ export function extractText(event: GitHubEvent): string | null {
178208 if ( event . action === 'opened' && 'issue' in event ) {
179209 return event . issue . body ;
180210 }
181- // Ensure 'comment' exists before accessing 'body'
211+ // Ensure 'comment' exists before accessing 'body' for issue/PR comments
182212 if ( event . action === 'created' && 'comment' in event && event . comment ) {
183213 return event . comment . body ;
184214 }
@@ -258,11 +288,12 @@ export async function commitAndPush(
258288 workspace : string ,
259289 octokit : Octokit ,
260290 repo : RepoContext ,
261- event : GitHubEventPullRequestCommentCreated ,
291+ event : GitHubEventPullRequestCommentCreated | GitHubEventPullRequestReviewCommentCreated ,
262292 commitMessage : string ,
263293 output : string
264294) : Promise < void > {
265- const prNumber = event . issue . number ; // In PR comments, issue.number is the PR number
295+ // Get PR number from the event - different location based on event type
296+ const prNumber = 'issue' in event ? event . issue . number : event . pull_request . number ;
266297
267298 try {
268299 // Get current branch name from the PR context
@@ -333,18 +364,46 @@ export async function postComment(
333364 event : GitHubEvent ,
334365 body : string
335366) : Promise < void > {
336- const issueNumber = event . issue . number ;
337-
338367 try {
368+ if ( 'issue' in event ) {
369+ // For regular issues and PR conversation comments
370+ const issueNumber = event . issue . number ;
339371 await octokit . rest . issues . createComment ( {
340372 ...repo ,
341373 issue_number : issueNumber ,
342374 body : body ,
343375 } ) ;
344376 core . info ( `Comment posted to Issue/PR #${ issueNumber } ` ) ;
377+ } else if ( 'pull_request' in event ) {
378+ // For PR review comments
379+ const prNumber = event . pull_request . number ;
380+ const commentId = event . comment . id ;
381+ const inReplyTo = event . comment . in_reply_to_id ;
382+
383+ try {
384+ await octokit . rest . pulls . createReplyForReviewComment ( {
385+ ...repo ,
386+ pull_number : prNumber ,
387+ comment_id : inReplyTo ?? commentId , // Use the original comment ID if no reply
388+ body : body ,
389+ } ) ;
390+ core . info ( `Comment posted to PR #${ prNumber } Reply to comment #${ commentId } ` ) ;
391+
392+ } catch ( commentError ) {
393+ // If we can't determine if it's a top-level comment, fall back to creating a regular PR comment
394+ core . warning ( `Failed to check if comment is top-level: ${ commentError instanceof Error ? commentError . message : commentError } ` ) ;
395+ core . info ( `Falling back to creating a regular PR comment instead of a reply` ) ;
396+ await octokit . rest . issues . createComment ( {
397+ ...repo ,
398+ issue_number : prNumber ,
399+ body : body ,
400+ } ) ;
401+ core . info ( `Regular comment posted to PR #${ prNumber } ` ) ;
402+ }
403+ }
345404 } catch ( error ) {
346- core . error ( `Failed to post comment to Issue/PR # ${ issueNumber } : ${ error } ` ) ;
347- // Don't re-throw here, as posting a comment failure might not be critical
405+ core . error ( `Failed to post comment: ${ error instanceof Error ? error . message : error } ` ) ;
406+ // Don't re-throw here, as posting a comment failure might not be critical
348407 }
349408}
350409
@@ -361,12 +420,22 @@ export async function generatePrompt(
361420 const contents = await getContentsData ( octokit , repo , event ) ;
362421
363422 let prFiles : string [ ] = [ ] ;
423+ let contextInfo : string = '' ;
364424
365- if ( event . type === 'pullRequestCommentCreated' ) {
425+ if ( event . type === 'pullRequestCommentCreated' || event . type === 'pullRequestReviewCommentCreated' ) {
366426 // Get the changed files in the PR
367427 prFiles = await getChangedFiles ( octokit , repo , event ) ;
368428 }
369429
430+ // For PR review comments, add information about the file path and line
431+ if ( event . type === 'pullRequestReviewCommentCreated' ) {
432+ const comment = event . github . comment ;
433+ contextInfo = `Comment on file: ${ comment . path } ` ;
434+ if ( comment . line ) {
435+ contextInfo += `, line: ${ comment . line } ` ;
436+ }
437+ }
438+
370439 let historyPropmt = genContentsString ( contents . content , userPrompt ) ;
371440 for ( const comment of contents . comments ) {
372441 historyPropmt += genContentsString ( comment , userPrompt ) ;
@@ -376,6 +445,9 @@ export async function generatePrompt(
376445 if ( historyPropmt ) {
377446 prompt += `[History]\n${ historyPropmt } \n\n` ;
378447 }
448+ if ( contextInfo ) {
449+ prompt += `[Context]\n${ contextInfo } \n\n` ;
450+ }
379451 if ( prFiles . length > 0 ) {
380452 prompt += `[Changed Files]\n${ prFiles . join ( '\n' ) } \n\n` ;
381453 }
@@ -394,9 +466,19 @@ export async function getChangedFiles(
394466 repo : RepoContext ,
395467 event : AgentEvent
396468) : Promise < string [ ] > {
469+ let prNumber : number ;
470+
471+ if ( event . type === 'pullRequestCommentCreated' ) {
472+ prNumber = event . github . issue . number ;
473+ } else if ( event . type === 'pullRequestReviewCommentCreated' ) {
474+ prNumber = event . github . pull_request . number ;
475+ } else {
476+ throw new Error ( `Cannot get changed files for event type: ${ event . type } ` ) ;
477+ }
478+
397479 const prFilesResponse = await octokit . rest . pulls . listFiles ( {
398480 ...repo ,
399- pull_number : event . github . issue . number ,
481+ pull_number : prNumber ,
400482 } ) ;
401483 return prFilesResponse . data . map ( file => file . filename ) ;
402484}
@@ -411,6 +493,8 @@ export async function getContentsData(
411493 return await getIssueData ( octokit , repo , event . github . issue . number ) ;
412494 } else if ( event . type === 'pullRequestCommentCreated' ) {
413495 return await getPullRequestData ( octokit , repo , event . github . issue . number ) ;
496+ } else if ( event . type === 'pullRequestReviewCommentCreated' ) {
497+ return await getPullRequestReviewCommentsData ( octokit , repo , event . github . pull_request . number , event . github . comment . in_reply_to_id ?? event . github . comment . id ) ;
414498 }
415499 throw new Error ( 'Invalid event type for data retrieval' ) ;
416500}
@@ -457,6 +541,51 @@ async function getIssueData(
457541 }
458542}
459543
544+ /**
545+ * Retrieves the body and all review comment bodies for a specific pull request.
546+ * Note: PR review comments are fetched via the pulls API endpoint.
547+ */
548+ async function getPullRequestReviewCommentsData (
549+ octokit : Octokit ,
550+ repo : RepoContext ,
551+ pullNumber : number ,
552+ targetCommentId : number
553+ ) : Promise < GithubContentsData > {
554+ core . info ( `Fetching data for pull request review comments #${ pullNumber } ...` ) ;
555+ try {
556+ // Get PR body
557+ const prResponse = await octokit . rest . pulls . get ( {
558+ ...repo ,
559+ pull_number : pullNumber ,
560+ } ) ;
561+ const content = {
562+ number : prResponse . data . number ,
563+ title : prResponse . data . title ,
564+ body : prResponse . data . body ?? '' ,
565+ login : prResponse . data . user ?. login ?? 'anonymous'
566+ } ;
567+
568+ // Get PR review comments
569+ const commentsData = await octokit . paginate ( octokit . rest . pulls . listReviewComments , {
570+ ...repo ,
571+ pull_number : pullNumber ,
572+ per_page : 100 , // Fetch 100 per page for efficiency
573+ } ) ;
574+
575+ // Filter comments to include only those related to the target comment ID
576+ const comments = commentsData . filter ( comment => comment . id === targetCommentId || comment . in_reply_to_id === targetCommentId ) . map ( comment => ( {
577+ body : comment . body ?? '' ,
578+ login : comment . user ?. login ?? 'anonymous'
579+ } ) ) ;
580+ core . info ( `Fetched ${ commentsData . length } review comments for PR #${ pullNumber } .` ) ;
581+
582+ return { content, comments } ;
583+ } catch ( error ) {
584+ core . error ( `Failed to get data for pull request review comments #${ pullNumber } : ${ error } ` ) ;
585+ throw new Error ( `Could not retrieve data for pull request review comments #${ pullNumber } : ${ error instanceof Error ? error . message : error } ` ) ;
586+ }
587+ }
588+
460589/**
461590 * Retrieves the body and all comment bodies for a specific pull request.
462591 * Note: PR comments are fetched via the issues API endpoint.
0 commit comments