33
44import {
55 CancellationToken ,
6- l10n ,
7- LanguageModelTextPart ,
86 LanguageModelTool ,
97 LanguageModelToolInvocationOptions ,
108 LanguageModelToolInvocationPrepareOptions ,
119 LanguageModelToolResult ,
1210 PreparedToolInvocation ,
1311 Uri ,
1412 workspace ,
15- commands ,
16- QuickPickItem ,
13+ lm ,
1714} from 'vscode' ;
18- import { PythonExtension , ResolvedEnvironment } from '../api/types' ;
15+ import { PythonExtension } from '../api/types' ;
1916import { IServiceContainer } from '../ioc/types' ;
2017import { ICodeExecutionService } from '../terminals/types' ;
2118import { 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' ;
2326import { resolveFilePath } from './utils' ;
24- import { IRecommendedEnvironmentService } from '../interpreter/configuration/types' ;
2527import { 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' ;
4132
4233export class ConfigurePythonEnvTool implements LanguageModelTool < IResourceReference > {
4334 private readonly terminalExecutionService : TerminalCodeExecutionProvider ;
4435 private readonly terminalHelper : ITerminalHelper ;
4536 private readonly recommendedEnvService : IRecommendedEnvironmentService ;
4637 public static readonly toolName = 'configure_python_environment' ;
4738 constructor (
39+ private readonly discoveryApi : IDiscoveryAPI ,
4840 private readonly api : PythonExtension [ 'environments' ] ,
4941 private readonly serviceContainer : IServiceContainer ,
5042 ) {
@@ -57,12 +49,7 @@ export class ConfigurePythonEnvTool implements LanguageModelTool<IResourceRefere
5749 IRecommendedEnvironmentService ,
5850 ) ;
5951 }
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+
6653 async invoke (
6754 options : LanguageModelToolInvocationOptions < IResourceReference > ,
6855 token : CancellationToken ,
@@ -73,22 +60,14 @@ export class ConfigurePythonEnvTool implements LanguageModelTool<IResourceRefere
7360 return notebookResponse ;
7461 }
7562
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 ,
9271 this . api ,
9372 this . terminalExecutionService ,
9473 this . terminalHelper ,
@@ -97,174 +76,46 @@ export class ConfigurePythonEnvTool implements LanguageModelTool<IResourceRefere
9776 ) ;
9877 }
9978
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+ }
11295 }
113- return new LanguageModelToolResult ( [
114- new LanguageModelTextPart ( 'User did not select a Python environment.' ) ,
115- ] ) ;
11696 }
11797
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 ) ;
13399 }
134100
135101 async prepareInvocation ?(
136- options : LanguageModelToolInvocationPrepareOptions < IResourceReference > ,
102+ _options : LanguageModelToolInvocationPrepareOptions < IResourceReference > ,
137103 _token : CancellationToken ,
138104 ) : 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 ) {
146111 const recommededEnv = await this . recommendedEnvService . getRecommededEnvironment ( resource ) ;
147112 // Already selected workspace env, hence nothing to do.
148113 if ( recommededEnv ?. reason === 'workspaceUserSelected' && workspace . workspaceFolders ?. length ) {
149- return { } ;
114+ return recommededEnv . environment ;
150115 }
151116 // No workspace folders, and the user selected a global environment.
152117 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 ;
268119 }
269120 }
270121}
0 commit comments