@@ -34,6 +34,7 @@ import { IWorkspaceService } from '../../../platform/workspace/common/workspaceS
34
34
import { raceFilter } from '../../../util/common/async' ;
35
35
import * as errors from '../../../util/common/errors' ;
36
36
import { Result } from '../../../util/common/result' ;
37
+ import { TokenizerType } from '../../../util/common/tokenizer' ;
37
38
import { createTracer , ITracer } from '../../../util/common/tracing' ;
38
39
import { AsyncIterableObject , DeferredPromise , raceTimeout , timeout } from '../../../util/vs/base/common/async' ;
39
40
import { CancellationToken } from '../../../util/vs/base/common/cancellation' ;
@@ -287,6 +288,9 @@ export class XtabProvider implements IStatelessNextEditProvider {
287
288
}
288
289
289
290
const promptPieces = new PromptPieces (
291
+ currentDocument ,
292
+ editWindowLinesRange ,
293
+ areaAroundEditWindowLinesRange ,
290
294
activeDocument ,
291
295
request . xtabEditHistory ,
292
296
taggedCurrentFileContent ,
@@ -332,6 +336,7 @@ export class XtabProvider implements IStatelessNextEditProvider {
332
336
cursorOriginalLinesOffset ,
333
337
cursorLineOffset ,
334
338
editWindowLinesRange ,
339
+ promptPieces ,
335
340
prediction ,
336
341
{
337
342
shouldRemoveCursorTagFromResponse,
@@ -351,7 +356,7 @@ export class XtabProvider implements IStatelessNextEditProvider {
351
356
currentDocument : CurrentDocument ,
352
357
editWindowLinesRange : OffsetRange ,
353
358
areaAroundEditWindowLinesRange : OffsetRange ,
354
- promptOptions : ModelConfig ,
359
+ promptOptions : xtabPromptOptions . PromptOptions ,
355
360
computeTokens : ( s : string ) => number ,
356
361
opts : {
357
362
includeLineNumbers : boolean ;
@@ -533,6 +538,7 @@ export class XtabProvider implements IStatelessNextEditProvider {
533
538
cursorOriginalLinesOffset : number ,
534
539
cursorLineOffset : number , // cursor offset within the line it's in; 1-based
535
540
editWindowLineRange : OffsetRange ,
541
+ promptPieces : PromptPieces ,
536
542
prediction : Prediction | undefined ,
537
543
opts : {
538
544
promptingStrategy : xtabPromptOptions . PromptingStrategy | undefined ;
@@ -679,7 +685,7 @@ export class XtabProvider implements IStatelessNextEditProvider {
679
685
const trimmedLines = firstLine . value . trim ( ) ;
680
686
681
687
if ( trimmedLines === ResponseTags . NO_CHANGE . start ) {
682
- this . pushNoSuggestionsOrRetry ( request , editWindow , pushEdit , delaySession , logContext , cancellationToken , telemetryBuilder , opts . retryState ) ;
688
+ await this . pushNoSuggestionsOrRetry ( request , editWindow , promptPieces , pushEdit , delaySession , logContext , cancellationToken , telemetryBuilder , opts . retryState ) ;
683
689
return ;
684
690
}
685
691
@@ -807,7 +813,7 @@ export class XtabProvider implements IStatelessNextEditProvider {
807
813
if ( hadEdits ) {
808
814
pushEdit ( Result . error ( new NoNextEditReason . NoSuggestions ( request . documentBeforeEdits , editWindow ) ) ) ;
809
815
} else {
810
- this . pushNoSuggestionsOrRetry ( request , editWindow , pushEdit , delaySession , logContext , cancellationToken , telemetryBuilder , opts . retryState ) ;
816
+ await this . pushNoSuggestionsOrRetry ( request , editWindow , promptPieces , pushEdit , delaySession , logContext , cancellationToken , telemetryBuilder , opts . retryState ) ;
811
817
}
812
818
813
819
} catch ( err ) {
@@ -818,9 +824,10 @@ export class XtabProvider implements IStatelessNextEditProvider {
818
824
} ) ( ) ;
819
825
}
820
826
821
- private pushNoSuggestionsOrRetry (
827
+ private async pushNoSuggestionsOrRetry (
822
828
request : StatelessNextEditRequest ,
823
829
editWindow : OffsetRange ,
830
+ promptPieces : PromptPieces ,
824
831
pushEdit : PushEdit ,
825
832
delaySession : DelaySession ,
826
833
logContext : InlineEditRequestLogContext ,
@@ -836,6 +843,17 @@ export class XtabProvider implements IStatelessNextEditProvider {
836
843
return ;
837
844
}
838
845
846
+ // FIXME@ulugbekna : think out how it works with retrying logic
847
+ if ( this . configService . getExperimentBasedConfig ( ConfigKey . Internal . InlineEditsNextCursorPredictionEnabled , this . expService ) ) {
848
+ // FIXME@ulugbekna : possibly convert from 1-based to 0-based
849
+ const nextCursorLine = await this . predictNextCursorPosition ( promptPieces ) ;
850
+ if ( nextCursorLine ) {
851
+ this . tracer . trace ( `Predicted next cursor line: ${ nextCursorLine } ` ) ;
852
+ this . doGetNextEditWithSelection ( request , new Range ( nextCursorLine , 1 , nextCursorLine , 1 ) , pushEdit , delaySession , logContext , cancellationToken , telemetryBuilder , RetryState . NotRetrying ) ;
853
+ return ;
854
+ }
855
+ }
856
+
839
857
pushEdit ( Result . error ( new NoNextEditReason . NoSuggestions ( request . documentBeforeEdits , editWindow ) ) ) ;
840
858
return ;
841
859
}
@@ -996,6 +1014,113 @@ export class XtabProvider implements IStatelessNextEditProvider {
996
1014
return sourcedModelConfig ;
997
1015
}
998
1016
1017
+ private async predictNextCursorPosition ( promptPieces : PromptPieces ) {
1018
+
1019
+ const tracer = this . tracer . sub ( 'predictNextCursorPosition' ) ;
1020
+
1021
+ 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.' ;
1022
+
1023
+ const currentFileContentR = this . constructTaggedFile (
1024
+ promptPieces . currentDocument ,
1025
+ promptPieces . editWindowLinesRange ,
1026
+ promptPieces . areaAroundEditWindowLinesRange ,
1027
+ promptPieces . opts ,
1028
+ XtabProvider . computeTokens ,
1029
+ { includeLineNumbers : true }
1030
+ ) ;
1031
+
1032
+ if ( currentFileContentR . isError ( ) ) {
1033
+ tracer . trace ( `Failed to construct tagged file: ${ currentFileContentR . err } ` ) ;
1034
+ return ;
1035
+ }
1036
+
1037
+ const { taggedCurrentFileR : { taggedCurrentFileContent } , areaAroundCodeToEdit } = currentFileContentR . val ;
1038
+
1039
+ const newPromptPieces = new PromptPieces (
1040
+ promptPieces . currentDocument ,
1041
+ promptPieces . editWindowLinesRange ,
1042
+ promptPieces . areaAroundEditWindowLinesRange ,
1043
+ promptPieces . activeDoc ,
1044
+ promptPieces . xtabHistory ,
1045
+ taggedCurrentFileContent ,
1046
+ areaAroundCodeToEdit ,
1047
+ promptPieces . langCtx ,
1048
+ XtabProvider . computeTokens ,
1049
+ promptPieces . opts ,
1050
+ ) ;
1051
+
1052
+ const userMessage = getUserPrompt ( newPromptPieces ) ;
1053
+
1054
+ const messages = constructMessages ( {
1055
+ systemMsg : systemMessage ,
1056
+ userMsg : userMessage
1057
+ } ) ;
1058
+
1059
+ const modelName = this . configService . getExperimentBasedConfig ( ConfigKey . Internal . InlineEditsNextCursorPredictionModelName , this . expService ) ;
1060
+ if ( modelName === undefined ) {
1061
+ tracer . trace ( 'Model name for cursor prediction is not defined; skipping prediction' ) ;
1062
+ return ;
1063
+ }
1064
+
1065
+ const url = this . configService . getConfig ( ConfigKey . Internal . InlineEditsNextCursorPredictionUrl ) ;
1066
+ const secretKey = this . configService . getConfig ( ConfigKey . Internal . InlineEditsNextCursorPredictionApiKey ) ;
1067
+
1068
+ const endpoint = this . instaService . createInstance ( ChatEndpoint , {
1069
+ id : modelName ,
1070
+ name : 'nes.nextCursorPosition' ,
1071
+ urlOrRequestMetadata : url ,
1072
+ model_picker_enabled : false ,
1073
+ is_chat_default : false ,
1074
+ is_chat_fallback : false ,
1075
+ version : '' ,
1076
+ capabilities : {
1077
+ type : 'chat' ,
1078
+ family : '' ,
1079
+ tokenizer : TokenizerType . CL100K ,
1080
+ limits : undefined ,
1081
+ supports : {
1082
+ parallel_tool_calls : false ,
1083
+ tool_calls : false ,
1084
+ streaming : true ,
1085
+ vision : false ,
1086
+ prediction : false ,
1087
+ thinking : false
1088
+ }
1089
+ } ,
1090
+ } ) ;
1091
+
1092
+ const response = await endpoint . makeChatRequest2 (
1093
+ {
1094
+ messages,
1095
+ debugName : 'nes.nextCursorPosition' ,
1096
+ finishedCb : undefined ,
1097
+ location : ChatLocation . Other ,
1098
+ requestOptions : {
1099
+ secretKey,
1100
+ }
1101
+ } ,
1102
+ CancellationToken . None
1103
+ ) ;
1104
+
1105
+ if ( response . type !== ChatFetchResponseType . Success ) {
1106
+ return ;
1107
+ }
1108
+
1109
+ try {
1110
+ const trimmed = response . value . trim ( ) ;
1111
+ const lineNumber = parseInt ( trimmed , 10 ) ;
1112
+ if ( isNaN ( lineNumber ) || lineNumber < 0 ) {
1113
+ throw new Error ( `parsed line number is NaN or negative: ${ trimmed } ` ) ;
1114
+ }
1115
+
1116
+ return lineNumber ;
1117
+ } catch ( err ) {
1118
+ tracer . trace ( `Failed to parse predicted line number from response '${ response . value } ': ${ err } ` ) ;
1119
+ return undefined ;
1120
+ }
1121
+
1122
+ }
1123
+
999
1124
private determinePromptingStrategy ( ) : xtabPromptOptions . PromptingStrategy | undefined {
1000
1125
const isXtabUnifiedModel = this . configService . getExperimentBasedConfig ( ConfigKey . Internal . InlineEditsXtabUseUnifiedModel , this . expService ) ;
1001
1126
const isCodexV21NesUnified = this . configService . getExperimentBasedConfig ( ConfigKey . Internal . InlineEditsXtabCodexV21NesUnified , this . expService ) ;
0 commit comments