@@ -57,7 +57,12 @@ import type {
5757 AIProviderDescriptorWithConfiguration ,
5858 AIProviderDescriptorWithType ,
5959} from './models/model' ;
60- import type { PromptTemplate , PromptTemplateContext , PromptTemplateType } from './models/promptTemplates' ;
60+ import type {
61+ PromptTemplate ,
62+ PromptTemplateContext ,
63+ PromptTemplateId ,
64+ PromptTemplateType ,
65+ } from './models/promptTemplates' ;
6166import type { AIChatMessage , AIProvider , AIRequestResult } from './models/provider' ;
6267import { getLocalPromptTemplate , resolvePrompt } from './utils/-webview/prompt.utils' ;
6368
@@ -201,9 +206,8 @@ export class AIProviderService implements Disposable {
201206
202207 private readonly _disposable : Disposable ;
203208 private _model : AIModel | undefined ;
204- private readonly _promptTemplates = new PromiseCache < PromptTemplateType , PromptTemplate > ( {
209+ private readonly _promptTemplates = new PromiseCache < PromptTemplateId , PromptTemplate | undefined > ( {
205210 createTTL : 12 * 60 * 60 * 1000 , // 12 hours
206- expireOnError : true ,
207211 } ) ;
208212 private _provider : AIProvider | undefined ;
209213 private _providerDisposable : Disposable | undefined ;
@@ -1114,71 +1118,81 @@ export class AIProviderService implements Disposable {
11141118 }
11151119
11161120 private async getPrompt < T extends PromptTemplateType > (
1117- template : T ,
1121+ templateType : T ,
11181122 model : AIModel ,
11191123 context : PromptTemplateContext < T > ,
11201124 maxInputTokens : number ,
11211125 retries : number ,
11221126 reporting : TelemetryEvents [ 'ai/generate' | 'ai/explain' ] ,
11231127 ) : Promise < { prompt : string ; truncated : boolean } > {
1124- const promptTemplate = await this . getPromptTemplate ( template , model ) ;
1128+ const promptTemplate = await this . getPromptTemplate ( templateType , model ) ;
11251129 if ( promptTemplate == null ) {
11261130 debugger ;
1127- throw new Error ( `No prompt template found for ${ template } ` ) ;
1131+ throw new Error ( `No prompt template found for ${ templateType } ` ) ;
11281132 }
11291133
11301134 const result = await resolvePrompt ( model , promptTemplate , context , maxInputTokens , retries , reporting ) ;
11311135 return result ;
11321136 }
11331137
11341138 private async getPromptTemplate < T extends PromptTemplateType > (
1135- template : T ,
1139+ templateType : T ,
11361140 model : AIModel ,
11371141 ) : Promise < PromptTemplate | undefined > {
1138- if ( ( await this . container . subscription . getSubscription ( ) ) . account ) {
1139- const scope = getLogScope ( ) ;
1142+ const scope = getLogScope ( ) ;
1143+
1144+ const template = getLocalPromptTemplate ( templateType , model ) ;
1145+ const templateId = template ?. id ?? templateType ;
1146+
1147+ return this . _promptTemplates . get ( templateId , async cancellable => {
1148+ if ( ! ( await this . container . subscription . getSubscription ( ) ) . account ) {
1149+ return template ;
1150+ }
11401151
11411152 try {
1142- return await this . _promptTemplates . get ( template , async ( ) => {
1143- const url = this . container . urls . getGkAIApiUrl ( `templates/message-prompt/${ template } ` ) ;
1144- const rsp = await fetch ( url , {
1145- headers : await this . connection . getGkHeaders ( undefined , undefined , {
1146- Accept : 'application/json' ,
1147- } ) ,
1148- } ) ;
1149- if ( ! rsp . ok ) {
1150- throw new Error ( `Getting prompt template (${ url } ) failed: ${ rsp . status } (${ rsp . statusText } )` ) ;
1153+ const url = this . container . urls . getGkAIApiUrl ( `templates/message-prompt/${ templateId } ` ) ;
1154+ const rsp = await fetch ( url , {
1155+ headers : await this . connection . getGkHeaders ( undefined , undefined , { Accept : 'application/json' } ) ,
1156+ } ) ;
1157+ if ( ! rsp . ok ) {
1158+ if ( rsp . status === 404 ) {
1159+ Logger . warn (
1160+ scope ,
1161+ `${ rsp . status } (${ rsp . statusText } ): Failed to get prompt template '${ templateId } ' (${ url } )` ,
1162+ ) ;
1163+ return template ;
11511164 }
11521165
1153- interface PromptResponse {
1154- data : {
1155- id : string ;
1156- template : string ;
1157- variables : string [ ] ;
1158- } ;
1159- error ?: null ;
1160- }
1166+ if ( rsp . status === 401 ) throw new AuthenticationRequiredError ( ) ;
1167+ throw new Error (
1168+ `${ rsp . status } (${ rsp . statusText } ): Failed to get prompt template '${ templateId } ' (${ url } )` ,
1169+ ) ;
1170+ }
11611171
1162- const result : PromptResponse = ( await rsp . json ( ) ) as PromptResponse ;
1163- if ( result . error != null ) {
1164- throw new Error ( `Getting prompt template ( ${ url } ) failed: ${ String ( result . error ) } ` ) ;
1165- }
1172+ interface PromptResponse {
1173+ data : { id : string ; template : string ; variables : string [ ] } ;
1174+ error ?: null ;
1175+ }
11661176
1167- return {
1168- id : result . data . id ,
1169- template : result . data . template ,
1170- variables : result . data . variables ,
1171- } ;
1172- } ) ;
1177+ const result : PromptResponse = ( await rsp . json ( ) ) as PromptResponse ;
1178+ if ( result . error != null ) {
1179+ throw new Error ( `Failed to get prompt template '${ templateId } ' (${ url } ). ${ String ( result . error ) } ` ) ;
1180+ }
1181+
1182+ return {
1183+ id : result . data . id as PromptTemplateId < T > ,
1184+ template : result . data . template ,
1185+ variables : result . data . variables as ( keyof PromptTemplateContext < T > ) [ ] ,
1186+ } satisfies PromptTemplate < T > ;
11731187 } catch ( ex ) {
1188+ cancellable . cancel ( ) ;
11741189 if ( ! ( ex instanceof AuthenticationRequiredError ) ) {
11751190 debugger ;
1176- Logger . error ( ex , scope , `Unable to get prompt template for ' ${ template } '` ) ;
1191+ Logger . error ( ex , scope , String ( ex ) ) ;
11771192 }
1193+ return template ;
11781194 }
1179- }
1180-
1181- return getLocalPromptTemplate ( template , model ) ;
1195+ } ) ;
11821196 }
11831197
11841198 async reset ( all ?: boolean ) : Promise < void > {
@@ -1292,32 +1306,42 @@ async function showConfirmAIProviderToS(storage: Storage): Promise<boolean> {
12921306
12931307function parseSummarizeResult ( result : string ) : NonNullable < AISummarizeResult [ 'parsed' ] > {
12941308 result = result . trim ( ) ;
1295- let summary = result . match ( / < s u m m a r y > \s ? ( [ \s \S ] * ?) \s ? ( < \/ s u m m a r y > | $ ) / ) ?. [ 1 ] ?. trim ( ) ?? '' ;
1296- let body = result . match ( / < b o d y > \s ? ( [ \s \S ] * ?) \s ? ( < \/ b o d y > | $ ) / ) ?. [ 1 ] ?. trim ( ) ?? '' ;
1309+ const summary = result . match ( / < s u m m a r y > ( [ \s \S ] * ?) (?: < \/ s u m m a r y > | $ ) / ) ?. [ 1 ] ?. trim ( ) ?? undefined ;
1310+ if ( summary != null ) {
1311+ result = result . replace ( / < s u m m a r y > [ \s \S ] * ?(?: < \/ s u m m a r y > | $ ) / , '' ) . trim ( ) ;
1312+ }
1313+
1314+ let body = result . match ( / < b o d y > ( [ \s \S ] * ?) (?: < \/ b o d y > | $ ) / ) ?. [ 1 ] ?. trim ( ) ?? undefined ;
1315+ if ( body != null ) {
1316+ result = result . replace ( / < b o d y > [ \s \S ] * ?(?: < \/ b o d y > | $ ) / , '' ) . trim ( ) ;
1317+ }
1318+
1319+ // Check for self-closing body tag
1320+ if ( body == null && result . includes ( '<body/>' ) ) {
1321+ body = '' ;
1322+ }
1323+
1324+ // If both tags are present, return them
1325+ if ( summary != null && body != null ) return { summary : summary , body : body } ;
12971326
12981327 // If both tags are missing, split the result
1299- if ( ! summary && ! body ) {
1300- return splitMessageIntoSummaryAndBody ( result ) ;
1328+ if ( summary == null && body == null ) return splitMessageIntoSummaryAndBody ( result ) ;
1329+
1330+ // If only summary tag is present, use any remaining text as the body
1331+ if ( summary && body == null ) {
1332+ return result ? { summary : summary , body : result } : splitMessageIntoSummaryAndBody ( summary ) ;
13011333 }
13021334
1303- if ( summary && ! body ) {
1304- // If only summary tag is present, use the remaining text as the body
1305- body = result . replace ( / < s u m m a r y > [ \s \S ] * ?< \/ s u m m a r y > / , '' ) ?. trim ( ) ?? '' ;
1306- if ( ! body ) {
1307- return splitMessageIntoSummaryAndBody ( summary ) ;
1308- }
1309- } else if ( ! summary && body ) {
1310- // If only body tag is present, use the remaining text as the summary
1311- summary = result . replace ( / < b o d y > [ \s \S ] * ?< \/ b o d y > / , '' ) . trim ( ) ?? '' ;
1312- if ( ! summary ) {
1313- return splitMessageIntoSummaryAndBody ( body ) ;
1314- }
1335+ // If only body tag is present, use the remaining text as the summary
1336+ if ( summary == null && body ) {
1337+ return result ? { summary : result , body : body } : splitMessageIntoSummaryAndBody ( body ) ;
13151338 }
13161339
1317- return { summary : summary , body : body } ;
1340+ return { summary : summary ?? '' , body : body ?? '' } ;
13181341}
13191342
13201343function splitMessageIntoSummaryAndBody ( message : string ) : NonNullable < AISummarizeResult [ 'parsed' ] > {
1344+ message = message . replace ( / ` ` ` ( [ \s \S ] * ?) ` ` ` / , '$1' ) . trim ( ) ;
13211345 const index = message . indexOf ( '\n' ) ;
13221346 if ( index === - 1 ) return { summary : message , body : '' } ;
13231347
0 commit comments