3
3
4
4
import {
5
5
CancellationToken ,
6
- l10n ,
7
- LanguageModelTextPart ,
8
6
LanguageModelTool ,
9
7
LanguageModelToolInvocationOptions ,
10
8
LanguageModelToolInvocationPrepareOptions ,
11
9
LanguageModelToolResult ,
12
10
PreparedToolInvocation ,
13
11
Uri ,
14
12
workspace ,
15
- commands ,
16
- QuickPickItem ,
13
+ lm ,
17
14
} from 'vscode' ;
18
- import { PythonExtension , ResolvedEnvironment } from '../api/types' ;
15
+ import { PythonExtension } from '../api/types' ;
19
16
import { IServiceContainer } from '../ioc/types' ;
20
17
import { ICodeExecutionService } from '../terminals/types' ;
21
18
import { TerminalCodeExecutionProvider } from '../terminals/codeExecution/terminalCodeExecution' ;
22
- import { getEnvironmentDetails , getToolResponseIfNotebook , raceCancellationError } from './utils' ;
19
+ import {
20
+ getEnvDetailsForResponse ,
21
+ getToolResponseIfNotebook ,
22
+ IResourceReference ,
23
+ isCancellationError ,
24
+ raceCancellationError ,
25
+ } from './utils' ;
23
26
import { resolveFilePath } from './utils' ;
24
- import { IRecommendedEnvironmentService } from '../interpreter/configuration/types' ;
25
27
import { ITerminalHelper } from '../common/terminal/types' ;
26
- import { raceTimeout } from '../common/utils/async' ;
27
- import { Commands , Octicons } from '../common/constants' ;
28
- import { CreateEnvironmentResult } from '../pythonEnvironments/creation/proposed.createEnvApis' ;
29
- import { IInterpreterPathService } from '../common/types' ;
30
- import { DisposableStore } from '../common/utils/resourceLifecycle' ;
31
- import { Common , InterpreterQuickPickList } from '../common/utils/localize' ;
32
- import { QuickPickItemKind } from '../../test/mocks/vsc' ;
33
- import { showQuickPick } from '../common/vscodeApis/windowApis' ;
34
- import { SelectEnvironmentResult } from '../interpreter/configuration/interpreterSelector/commands/setInterpreter' ;
35
-
36
- export interface IResourceReference {
37
- resourcePath ?: string ;
38
- }
39
-
40
- let _environmentConfigured = false ;
28
+ import { IRecommendedEnvironmentService } from '../interpreter/configuration/types' ;
29
+ import { IDiscoveryAPI } from '../pythonEnvironments/base/locator' ;
30
+ import { CreateVirtualEnvTool } from './createVirtualEnvTool' ;
31
+ import { SelectPythonEnvTool } from './selectEnvTool' ;
41
32
42
33
export class ConfigurePythonEnvTool implements LanguageModelTool < IResourceReference > {
43
34
private readonly terminalExecutionService : TerminalCodeExecutionProvider ;
44
35
private readonly terminalHelper : ITerminalHelper ;
45
36
private readonly recommendedEnvService : IRecommendedEnvironmentService ;
46
37
public static readonly toolName = 'configure_python_environment' ;
47
38
constructor (
39
+ private readonly discoveryApi : IDiscoveryAPI ,
48
40
private readonly api : PythonExtension [ 'environments' ] ,
49
41
private readonly serviceContainer : IServiceContainer ,
50
42
) {
@@ -57,12 +49,7 @@ export class ConfigurePythonEnvTool implements LanguageModelTool<IResourceRefere
57
49
IRecommendedEnvironmentService ,
58
50
) ;
59
51
}
60
- /**
61
- * Invokes the tool to get the information about the Python environment.
62
- * @param options - The invocation options containing the file path.
63
- * @param token - The cancellation token.
64
- * @returns The result containing the information about the Python environment or an error message.
65
- */
52
+
66
53
async invoke (
67
54
options : LanguageModelToolInvocationOptions < IResourceReference > ,
68
55
token : CancellationToken ,
@@ -73,22 +60,14 @@ export class ConfigurePythonEnvTool implements LanguageModelTool<IResourceRefere
73
60
return notebookResponse ;
74
61
}
75
62
76
- const recommededEnv = await this . recommendedEnvService . getRecommededEnvironment ( resource ) ;
77
- // Already selected workspace env, hence nothing to do.
78
- if ( recommededEnv ?. reason === 'workspaceUserSelected' && workspace . workspaceFolders ?. length ) {
79
- return await getEnvDetailsForResponse (
80
- recommededEnv . environment ,
81
- this . api ,
82
- this . terminalExecutionService ,
83
- this . terminalHelper ,
84
- resource ,
85
- token ,
86
- ) ;
87
- }
88
- // No workspace folders, and the user selected a global environment.
89
- if ( recommededEnv ?. reason === 'globalUserSelected' && ! workspace . workspaceFolders ?. length ) {
90
- return await getEnvDetailsForResponse (
91
- recommededEnv . environment ,
63
+ const workspaceSpecificEnv = await raceCancellationError (
64
+ this . hasAlreadyGotAWorkspaceSpecificEnvironment ( resource ) ,
65
+ token ,
66
+ ) ;
67
+
68
+ if ( workspaceSpecificEnv ) {
69
+ return getEnvDetailsForResponse (
70
+ workspaceSpecificEnv ,
92
71
this . api ,
93
72
this . terminalExecutionService ,
94
73
this . terminalHelper ,
@@ -97,174 +76,46 @@ export class ConfigurePythonEnvTool implements LanguageModelTool<IResourceRefere
97
76
) ;
98
77
}
99
78
100
- if ( ! workspace . workspaceFolders ?. length ) {
101
- const selected = await Promise . resolve ( commands . executeCommand ( Commands . Set_Interpreter ) ) ;
102
- const env = await this . api . resolveEnvironment ( this . api . getActiveEnvironmentPath ( resource ) ) ;
103
- if ( selected && env ) {
104
- return await getEnvDetailsForResponse (
105
- env ,
106
- this . api ,
107
- this . terminalExecutionService ,
108
- this . terminalHelper ,
109
- resource ,
110
- token ,
111
- ) ;
79
+ let reason : 'cancelled' | undefined ;
80
+ if (
81
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
82
+ await new CreateVirtualEnvTool ( this . discoveryApi , this . api , this . serviceContainer ) . canCreateNewVirtualEnv (
83
+ resolveFilePath ( options . input . resourcePath ) ,
84
+ token ,
85
+ )
86
+ ) {
87
+ reason = 'cancelled' ;
88
+ try {
89
+ return await lm . invokeTool ( CreateVirtualEnvTool . toolName , options , token ) ;
90
+ } catch ( ex ) {
91
+ // If the user cancelled the tool, then we should not invoke the select env tool.
92
+ if ( ! isCancellationError ( ex ) ) {
93
+ throw ex ;
94
+ }
112
95
}
113
- return new LanguageModelToolResult ( [
114
- new LanguageModelTextPart ( 'User did not select a Python environment.' ) ,
115
- ] ) ;
116
96
}
117
97
118
- const selected = await showCreateAndSelectEnvironmentQuickPick ( resource , this . serviceContainer ) ;
119
- const env = await this . api . resolveEnvironment ( this . api . getActiveEnvironmentPath ( resource ) ) ;
120
- if ( selected && env ) {
121
- return await getEnvDetailsForResponse (
122
- env ,
123
- this . api ,
124
- this . terminalExecutionService ,
125
- this . terminalHelper ,
126
- resource ,
127
- token ,
128
- ) ;
129
- }
130
- return new LanguageModelToolResult ( [
131
- new LanguageModelTextPart ( 'User did not create nor select a Python environment.' ) ,
132
- ] ) ;
98
+ return lm . invokeTool ( SelectPythonEnvTool . toolName , { ...options , input : { ...options . input , reason } } , token ) ;
133
99
}
134
100
135
101
async prepareInvocation ?(
136
- options : LanguageModelToolInvocationPrepareOptions < IResourceReference > ,
102
+ _options : LanguageModelToolInvocationPrepareOptions < IResourceReference > ,
137
103
_token : CancellationToken ,
138
104
) : Promise < PreparedToolInvocation > {
139
- if ( _environmentConfigured ) {
140
- return { } ;
141
- }
142
- const resource = resolveFilePath ( options . input . resourcePath ) ;
143
- if ( getToolResponseIfNotebook ( resource ) ) {
144
- return { } ;
145
- }
105
+ return {
106
+ invocationMessage : 'Configuring a Python Environment' ,
107
+ } ;
108
+ }
109
+
110
+ async hasAlreadyGotAWorkspaceSpecificEnvironment ( resource : Uri | undefined ) {
146
111
const recommededEnv = await this . recommendedEnvService . getRecommededEnvironment ( resource ) ;
147
112
// Already selected workspace env, hence nothing to do.
148
113
if ( recommededEnv ?. reason === 'workspaceUserSelected' && workspace . workspaceFolders ?. length ) {
149
- return { } ;
114
+ return recommededEnv . environment ;
150
115
}
151
116
// No workspace folders, and the user selected a global environment.
152
117
if ( recommededEnv ?. reason === 'globalUserSelected' && ! workspace . workspaceFolders ?. length ) {
153
- return { } ;
154
- }
155
-
156
- if ( ! workspace . workspaceFolders ?. length ) {
157
- return {
158
- confirmationMessages : {
159
- title : l10n . t ( 'Configure a Python Environment?' ) ,
160
- message : l10n . t ( 'You will be prompted to select a Python Environment.' ) ,
161
- } ,
162
- } ;
163
- }
164
- return {
165
- confirmationMessages : {
166
- title : l10n . t ( 'Configure a Python Environment?' ) ,
167
- message : l10n . t (
168
- [
169
- 'The recommended option is to create a new Python Environment, providing the benefit of isolating packages from other environments. ' ,
170
- 'Optionally you could select an existing Python Environment.' ,
171
- ] . join ( '\n' ) ,
172
- ) ,
173
- } ,
174
- } ;
175
- }
176
- }
177
-
178
- async function getEnvDetailsForResponse (
179
- environment : ResolvedEnvironment | undefined ,
180
- api : PythonExtension [ 'environments' ] ,
181
- terminalExecutionService : TerminalCodeExecutionProvider ,
182
- terminalHelper : ITerminalHelper ,
183
- resource : Uri | undefined ,
184
- token : CancellationToken ,
185
- ) : Promise < LanguageModelToolResult > {
186
- const envPath = api . getActiveEnvironmentPath ( resource ) ;
187
- environment = environment || ( await raceCancellationError ( api . resolveEnvironment ( envPath ) , token ) ) ;
188
- if ( ! environment || ! environment . version ) {
189
- throw new Error ( 'No environment found for the provided resource path: ' + resource ?. fsPath ) ;
190
- }
191
- const message = await getEnvironmentDetails (
192
- resource ,
193
- api ,
194
- terminalExecutionService ,
195
- terminalHelper ,
196
- undefined ,
197
- token ,
198
- ) ;
199
- return new LanguageModelToolResult ( [
200
- new LanguageModelTextPart ( `A Python Environment has been configured. \n` + message ) ,
201
- ] ) ;
202
- }
203
-
204
- async function showCreateAndSelectEnvironmentQuickPick (
205
- uri : Uri | undefined ,
206
- serviceContainer : IServiceContainer ,
207
- ) : Promise < boolean | undefined > {
208
- const createLabel = `${ Octicons . Add } ${ InterpreterQuickPickList . create . label } ` ;
209
- const selectLabel = l10n . t ( 'Select an existing Python Environment' ) ;
210
- const items : QuickPickItem [ ] = [
211
- { kind : QuickPickItemKind . Separator , label : Common . recommended } ,
212
- { label : createLabel } ,
213
- { label : selectLabel } ,
214
- ] ;
215
-
216
- const selectedItem = await showQuickPick ( items , {
217
- placeHolder : l10n . t ( 'Configure a Python Environment' ) ,
218
- matchOnDescription : true ,
219
- ignoreFocusOut : true ,
220
- } ) ;
221
-
222
- if ( selectedItem && ! Array . isArray ( selectedItem ) && selectedItem . label === createLabel ) {
223
- const disposables = new DisposableStore ( ) ;
224
- try {
225
- const workspaceFolder =
226
- ( workspace . workspaceFolders ?. length && uri ? workspace . getWorkspaceFolder ( uri ) : undefined ) ||
227
- ( workspace . workspaceFolders ?. length === 1 ? workspace . workspaceFolders [ 0 ] : undefined ) ;
228
- const interpreterPathService = serviceContainer . get < IInterpreterPathService > ( IInterpreterPathService ) ;
229
- const interpreterChanged = new Promise < void > ( ( resolve ) => {
230
- disposables . add ( interpreterPathService . onDidChange ( ( ) => resolve ( ) ) ) ;
231
- } ) ;
232
- const created : CreateEnvironmentResult | undefined = await commands . executeCommand (
233
- Commands . Create_Environment ,
234
- {
235
- showBackButton : true ,
236
- selectEnvironment : true ,
237
- workspaceFolder,
238
- } ,
239
- ) ;
240
-
241
- if ( created ?. action === 'Back' ) {
242
- return showCreateAndSelectEnvironmentQuickPick ( uri , serviceContainer ) ;
243
- }
244
- if ( created ?. action === 'Cancel' ) {
245
- return undefined ;
246
- }
247
- if ( created ?. path ) {
248
- // Wait a few secs to ensure the env is selected as the active environment..
249
- await raceTimeout ( 5_000 , interpreterChanged ) ;
250
- return true ;
251
- }
252
- } finally {
253
- disposables . dispose ( ) ;
254
- }
255
- }
256
- if ( selectedItem && ! Array . isArray ( selectedItem ) && selectedItem . label === selectLabel ) {
257
- const result = ( await Promise . resolve (
258
- commands . executeCommand ( Commands . Set_Interpreter , { hideCreateVenv : true , showBackButton : true } ) ,
259
- ) ) as SelectEnvironmentResult | undefined ;
260
- if ( result ?. action === 'Back' ) {
261
- return showCreateAndSelectEnvironmentQuickPick ( uri , serviceContainer ) ;
262
- }
263
- if ( result ?. action === 'Cancel' ) {
264
- return undefined ;
265
- }
266
- if ( result ?. path ) {
267
- return true ;
118
+ return recommededEnv . environment ;
268
119
}
269
120
}
270
121
}
0 commit comments