11import { RestEndpointMethodTypes } from '@octokit/plugin-rest-endpoint-methods' ;
2- import { generateObject , generateText } from 'ai' ;
2+ import { generateObject } from 'ai' ;
33import { Context } from 'probot' ;
4- import { z } from 'zod' ;
54import { toneDetails } from '~common/consts' ;
65import { Severity } from '~common/enums' ;
76import { Handler } from '~common/interfaces' ;
8- import { Config , FileContent , FileContext } from '~common/types' ;
7+ import { Config , FileContent , SummaryObject } from '~common/types' ;
98import { aiConfig , appConfig } from '~configs' ;
109import { SystemError } from '~errors' ;
11- import { calculateContentTokens , isTextContent } from '~helpers ' ;
10+ import { summaryObjectSchema } from '~schemas ' ;
1211import { AiService , CacheService , GitHubService , QueueService , TemplateService } from '~services' ;
13- import {
14- summaryCommentTemplate ,
15- summaryCreatePromptTemplate ,
16- summarySystemTemplate ,
17- summaryUpdatePromptTemplate
18- } from '~templates' ;
12+ import { summaryCommentTemplate , summaryPromptTemplate , summarySystemTemplate } from '~templates' ;
1913import { SummarySkipConditions } from '~utils' ;
2014
2115export class SummaryHandler implements Handler < 'pull_request' > {
@@ -33,14 +27,9 @@ export class SummaryHandler implements Handler<'pull_request'> {
3327 }
3428
3529 const fileContents = await this . queueService . schedule ( ( ) => GitHubService . getPullRequestFilesContent ( context ) ) ;
36- const fileContexts = await this . generateFileContexts ( apiKey , fileContents ) ;
30+ const summaryObject = await this . generateSummaryObject ( apiKey , config , fileContents ) ;
31+ const summary = this . generateMarkdown ( summaryObject . data ) ;
3732 const existingComment = await this . findExistingComment ( context ) ;
38- const summary = await this . generateSummary (
39- apiKey ,
40- config ,
41- fileContexts ,
42- existingComment ?. body ?. split ( '<!-- heading title summary -->' ) [ 1 ] // FIX: This is a temporary fix
43- ) ;
4433
4534 if ( existingComment ?. id ) {
4635 await this . queueService . schedule ( ( ) =>
@@ -55,7 +44,7 @@ export class SummaryHandler implements Handler<'pull_request'> {
5544 ) ;
5645 } else {
5746 const commentBody = TemplateService . render ( summaryCommentTemplate , {
58- summary : summary ,
47+ summary,
5948 commentType : appConfig . metadata . summary
6049 } ) ;
6150
@@ -71,89 +60,51 @@ export class SummaryHandler implements Handler<'pull_request'> {
7160 return comments . find ( ( comment ) => comment . body ?. includes ( appConfig . metadata . summary ) ) ;
7261 }
7362
74- private async generateFileContexts ( apiKey : string , fileContents : FileContent [ ] ) : Promise < FileContext [ ] > {
75- try {
76- const filesContexts = Promise . all (
77- fileContents
78- . filter ( ( file ) => isTextContent ( file . content ) )
79- . map ( async ( file ) => {
80- const contentTpm = calculateContentTokens ( file . content ?? '' , aiConfig . model . summary ) ;
63+ private generateMarkdown ( data : SummaryObject [ 'data' ] ) : string {
64+ const groupedData = data . reduce ( ( acc : Record < string , string [ ] > , item ) => {
65+ acc [ item . type ] = acc [ item . type ] || [ ] ;
66+ acc [ item . type ] . push ( item . description ) ;
8167
82- if ( contentTpm > aiConfig . maxTpm ) {
83- return this . makeSkippedLargeFileContext ( file ) ;
84- }
68+ return acc ;
69+ } , { } ) ;
8570
86- return this . queueService . schedule ( async ( ) => {
87- try {
88- const result = await this . queueService . schedule ( ( ) =>
89- generateObject < FileContext > ( {
90- topP : aiConfig . topP . summary ,
91- model : this . aiService . getModel ( aiConfig . model . summary , apiKey ) ,
92- system : `This is a Context Generator AI system which reads the content of the file and generates the context for the given file.` ,
93- prompt : `create a proper and clean context using these:\nFilename: ${ file . name } \nPath: ${ file . path } \nContent: ${ file . content } ` ,
94- schema : z . object ( {
95- name : z . string ( ) . describe ( 'The name of the file' ) ,
96- path : z . string ( ) . describe ( 'The path of the file' ) ,
97- context : z . string ( ) . describe ( 'The context of the file' )
98- } )
99- } )
100- ) ;
101- return result . object ;
102- } catch {
103- // TODO I expect we don't need this line
104- return this . makeSkippedLargeFileContext ( file ) ;
105- }
106- } ) ;
107- } )
108- ) ;
71+ return Object . entries ( groupedData )
72+ . map ( ( [ type , descriptions ] ) => {
73+ const description = descriptions . map ( ( description ) => ` - ${ description } ` ) . join ( '\n' ) ;
10974
110- return filesContexts ;
111- } catch ( error ) {
112- throw new SystemError ( 'Failed to generate summary' , Severity . ERROR , error ) ;
113- }
75+ return `- **${ type } **\n${ description } ` ;
76+ } )
77+ . join ( '\n' ) ;
11478 }
11579
116- private async generateSummary (
80+ private async generateSummaryObject (
11781 apiKey : string ,
11882 config : Config ,
119- summaryContexts : FileContext [ ] ,
120- existingCommentBody : string | undefined
121- ) : Promise < string > {
83+ fileContents : FileContent [ ]
84+ ) : Promise < SummaryObject > {
85+ const filesContext = JSON . stringify ( fileContents ) ;
86+
12287 try {
123- const filesContext = summaryContexts . map ( ( file ) => file . path + ': ' + file . context ) . join ( '\n' ) ;
12488 const result = await this . queueService . schedule ( ( ) =>
125- generateText ( {
89+ generateObject < SummaryObject > ( {
12690 topP : aiConfig . topP . summary ,
127- model : this . aiService . getModel ( aiConfig . model . summary , apiKey ) ,
91+ model : this . aiService . getModel ( aiConfig . model . summary , apiKey , true ) ,
12892 system : TemplateService . render ( summarySystemTemplate , {
12993 tone : toneDetails [ config . summary . tone ] ,
13094 language : config . language
13195 } ) ,
132- prompt : existingCommentBody
133- ? TemplateService . render ( summaryUpdatePromptTemplate , {
134- existingSummary : existingCommentBody ,
135- filesContext
136- } )
137- : TemplateService . render ( summaryCreatePromptTemplate , {
138- filesContext
139- } )
96+ prompt : TemplateService . render ( summaryPromptTemplate , {
97+ tone : toneDetails [ config . summary . tone ] ,
98+ language : config . language ,
99+ filesContext
100+ } ) ,
101+ schema : summaryObjectSchema
140102 } )
141103 ) ;
142104
143- return result . text ;
144- } catch {
145- // TODO I expect we should throw an exception here
146- return 'No summary available' ;
105+ return result . object ;
106+ } catch ( error ) {
107+ throw new SystemError ( 'Failed to generate summary object' , Severity . ERROR , error ) ;
147108 }
148109 }
149-
150- private makeSkippedLargeFileContext ( fileContent : FileContent ) : FileContext {
151- const { name, path } = fileContent ;
152-
153- return {
154- name,
155- path,
156- context : "Skipped due to the API's rate limitation."
157- } ;
158- }
159110}
0 commit comments