@@ -57,16 +57,21 @@ import { CopilotInteractiveEditorResponse, InteractionOutcome, InteractionOutcom
5757
5858const INLINE_CHAT_EXIT_TOOL_NAME = 'inline_chat_exit' ;
5959
60- interface Result {
60+ interface IInlineChatEditResult {
6161 telemetry : InlineChatTelemetry ;
6262 lastResponse : ChatResponse ;
63+ needsExitTool : boolean ;
64+ }
65+
66+ interface IInlineChatEditStrategy {
67+ executeEdit ( endpoint : IChatEndpoint , conversation : Conversation , request : vscode . ChatRequest , stream : vscode . ChatResponseStream , token : CancellationToken , documentContext : IDocumentContext , chatTelemetry : ChatTelemetryBuilder ) : Promise < IInlineChatEditResult > ;
6368}
6469
6570export class InlineChatIntent implements IIntent {
6671
6772 static readonly ID = Intent . InlineChat ;
6873
69- private static readonly _EDIT_TOOLS = new Set < string > ( [
74+ static readonly _EDIT_TOOLS = new Set < string > ( [
7075 ToolName . ApplyPatch ,
7176 ToolName . EditFile ,
7277 ToolName . ReplaceString ,
@@ -208,11 +213,13 @@ export class InlineChatIntent implements IIntent {
208213 }
209214 }
210215
211- let result : Result ;
216+ let result : IInlineChatEditResult ;
212217 try {
213- result = useToolsForEdit
214- ? await this . _handleRequestWithEditTools ( endpoint , conversation , request , stream , token , documentContext , chatTelemetry )
215- : await this . _handleRequestWithEditHeuristic ( endpoint , conversation , request , stream , token , documentContext , chatTelemetry ) ;
218+ const strategy : IInlineChatEditStrategy = useToolsForEdit
219+ ? this . _instantiationService . createInstance ( InlineChatEditToolsStrategy , this )
220+ : this . _instantiationService . createInstance ( InlineChatEditHeuristicStrategy , this ) ;
221+
222+ result = await strategy . executeEdit ( endpoint , conversation , request , stream , token , documentContext , chatTelemetry ) ;
216223 } catch ( err ) {
217224 this . _logService . error ( err , 'InlineChatIntent: prompt rendering failed' ) ;
218225 return {
@@ -228,6 +235,11 @@ export class InlineChatIntent implements IIntent {
228235 return CanceledResult ;
229236 }
230237
238+ if ( result . needsExitTool ) {
239+ // BAILOUT: when no edits were emitted, invoke the exit tool manually
240+ await this . _toolsService . invokeTool ( INLINE_CHAT_EXIT_TOOL_NAME , { toolInvocationToken : request . toolInvocationToken , input : undefined } , token ) ;
241+ }
242+
231243 // store metadata for telemetry sending
232244 const turn = conversation . getLatestTurn ( ) ;
233245 turn . setMetadata ( new InteractionOutcome ( didSeeAnyEdit ? 'inlineEdit' : 'none' , [ ] ) ) ;
@@ -243,10 +255,6 @@ export class InlineChatIntent implements IIntent {
243255 buildPrompt : ( ) => { throw new Error ( ) ; } ,
244256 } ) ) ;
245257
246- if ( token . isCancellationRequested ) {
247- return CanceledResult ;
248- }
249-
250258 if ( result . lastResponse . type !== ChatFetchResponseType . Success ) {
251259 const details = getErrorDetailsFromChatFetchError ( result . lastResponse , await this . _endpointProvider . getChatEndpoint ( 'copilot-base' ) , ( await this . _authenticationService . getCopilotToken ( ) ) . copilotPlan ) ;
252260 return {
@@ -260,9 +268,25 @@ export class InlineChatIntent implements IIntent {
260268 return { } ;
261269 }
262270
263- // --- NEW world: edit tools
271+ invoke ( ) : Promise < never > {
272+ throw new TypeError ( ) ;
273+ }
274+ }
275+
276+ class InlineChatEditToolsStrategy implements IInlineChatEditStrategy {
264277
265- private async _handleRequestWithEditTools ( endpoint : IChatEndpoint , conversation : Conversation , request : vscode . ChatRequest , stream : vscode . ChatResponseStream , token : CancellationToken , documentContext : IDocumentContext , chatTelemetry : ChatTelemetryBuilder ) : Promise < Result > {
278+ readonly id = InlineChatIntent . ID ;
279+ readonly locations = [ ChatLocation . Editor ] ;
280+ readonly description = '' ;
281+
282+ constructor (
283+ private readonly _intent : InlineChatIntent ,
284+ @IInstantiationService private readonly _instantiationService : IInstantiationService ,
285+ @ILogService private readonly _logService : ILogService ,
286+ @IToolsService private readonly _toolsService : IToolsService ,
287+ ) { }
288+
289+ async executeEdit ( endpoint : IChatEndpoint , conversation : Conversation , request : vscode . ChatRequest , stream : vscode . ChatResponseStream , token : CancellationToken , documentContext : IDocumentContext , chatTelemetry : ChatTelemetryBuilder ) : Promise < IInlineChatEditResult > {
266290 assertType ( request . location2 instanceof ChatRequestEditorData ) ;
267291 assertType ( documentContext ) ;
268292
@@ -286,7 +310,7 @@ export class InlineChatIntent implements IIntent {
286310
287311 const renderResult = await renderer . render ( undefined , token , { trace : true } ) ;
288312
289- telemetry = chatTelemetry . makeRequest ( this , ChatLocation . Editor , conversation , renderResult . messages , renderResult . tokenCount , renderResult . references , endpoint , [ ] , availableTools . length ) ;
313+ telemetry = chatTelemetry . makeRequest ( this . _intent , ChatLocation . Editor , conversation , renderResult . messages , renderResult . tokenCount , renderResult . references , endpoint , [ ] , availableTools . length ) ;
290314
291315 stream = ChatResponseStreamImpl . spy ( stream , part => {
292316 if ( part instanceof ChatResponseTextEditPart ) {
@@ -317,8 +341,7 @@ export class InlineChatIntent implements IIntent {
317341 }
318342
319343 if ( result . toolCalls . length === 0 ) {
320- // BAILOUT: when no tools have been used, invoke the exit tool manually
321- await this . _toolsService . invokeTool ( INLINE_CHAT_EXIT_TOOL_NAME , { toolInvocationToken : request . toolInvocationToken , input : undefined } , token ) ;
344+ // BAILOUT: when no tools have been used
322345 break ;
323346 }
324347
@@ -329,15 +352,15 @@ export class InlineChatIntent implements IIntent {
329352
330353 if ( editAttempts . push ( ...result . failedEdits ) > 5 ) {
331354 // TOO MANY FAILED ATTEMPTS
332- this . _logService . warn ( `Aborting inline chat edit: too many failed edit attempts` ) ;
355+ this . _logService . error ( `Aborting inline chat edit: too many failed edit attempts` ) ;
333356 break ;
334357 }
335358 }
336359
337360 telemetry . sendToolCallingTelemetry ( toolCallRounds , availableTools , token . isCancellationRequested ? 'cancelled' : lastResponse . type ) ;
338361
339-
340- return { lastResponse, telemetry } ;
362+ const needsExitTool = toolCallRounds . length === 0 || ( toolCallRounds . length > 0 && toolCallRounds [ toolCallRounds . length - 1 ] . toolCalls . length === 0 ) ;
363+ return { lastResponse, telemetry, needsExitTool } ;
341364 }
342365
343366 private async _makeRequestAndRunTools ( endpoint : IChatEndpoint , request : vscode . ChatRequest , stream : vscode . ChatResponseStream , messages : Raw . ChatMessage [ ] , inlineChatTools : vscode . LanguageModelToolInformation [ ] , telemetry : InlineChatTelemetry , token : CancellationToken ) {
@@ -372,9 +395,9 @@ export class InlineChatIntent implements IIntent {
372395 telemetryProperties : {
373396 messageId : telemetry . telemetryMessageId ,
374397 conversationId : telemetry . sessionId ,
375- messageSource : this . id
398+ messageSource : this . _intent . id
376399 } ,
377- finishedCb : async ( text , index , delta ) => {
400+ finishedCb : async ( _text , _index , delta ) => {
378401
379402 telemetry . markReceivedToken ( ) ;
380403
@@ -471,10 +494,20 @@ export class InlineChatIntent implements IIntent {
471494
472495 return [ exitTool , ...editTools ] ;
473496 }
497+ }
474498
475- // ---- NEW world: edit prompt
499+ class InlineChatEditHeuristicStrategy implements IInlineChatEditStrategy {
476500
477- private async _handleRequestWithEditHeuristic ( endpoint : IChatEndpoint , conversation : Conversation , request : vscode . ChatRequest , stream : vscode . ChatResponseStream , token : CancellationToken , documentContext : IDocumentContext , chatTelemetry : ChatTelemetryBuilder ) : Promise < Result > {
501+ readonly id = InlineChatIntent . ID ;
502+ readonly locations = [ ChatLocation . Editor ] ;
503+ readonly description = '' ;
504+
505+ constructor (
506+ private readonly _intent : InlineChatIntent ,
507+ @IInstantiationService private readonly _instantiationService : IInstantiationService ,
508+ ) { }
509+
510+ async executeEdit ( endpoint : IChatEndpoint , conversation : Conversation , request : vscode . ChatRequest , stream : vscode . ChatResponseStream , token : CancellationToken , documentContext : IDocumentContext , chatTelemetry : ChatTelemetryBuilder ) : Promise < IInlineChatEditResult > {
478511
479512 assertType ( request . location2 instanceof ChatRequestEditorData ) ;
480513
@@ -494,7 +527,7 @@ export class InlineChatIntent implements IIntent {
494527 const replyInterpreter = renderResult . metadata . get ( ReplyInterpreterMetaData ) ?. replyInterpreter ?? new NoopReplyInterpreter ( ) ;
495528 const telemetryData = renderResult . metadata . getAll ( TelemetryData ) ;
496529
497- const telemetry = chatTelemetry . makeRequest ( this , ChatLocation . Editor , conversation , renderResult . messages , renderResult . tokenCount , renderResult . references , endpoint , telemetryData , 0 ) ;
530+ const telemetry = chatTelemetry . makeRequest ( this . _intent , ChatLocation . Editor , conversation , renderResult . messages , renderResult . tokenCount , renderResult . references , endpoint , telemetryData , 0 ) ;
498531
499532 stream = ChatResponseStreamImpl . spy ( stream , part => {
500533 if ( part instanceof ChatResponseTextEditPart ) {
@@ -523,7 +556,7 @@ export class InlineChatIntent implements IIntent {
523556 telemetryProperties : {
524557 messageId : telemetry . telemetryMessageId ,
525558 conversationId : telemetry . sessionId ,
526- messageSource : this . id
559+ messageSource : this . _intent . id
527560 } ,
528561 requestOptions : {
529562 stream : true ,
@@ -543,19 +576,10 @@ export class InlineChatIntent implements IIntent {
543576 const responseText = fetchResult . type === ChatFetchResponseType . Success ? fetchResult . value : '' ;
544577 telemetry . sendTelemetry (
545578 fetchResult . requestId , fetchResult . type , responseText ,
546- new InteractionOutcome ( 'inlineEdit' , [ ] ) ,
579+ new InteractionOutcome ( telemetry . editCount > 0 ? 'inlineEdit' : 'none ', [ ] ) ,
547580 [ ]
548581 ) ;
549582
550- if ( telemetry . editCount === 0 ) {
551- // BAILOUT: when no edits were emitted, invoke the exit tool manually
552- await this . _toolsService . invokeTool ( INLINE_CHAT_EXIT_TOOL_NAME , { toolInvocationToken : request . toolInvocationToken , input : undefined } , token ) ;
553- }
554-
555- return { lastResponse : fetchResult , telemetry } ;
556- }
557-
558- invoke ( ) : Promise < never > {
559- throw new TypeError ( ) ;
583+ return { lastResponse : fetchResult , telemetry, needsExitTool : telemetry . editCount === 0 } ;
560584 }
561585}
0 commit comments