1
- import { ControlPlaneClient } from "../control-plane/client.js" ;
2
1
import {
3
- BrowserSerializedContinueConfig ,
4
- ContinueConfig ,
5
- IContextProvider ,
6
- IDE ,
7
- IdeSettings ,
8
- ILLM ,
9
- } from "../index.js" ;
10
- import { GlobalContext } from "../util/GlobalContext.js" ;
11
- import { finalToBrowserConfig } from "./load.js" ;
12
- import ControlPlaneProfileLoader from "./profile/ControlPlaneProfileLoader.js" ;
13
- import { IProfileLoader } from "./profile/IProfileLoader.js" ;
14
- import LocalProfileLoader from "./profile/LocalProfileLoader.js" ;
15
-
16
- export interface ProfileDescription {
17
- title : string ;
18
- id : string ;
19
- }
20
-
21
- // Separately manages saving/reloading each profile
22
- class ProfileLifecycleManager {
23
- private savedConfig : ContinueConfig | undefined ;
24
- private savedBrowserConfig ?: BrowserSerializedContinueConfig ;
25
- private pendingConfigPromise ?: Promise < ContinueConfig > ;
26
-
27
- constructor ( private readonly profileLoader : IProfileLoader ) { }
28
-
29
- get profileId ( ) {
30
- return this . profileLoader . profileId ;
31
- }
32
-
33
- get profileTitle ( ) {
34
- return this . profileLoader . profileTitle ;
35
- }
36
-
37
- get profileDescription ( ) : ProfileDescription {
38
- return {
39
- title : this . profileTitle ,
40
- id : this . profileId ,
41
- } ;
42
- }
43
-
44
- clearConfig ( ) {
45
- this . savedConfig = undefined ;
46
- this . savedBrowserConfig = undefined ;
47
- this . pendingConfigPromise = undefined ;
48
- }
49
-
50
- // Clear saved config and reload
51
- reloadConfig ( ) : Promise < ContinueConfig > {
52
- this . savedConfig = undefined ;
53
- this . savedBrowserConfig = undefined ;
54
- this . pendingConfigPromise = undefined ;
55
-
56
- return this . profileLoader . doLoadConfig ( ) ;
57
- }
58
-
59
- async loadConfig (
60
- additionalContextProviders : IContextProvider [ ] ,
61
- ) : Promise < ContinueConfig > {
62
- // If we already have a config, return it
63
- if ( this . savedConfig ) {
64
- return this . savedConfig ;
65
- } else if ( this . pendingConfigPromise ) {
66
- return this . pendingConfigPromise ;
2
+ BrowserSerializedContinueConfig ,
3
+ ContinueConfig ,
4
+ ContinueRcJson ,
5
+ IContextProvider ,
6
+ IDE ,
7
+ IdeSettings ,
8
+ ILLM ,
9
+ } from "../index.js" ;
10
+ import { Telemetry } from "../util/posthog.js" ;
11
+ import { IConfigHandler } from "./IConfigHandler.js" ;
12
+ import { finalToBrowserConfig , loadFullConfigNode } from "./load.js" ;
13
+
14
+ export class ConfigHandler implements IConfigHandler {
15
+ private savedConfig : ContinueConfig | undefined ;
16
+ private savedBrowserConfig ?: BrowserSerializedContinueConfig ;
17
+ private additionalContextProviders : IContextProvider [ ] = [ ] ;
18
+
19
+ constructor (
20
+ private readonly ide : IDE ,
21
+ private ideSettingsPromise : Promise < IdeSettings > ,
22
+ private readonly writeLog : ( text : string ) => Promise < void > ,
23
+ ) {
24
+ this . ide = ide ;
25
+ this . ideSettingsPromise = ideSettingsPromise ;
26
+ this . writeLog = writeLog ;
27
+ try {
28
+ this . loadConfig ( ) ;
29
+ } catch ( e ) {
30
+ console . error ( "Failed to load config: " , e ) ;
31
+ }
32
+ }
33
+
34
+ updateIdeSettings ( ideSettings : IdeSettings ) {
35
+ this . ideSettingsPromise = Promise . resolve ( ideSettings ) ;
36
+ this . reloadConfig ( ) ;
67
37
}
68
-
69
- // Set pending config promise
70
- this . pendingConfigPromise = new Promise ( async ( resolve , reject ) => {
71
- const newConfig = await this . profileLoader . doLoadConfig ( ) ;
72
-
73
- // Add registered context providers
74
- newConfig . contextProviders = ( newConfig . contextProviders ?? [ ] ) . concat (
75
- additionalContextProviders ,
76
- ) ;
77
-
78
- this . savedConfig = newConfig ;
79
- resolve ( newConfig ) ;
80
- } ) ;
81
-
82
- // Wait for the config promise to resolve
83
- this . savedConfig = await this . pendingConfigPromise ;
84
- this . pendingConfigPromise = undefined ;
85
- return this . savedConfig ;
86
- }
87
-
88
- async getSerializedConfig (
89
- additionalContextProviders : IContextProvider [ ] ,
90
- ) : Promise < BrowserSerializedContinueConfig > {
91
- if ( ! this . savedBrowserConfig ) {
92
- const continueConfig = await this . loadConfig ( additionalContextProviders ) ;
93
- this . savedBrowserConfig = finalToBrowserConfig ( continueConfig ) ;
38
+
39
+ private updateListeners : ( ( newConfig : ContinueConfig ) => void ) [ ] = [ ] ;
40
+ onConfigUpdate ( listener : ( newConfig : ContinueConfig ) => void ) {
41
+ this . updateListeners . push ( listener ) ;
94
42
}
95
- return this . savedBrowserConfig ;
96
- }
97
- }
98
-
99
- export class ConfigHandler {
100
- private readonly globalContext = new GlobalContext ( ) ;
101
- private additionalContextProviders : IContextProvider [ ] = [ ] ;
102
- private profiles : ProfileLifecycleManager [ ] ;
103
- private selectedProfileId : string ;
104
-
105
- // This will be the local profile
106
- private get fallbackProfile ( ) {
107
- return this . profiles [ 0 ] ;
108
- }
109
-
110
- get currentProfile ( ) {
111
- return (
112
- this . profiles . find ( ( p ) => p . profileId === this . selectedProfileId ) ??
113
- this . fallbackProfile
114
- ) ;
115
- }
116
-
117
- get inactiveProfiles ( ) {
118
- return this . profiles . filter ( ( p ) => p . profileId !== this . selectedProfileId ) ;
119
- }
120
-
121
- constructor (
122
- private readonly ide : IDE ,
123
- private ideSettingsPromise : Promise < IdeSettings > ,
124
- private readonly writeLog : ( text : string ) => Promise < void > ,
125
- private readonly controlPlaneClient : ControlPlaneClient ,
126
- ) {
127
- this . ide = ide ;
128
- this . ideSettingsPromise = ideSettingsPromise ;
129
- this . writeLog = writeLog ;
130
-
131
- // Set local profile as default
132
- const localProfileLoader = new LocalProfileLoader (
133
- ide ,
134
- ideSettingsPromise ,
135
- writeLog ,
136
- ) ;
137
- this . profiles = [ new ProfileLifecycleManager ( localProfileLoader ) ] ;
138
- this . selectedProfileId = localProfileLoader . profileId ;
139
-
140
- // Always load local profile immediately in case control plane doesn't load
141
- try {
142
- this . loadConfig ( ) ;
143
- } catch ( e ) {
144
- console . error ( "Failed to load config: " , e ) ;
43
+
44
+ async reloadConfig ( ) {
45
+ this . savedConfig = undefined ;
46
+ this . savedBrowserConfig = undefined ;
47
+ this . _pendingConfigPromise = undefined ;
48
+
49
+ const newConfig = await this . loadConfig ( ) ;
50
+
51
+ for ( const listener of this . updateListeners ) {
52
+ listener ( newConfig ) ;
53
+ }
54
+ }
55
+
56
+ async getSerializedConfig ( ) : Promise < BrowserSerializedContinueConfig > {
57
+ if ( ! this . savedBrowserConfig ) {
58
+ this . savedConfig = await this . loadConfig ( ) ;
59
+ this . savedBrowserConfig = finalToBrowserConfig ( this . savedConfig ) ;
60
+ }
61
+ return this . savedBrowserConfig ;
145
62
}
146
-
147
- // Load control plane profiles
148
- // TODO
149
- // Get the profiles and create their lifecycle managers
150
- this . controlPlaneClient . listWorkspaces ( ) . then ( async ( workspaces ) => {
151
- workspaces . forEach ( ( workspace ) => {
152
- const profileLoader = new ControlPlaneProfileLoader (
153
- workspace . id ,
154
- workspace . name ,
155
- this . controlPlaneClient ,
156
- ide ,
157
- ideSettingsPromise ,
158
- writeLog ,
159
- this . reloadConfig . bind ( this ) ,
63
+
64
+ private _pendingConfigPromise ?: Promise < ContinueConfig > ;
65
+ async loadConfig ( ) : Promise < ContinueConfig > {
66
+ if ( this . savedConfig ) {
67
+ return this . savedConfig ;
68
+ } else if ( this . _pendingConfigPromise ) {
69
+ return this . _pendingConfigPromise ;
70
+ }
71
+
72
+ this . _pendingConfigPromise = new Promise ( async ( resolve , reject ) => {
73
+ let workspaceConfigs : ContinueRcJson [ ] = [ ] ;
74
+ try {
75
+ workspaceConfigs = await this . ide . getWorkspaceConfigs ( ) ;
76
+ } catch ( e ) {
77
+ console . warn ( "Failed to load workspace configs" ) ;
78
+ }
79
+
80
+ const ideInfo = await this . ide . getIdeInfo ( ) ;
81
+ const uniqueId = await this . ide . getUniqueId ( ) ;
82
+ const ideSettings = await this . ideSettingsPromise ;
83
+
84
+ const newConfig = await loadFullConfigNode (
85
+ this . ide ,
86
+ workspaceConfigs ,
87
+ ideSettings ,
88
+ ideInfo . ideType ,
89
+ uniqueId ,
90
+ this . writeLog ,
160
91
) ;
161
- this . profiles . push ( new ProfileLifecycleManager ( profileLoader ) ) ;
162
- } ) ;
163
-
164
- this . notifyProfileListeners (
165
- this . profiles . map ( ( profile ) => profile . profileDescription ) ,
166
- ) ;
167
-
168
- // Check the last selected workspace, and reload if it isn't local
169
- const workspaceId = await this . getWorkspaceId ( ) ;
170
- const lastSelectedWorkspaceIds =
171
- this . globalContext . get ( "lastSelectedProfileForWorkspace" ) ?? { } ;
172
- const selectedWorkspaceId = lastSelectedWorkspaceIds [ workspaceId ] ;
173
- if ( selectedWorkspaceId ) {
174
- this . selectedProfileId = selectedWorkspaceId ;
175
- this . loadConfig ( ) ;
176
- } else {
177
- // Otherwise we stick with local profile, and record choice
178
- lastSelectedWorkspaceIds [ workspaceId ] = this . selectedProfileId ;
179
- this . globalContext . update (
180
- "lastSelectedProfileForWorkspace" ,
181
- lastSelectedWorkspaceIds ,
92
+ newConfig . allowAnonymousTelemetry =
93
+ newConfig . allowAnonymousTelemetry &&
94
+ ( await this . ide . isTelemetryEnabled ( ) ) ;
95
+
96
+ // Setup telemetry only after (and if) we know it is enabled
97
+ await Telemetry . setup (
98
+ newConfig . allowAnonymousTelemetry ?? true ,
99
+ await this . ide . getUniqueId ( ) ,
100
+ ideInfo . extensionVersion ,
182
101
) ;
183
- }
184
- } ) ;
185
- }
186
-
187
- async setSelectedProfile ( profileId : string ) {
188
- this . selectedProfileId = profileId ;
189
- const newConfig = await this . loadConfig ( ) ;
190
- this . notifyConfigListerners ( newConfig ) ;
191
- const selectedProfiles =
192
- this . globalContext . get ( "lastSelectedProfileForWorkspace" ) ?? { } ;
193
- selectedProfiles [ await this . getWorkspaceId ( ) ] = profileId ;
194
- this . globalContext . update (
195
- "lastSelectedProfileForWorkspace" ,
196
- selectedProfiles ,
197
- ) ;
198
- }
199
-
200
- // A unique ID for the current workspace, built from folder names
201
- private async getWorkspaceId ( ) : Promise < string > {
202
- const dirs = await this . ide . getWorkspaceDirs ( ) ;
203
- return dirs . join ( "&" ) ;
204
- }
205
-
206
- // Automatically refresh config when Continue-related IDE (e.g. VS Code) settings are changed
207
- updateIdeSettings ( ideSettings : IdeSettings ) {
208
- this . ideSettingsPromise = Promise . resolve ( ideSettings ) ;
209
- this . reloadConfig ( ) ;
210
- }
211
-
212
- private profilesListeners : ( ( profiles : ProfileDescription [ ] ) => void ) [ ] = [ ] ;
213
- onDidChangeAvailableProfiles (
214
- listener : ( profiles : ProfileDescription [ ] ) => void ,
215
- ) {
216
- this . profilesListeners . push ( listener ) ;
217
- }
218
-
219
- private notifyProfileListeners ( profiles : ProfileDescription [ ] ) {
220
- for ( const listener of this . profilesListeners ) {
221
- listener ( profiles ) ;
102
+
103
+ ( newConfig . contextProviders ?? [ ] ) . push (
104
+ ...this . additionalContextProviders ,
105
+ ) ;
106
+
107
+ this . savedConfig = newConfig ;
108
+ resolve ( newConfig ) ;
109
+ } ) ;
110
+
111
+ this . savedConfig = await this . _pendingConfigPromise ;
112
+ this . _pendingConfigPromise = undefined ;
113
+ return this . savedConfig ;
222
114
}
223
- }
224
-
225
- private notifyConfigListerners ( newConfig : ContinueConfig ) {
226
- // Notify listeners that config changed
227
- for ( const listener of this . updateListeners ) {
228
- listener ( newConfig ) ;
115
+
116
+ async llmFromTitle ( title ?: string ) : Promise < ILLM > {
117
+ const config = await this . loadConfig ( ) ;
118
+ const model =
119
+ config . models . find ( ( m ) => m . title === title ) || config . models [ 0 ] ;
120
+ if ( ! model ) {
121
+ throw new Error ( "No model found" ) ;
122
+ }
123
+
124
+ return model ;
229
125
}
230
- }
231
-
232
- private updateListeners : ( ( newConfig : ContinueConfig ) => void ) [ ] = [ ] ;
233
- onConfigUpdate ( listener : ( newConfig : ContinueConfig ) => void ) {
234
- this . updateListeners . push ( listener ) ;
235
- }
236
-
237
- async reloadConfig ( ) {
238
- // TODO: this isn't right, there are two different senses in which you want to "reload"
239
- const newConfig = await this . currentProfile . reloadConfig ( ) ;
240
- this . inactiveProfiles . forEach ( ( profile ) => profile . clearConfig ( ) ) ;
241
- this . notifyConfigListerners ( newConfig ) ;
242
- }
243
-
244
- getSerializedConfig ( ) : Promise < BrowserSerializedContinueConfig > {
245
- return this . currentProfile . getSerializedConfig (
246
- this . additionalContextProviders ,
247
- ) ;
248
- }
249
-
250
- listProfiles ( ) : ProfileDescription [ ] {
251
- return this . profiles . map ( ( p ) => p . profileDescription ) ;
252
- }
253
-
254
- async loadConfig ( ) : Promise < ContinueConfig > {
255
- return this . currentProfile . loadConfig ( this . additionalContextProviders ) ;
256
- }
257
-
258
- async llmFromTitle ( title ?: string ) : Promise < ILLM > {
259
- const config = await this . loadConfig ( ) ;
260
- const model =
261
- config . models . find ( ( m ) => m . title === title ) || config . models [ 0 ] ;
262
- if ( ! model ) {
263
- throw new Error ( "No model found" ) ;
126
+
127
+ registerCustomContextProvider ( contextProvider : IContextProvider ) {
128
+ this . additionalContextProviders . push ( contextProvider ) ;
129
+ this . reloadConfig ( ) ;
264
130
}
265
-
266
- return model ;
267
- }
268
-
269
- registerCustomContextProvider ( contextProvider : IContextProvider ) {
270
- this . additionalContextProviders . push ( contextProvider ) ;
271
- this . reloadConfig ( ) ;
272
- }
273
- }
131
+ }
0 commit comments