3
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
4
*--------------------------------------------------------------------------------------------*/
5
5
6
+ import { RequestType } from '@vscode/copilot-api' ;
6
7
import { Raw } from '@vscode/prompt-tsx' ;
7
8
import { ChatCompletionContentPartKind } from '@vscode/prompt-tsx/dist/base/output/rawTypes' ;
8
9
import { FetchStreamSource } from '../../../platform/chat/common/chatMLFetcher' ;
@@ -34,6 +35,7 @@ import { IWorkspaceService } from '../../../platform/workspace/common/workspaceS
34
35
import { raceFilter } from '../../../util/common/async' ;
35
36
import * as errors from '../../../util/common/errors' ;
36
37
import { Result } from '../../../util/common/result' ;
38
+ import { TokenizerType } from '../../../util/common/tokenizer' ;
37
39
import { createTracer , ITracer } from '../../../util/common/tracing' ;
38
40
import { AsyncIterableObject , DeferredPromise , raceTimeout , timeout } from '../../../util/vs/base/common/async' ;
39
41
import { CancellationToken } from '../../../util/vs/base/common/cancellation' ;
@@ -287,6 +289,9 @@ export class XtabProvider implements IStatelessNextEditProvider {
287
289
}
288
290
289
291
const promptPieces = new PromptPieces (
292
+ currentDocument ,
293
+ editWindowLinesRange ,
294
+ areaAroundEditWindowLinesRange ,
290
295
activeDocument ,
291
296
request . xtabEditHistory ,
292
297
taggedCurrentFileContent ,
@@ -332,6 +337,7 @@ export class XtabProvider implements IStatelessNextEditProvider {
332
337
cursorOriginalLinesOffset ,
333
338
cursorLineOffset ,
334
339
editWindowLinesRange ,
340
+ promptPieces ,
335
341
prediction ,
336
342
{
337
343
shouldRemoveCursorTagFromResponse,
@@ -351,7 +357,7 @@ export class XtabProvider implements IStatelessNextEditProvider {
351
357
currentDocument : CurrentDocument ,
352
358
editWindowLinesRange : OffsetRange ,
353
359
areaAroundEditWindowLinesRange : OffsetRange ,
354
- promptOptions : ModelConfig ,
360
+ promptOptions : xtabPromptOptions . PromptOptions ,
355
361
computeTokens : ( s : string ) => number ,
356
362
opts : {
357
363
includeLineNumbers : boolean ;
@@ -533,6 +539,7 @@ export class XtabProvider implements IStatelessNextEditProvider {
533
539
cursorOriginalLinesOffset : number ,
534
540
cursorLineOffset : number , // cursor offset within the line it's in; 1-based
535
541
editWindowLineRange : OffsetRange ,
542
+ promptPieces : PromptPieces ,
536
543
prediction : Prediction | undefined ,
537
544
opts : {
538
545
promptingStrategy : xtabPromptOptions . PromptingStrategy | undefined ;
@@ -682,7 +689,7 @@ export class XtabProvider implements IStatelessNextEditProvider {
682
689
const trimmedLines = firstLine . value . trim ( ) ;
683
690
684
691
if ( trimmedLines === ResponseTags . NO_CHANGE . start ) {
685
- this . pushNoSuggestionsOrRetry ( request , editWindow , pushEdit , delaySession , logContext , cancellationToken , telemetryBuilder , opts . retryState ) ;
692
+ await this . pushNoSuggestionsOrRetry ( request , editWindow , promptPieces , pushEdit , delaySession , logContext , cancellationToken , telemetryBuilder , opts . retryState ) ;
686
693
return ;
687
694
}
688
695
@@ -810,7 +817,7 @@ export class XtabProvider implements IStatelessNextEditProvider {
810
817
if ( hadEdits ) {
811
818
pushEdit ( Result . error ( new NoNextEditReason . NoSuggestions ( request . documentBeforeEdits , editWindow ) ) ) ;
812
819
} else {
813
- this . pushNoSuggestionsOrRetry ( request , editWindow , pushEdit , delaySession , logContext , cancellationToken , telemetryBuilder , opts . retryState ) ;
820
+ await this . pushNoSuggestionsOrRetry ( request , editWindow , promptPieces , pushEdit , delaySession , logContext , cancellationToken , telemetryBuilder , opts . retryState ) ;
814
821
}
815
822
816
823
} catch ( err ) {
@@ -821,9 +828,10 @@ export class XtabProvider implements IStatelessNextEditProvider {
821
828
} ) ( ) ;
822
829
}
823
830
824
- private pushNoSuggestionsOrRetry (
831
+ private async pushNoSuggestionsOrRetry (
825
832
request : StatelessNextEditRequest ,
826
833
editWindow : OffsetRange ,
834
+ promptPieces : PromptPieces ,
827
835
pushEdit : PushEdit ,
828
836
delaySession : DelaySession ,
829
837
logContext : InlineEditRequestLogContext ,
@@ -839,6 +847,17 @@ export class XtabProvider implements IStatelessNextEditProvider {
839
847
return ;
840
848
}
841
849
850
+ // FIXME@ulugbekna : think out how it works with retrying logic
851
+ if ( this . configService . getExperimentBasedConfig ( ConfigKey . Internal . InlineEditsNextCursorPredictionEnabled , this . expService ) ) {
852
+ // FIXME@ulugbekna : possibly convert from 1-based to 0-based
853
+ const nextCursorLine = await this . predictNextCursorPosition ( promptPieces ) ;
854
+ if ( nextCursorLine ) {
855
+ this . tracer . trace ( `Predicted next cursor line: ${ nextCursorLine } ` ) ;
856
+ this . doGetNextEditWithSelection ( request , new Range ( nextCursorLine , 1 , nextCursorLine , 1 ) , pushEdit , delaySession , logContext , cancellationToken , telemetryBuilder , RetryState . NotRetrying ) ;
857
+ return ;
858
+ }
859
+ }
860
+
842
861
pushEdit ( Result . error ( new NoNextEditReason . NoSuggestions ( request . documentBeforeEdits , editWindow ) ) ) ;
843
862
return ;
844
863
}
@@ -980,7 +999,8 @@ export class XtabProvider implements IStatelessNextEditProvider {
980
999
maxTokens : this . configService . getExperimentBasedConfig ( ConfigKey . Internal . InlineEditsXtabDiffMaxTokens , this . expService ) ,
981
1000
onlyForDocsInPrompt : this . configService . getExperimentBasedConfig ( ConfigKey . Internal . InlineEditsXtabDiffOnlyForDocsInPrompt , this . expService ) ,
982
1001
useRelativePaths : this . configService . getExperimentBasedConfig ( ConfigKey . Internal . InlineEditsXtabDiffUseRelativePaths , this . expService ) ,
983
- }
1002
+ } ,
1003
+ includePostScript : true ,
984
1004
} ;
985
1005
986
1006
const overridingModelConfig = this . configService . getConfig ( ConfigKey . Internal . InlineEditsXtabProviderModelConfiguration ) ;
@@ -999,6 +1019,120 @@ export class XtabProvider implements IStatelessNextEditProvider {
999
1019
return sourcedModelConfig ;
1000
1020
}
1001
1021
1022
+ private async predictNextCursorPosition ( promptPieces : PromptPieces ) {
1023
+
1024
+ const tracer = this . tracer . sub ( 'predictNextCursorPosition' ) ;
1025
+
1026
+ const systemMessage = 'Your task is to predict the next line number in the current file where the developer is most likely to make their next edit, using the provided context.' ;
1027
+
1028
+ const currentFileContentR = this . constructTaggedFile (
1029
+ promptPieces . currentDocument ,
1030
+ promptPieces . editWindowLinesRange ,
1031
+ promptPieces . areaAroundEditWindowLinesRange ,
1032
+ promptPieces . opts ,
1033
+ XtabProvider . computeTokens ,
1034
+ { includeLineNumbers : true }
1035
+ ) ;
1036
+
1037
+ if ( currentFileContentR . isError ( ) ) {
1038
+ tracer . trace ( `Failed to construct tagged file: ${ currentFileContentR . err } ` ) ;
1039
+ return ;
1040
+ }
1041
+
1042
+ const { taggedCurrentFileR : { taggedCurrentFileContent } , areaAroundCodeToEdit } = currentFileContentR . val ;
1043
+
1044
+ const newPromptPieces = new PromptPieces (
1045
+ promptPieces . currentDocument ,
1046
+ promptPieces . editWindowLinesRange ,
1047
+ promptPieces . areaAroundEditWindowLinesRange ,
1048
+ promptPieces . activeDoc ,
1049
+ promptPieces . xtabHistory ,
1050
+ taggedCurrentFileContent ,
1051
+ areaAroundCodeToEdit ,
1052
+ promptPieces . langCtx ,
1053
+ XtabProvider . computeTokens ,
1054
+ {
1055
+ ...promptPieces . opts ,
1056
+ includePostScript : false ,
1057
+ }
1058
+ ) ;
1059
+
1060
+ const userMessage = getUserPrompt ( newPromptPieces ) ;
1061
+
1062
+ const messages = constructMessages ( {
1063
+ systemMsg : systemMessage ,
1064
+ userMsg : userMessage
1065
+ } ) ;
1066
+
1067
+ const modelName = this . configService . getExperimentBasedConfig ( ConfigKey . Internal . InlineEditsNextCursorPredictionModelName , this . expService ) ;
1068
+ if ( modelName === undefined ) {
1069
+ tracer . trace ( 'Model name for cursor prediction is not defined; skipping prediction' ) ;
1070
+ return ;
1071
+ }
1072
+
1073
+ const url = this . configService . getConfig ( ConfigKey . Internal . InlineEditsNextCursorPredictionUrl ) ;
1074
+ const secretKey = this . configService . getConfig ( ConfigKey . Internal . InlineEditsNextCursorPredictionApiKey ) ;
1075
+
1076
+ const endpoint = this . instaService . createInstance ( ChatEndpoint , {
1077
+ id : modelName ,
1078
+ name : 'nes.nextCursorPosition' ,
1079
+ urlOrRequestMetadata : url ? url : { type : RequestType . ProxyChatCompletions } ,
1080
+ model_picker_enabled : false ,
1081
+ is_chat_default : false ,
1082
+ is_chat_fallback : false ,
1083
+ version : '' ,
1084
+ capabilities : {
1085
+ type : 'chat' ,
1086
+ family : '' ,
1087
+ tokenizer : TokenizerType . CL100K ,
1088
+ limits : undefined ,
1089
+ supports : {
1090
+ parallel_tool_calls : false ,
1091
+ tool_calls : false ,
1092
+ streaming : true ,
1093
+ vision : false ,
1094
+ prediction : false ,
1095
+ thinking : false
1096
+ }
1097
+ } ,
1098
+ } ) ;
1099
+
1100
+ const response = await endpoint . makeChatRequest2 (
1101
+ {
1102
+ messages,
1103
+ debugName : 'nes.nextCursorPosition' ,
1104
+ finishedCb : undefined ,
1105
+ location : ChatLocation . Other ,
1106
+ requestOptions : secretKey ? {
1107
+ secretKey,
1108
+ } : undefined ,
1109
+ } ,
1110
+ CancellationToken . None
1111
+ ) ;
1112
+
1113
+ if ( response . type !== ChatFetchResponseType . Success ) {
1114
+ return ;
1115
+ }
1116
+
1117
+ try {
1118
+ const trimmed = response . value . trim ( ) ;
1119
+ const lineNumber = parseInt ( trimmed , 10 ) ;
1120
+ if ( isNaN ( lineNumber ) || lineNumber < 0 ) {
1121
+ throw new Error ( `parsed line number is NaN or negative: ${ trimmed } ` ) ;
1122
+ }
1123
+ if ( lineNumber > promptPieces . currentDocument . lines . length ) {
1124
+ this . tracer . trace ( `Predicted line number ${ lineNumber } is out of bounds, document has ${ promptPieces . currentDocument . lines . length } lines` ) ;
1125
+ return undefined ;
1126
+ }
1127
+
1128
+ return lineNumber ;
1129
+ } catch ( err ) {
1130
+ tracer . trace ( `Failed to parse predicted line number from response '${ response . value } ': ${ err } ` ) ;
1131
+ return undefined ;
1132
+ }
1133
+
1134
+ }
1135
+
1002
1136
private determinePromptingStrategy ( ) : xtabPromptOptions . PromptingStrategy | undefined {
1003
1137
const isXtabUnifiedModel = this . configService . getExperimentBasedConfig ( ConfigKey . Internal . InlineEditsXtabUseUnifiedModel , this . expService ) ;
1004
1138
const isCodexV21NesUnified = this . configService . getExperimentBasedConfig ( ConfigKey . Internal . InlineEditsXtabCodexV21NesUnified , this . expService ) ;
0 commit comments