1
1
import { Configuration , CreateCompletionRequestPrompt , CreateCompletionResponse , OpenAIApi } from 'openai' ;
2
2
import { CancellationToken , InlineCompletionContext , InlineCompletionItem , InlineCompletionItemProvider , InlineCompletionList , Position , ProviderResult , Range , TextDocument , workspace , StatusBarItem } from 'vscode' ;
3
3
import { AxiosResponse } from 'axios' ;
4
- import { nextId } from './Uuid ' ;
4
+ import { debounce } from './utilities ' ;
5
5
6
6
export class FauxpilotCompletionProvider implements InlineCompletionItemProvider {
7
- cachedPrompts : Map < string , number > = new Map < string , number > ( ) ;
8
-
7
+ // TODO: Make dynamic
8
+ // AFAIK VSCode creates provider once. As such, token will never be updated
9
9
private configuration : Configuration = new Configuration ( {
10
10
apiKey : workspace . getConfiguration ( 'fauxpilot' ) . get ( "token" )
11
11
} ) ;
12
+ // TODO: Make dynamic
13
+ // AFAIK VSCode creates provider once. As such, server address will never be updated
12
14
private openai : OpenAIApi = new OpenAIApi ( this . configuration , `${ workspace . getConfiguration ( 'fauxpilot' ) . get ( "server" ) } /${ workspace . getConfiguration ( 'fauxpilot' ) . get ( "engine" ) } ` ) ;
13
- private requestStatus : string = "done" ;
14
- private statusBar : StatusBarItem ;
15
-
16
- constructor ( statusBar : StatusBarItem ) {
17
- this . statusBar = statusBar ;
15
+ private readonly debouncedApiCall : any = debounce (
16
+ // TODO: Extract to method.
17
+ // I absolutely forgot how to handle 'this' context in JS. Simple extraction makes this
18
+ // undefined. How to bind it?
19
+ ( prompt : string , position : Position ) => {
20
+ return new Promise ( resolve => {
21
+ console . debug ( "Requesting completion after debounce period" ) ;
22
+ this . statusBar . tooltip = "Fauxpilot - Working" ;
23
+ this . statusBar . text = "$(loading~spin)" ;
24
+ this . callOpenAi ( prompt ) . then ( ( response ) => {
25
+ this . statusBar . text = "$(light-bulb)" ;
26
+ resolve ( this . toInlineCompletions ( response . data , position ) ) ;
27
+ } ) . catch ( ( error ) => {
28
+ this . statusBar . text = "$(alert)" ;
29
+ console . error ( error ) ;
30
+ resolve ( ( [ ] as InlineCompletionItem [ ] ) ) ;
31
+ } ) ;
32
+ } ) ;
33
+ } , { timeout : workspace . getConfiguration ( 'fauxpilot' ) . get ( "suggestionDelay" ) as number , defaultReturn : [ ] } ) ;
34
+
35
+ constructor ( private statusBar : StatusBarItem , private testCompletion ?: any ) {
18
36
}
19
37
20
38
//@ts -ignore
@@ -26,46 +44,14 @@ export class FauxpilotCompletionProvider implements InlineCompletionItemProvider
26
44
}
27
45
28
46
const prompt = this . getPrompt ( document , position ) ;
29
- console . debug ( "Requesting completion for prompt" , prompt ) ;
30
47
31
48
if ( this . isNil ( prompt ) ) {
32
49
console . debug ( "Prompt is empty, skipping" ) ;
33
50
return Promise . resolve ( ( [ ] as InlineCompletionItem [ ] ) ) ;
34
51
}
35
52
36
- const currentTimestamp = Date . now ( ) ;
37
- const currentId = nextId ( ) ;
38
- this . cachedPrompts . set ( currentId , currentTimestamp ) ;
39
-
40
- // check there is no newer request util this.request_status is done
41
- while ( this . requestStatus === "pending" ) {
42
- await this . sleep ( 200 ) ;
43
- console . debug ( "current id = " , currentId , " request status = " , this . requestStatus ) ;
44
- if ( this . newestTimestamp ( ) > currentTimestamp ) {
45
- console . debug ( "newest timestamp=" , this . newestTimestamp ( ) , "current timestamp=" , currentTimestamp ) ;
46
- console . debug ( "Newer request is pending, skipping" ) ;
47
- this . cachedPrompts . delete ( currentId ) ;
48
- return Promise . resolve ( ( [ ] as InlineCompletionItem [ ] ) ) ;
49
- }
50
- }
51
-
52
- console . debug ( "current id = " , currentId , "set request status to pending" ) ;
53
- this . requestStatus = "pending" ;
54
- this . statusBar . tooltip = "Fauxpilot - Working" ;
55
- this . statusBar . text = "$(loading~spin)" ;
56
-
57
- return this . callOpenAi ( prompt as String ) . then ( ( response ) => {
58
- this . statusBar . text = "$(light-bulb)" ;
59
- return this . toInlineCompletions ( response . data , position ) ;
60
- } ) . catch ( ( error ) => {
61
- console . error ( error ) ;
62
- this . statusBar . text = "$(alert)" ;
63
- return ( [ ] as InlineCompletionItem [ ] ) ;
64
- } ) . finally ( ( ) => {
65
- console . debug ( "current id = " , currentId , "set request status to done" ) ;
66
- this . requestStatus = "done" ;
67
- this . cachedPrompts . delete ( currentId ) ;
68
- } ) ;
53
+ console . debug ( "Requesting completion for prompt" , prompt ) ;
54
+ return this . debouncedApiCall ( prompt , position ) ;
69
55
}
70
56
71
57
private getPrompt ( document : TextDocument , position : Position ) : String | undefined {
@@ -80,21 +66,16 @@ export class FauxpilotCompletionProvider implements InlineCompletionItemProvider
80
66
return value === undefined || value === null || value . length === 0 ;
81
67
}
82
68
83
- private newestTimestamp ( ) {
84
- return Array . from ( this . cachedPrompts . values ( ) ) . reduce ( ( a , b ) => Math . max ( a , b ) ) ;
85
- }
86
-
87
- private sleep ( milliseconds : number ) {
88
- return new Promise ( r => setTimeout ( r , milliseconds ) ) ;
89
- } ;
90
-
91
69
private callOpenAi ( prompt : String ) : Promise < AxiosResponse < CreateCompletionResponse , any > > {
92
70
console . debug ( "Calling OpenAi" , prompt ) ;
93
71
94
- //check if inline completion is enabled
72
+ // FIXME: I do not understand my own comment below. To verify
73
+ // check if inline completion is enabled
95
74
const stopWords = workspace . getConfiguration ( 'fauxpilot' ) . get ( "inlineCompletion" ) ? [ "\n" ] : [ ] ;
96
75
console . debug ( "Calling OpenAi with stop words = " , stopWords ) ;
97
- return this . openai . createCompletion ( {
76
+ // FIXME: how to mock in mocha?
77
+ // Current implementation works by injecting alternative provider via constructor
78
+ return ( this . testCompletion ?? this . openai ) . createCompletion ( {
98
79
model : workspace . getConfiguration ( 'fauxpilot' ) . get ( "model" ) ?? "<<UNSET>>" ,
99
80
prompt : prompt as CreateCompletionRequestPrompt ,
100
81
/* eslint-disable-next-line @typescript-eslint/naming-convention */
0 commit comments