Skip to content

Commit d637f32

Browse files
eleanorjboydCopilot
andcommitted
Enhance Python environment configuration tools with optional interpreter path support for direct setup
Co-authored-by: Copilot <copilot@github.com>
1 parent 347b1ac commit d637f32

4 files changed

Lines changed: 118 additions & 21 deletions

File tree

package.json

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1593,7 +1593,7 @@
15931593
{
15941594
"name": "configure_python_environment",
15951595
"displayName": "Configure Python Environment",
1596-
"modelDescription": "This tool configures a Python environment in the given workspace. ALWAYS Use this tool to set up the user's chosen environment and ALWAYS call this tool before using any other Python related tools or running any Python command in the terminal. IMPORTANT: This tool is only for Python environments (venv, virtualenv, conda, pipenv, poetry, pyenv, pixi, or any other Python environment manager). Do not use this tool for npm packages, system packages, Ruby gems, or any other non-Python dependencies.",
1596+
"modelDescription": "This tool configures a Python environment in the given workspace. ALWAYS Use this tool to set up the user's chosen environment and ALWAYS call this tool before using any other Python related tools or running any Python command in the terminal. If you already know which Python interpreter to use (e.g. from a previous tool call or user message), pass it as 'pythonPath' to skip interactive prompts and configure the environment automatically. IMPORTANT: This tool is only for Python environments (venv, virtualenv, conda, pipenv, poetry, pyenv, pixi, or any other Python environment manager). Do not use this tool for npm packages, system packages, Ruby gems, or any other non-Python dependencies.",
15971597
"userDescription": "%python.languageModelTools.configure_python_environment.userDescription%",
15981598
"toolReferenceName": "configurePythonEnvironment",
15991599
"tags": [
@@ -1609,6 +1609,10 @@
16091609
"resourcePath": {
16101610
"type": "string",
16111611
"description": "The path to the Python file or workspace for which a Python Environment needs to be configured."
1612+
},
1613+
"pythonPath": {
1614+
"type": "string",
1615+
"description": "Optional absolute path to a Python interpreter to use. When provided, the environment is configured automatically without any interactive prompts. Use this to avoid blocking the session on user input."
16121616
}
16131617
},
16141618
"required": []
@@ -1642,7 +1646,7 @@
16421646
{
16431647
"name": "selectEnvironment",
16441648
"displayName": "Select a Python Environment",
1645-
"modelDescription": "This tool will prompt the user to select an existing Python Environment",
1649+
"modelDescription": "This tool will prompt the user to select an existing Python Environment. If pythonPath is provided, it sets that interpreter directly without showing any UI.",
16461650
"tags": [],
16471651
"canBeReferencedInPrompt": false,
16481652
"inputSchema": {
@@ -1651,6 +1655,10 @@
16511655
"resourcePath": {
16521656
"type": "string",
16531657
"description": "The path to the Python file or workspace for which a Python Environment needs to be configured."
1658+
},
1659+
"pythonPath": {
1660+
"type": "string",
1661+
"description": "Optional absolute path to a Python interpreter to use. When provided, the interpreter is set directly without showing any UI picker."
16541662
}
16551663
},
16561664
"required": []

src/client/chat/configurePythonEnvTool.ts

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,20 @@ import { IRecommendedEnvironmentService } from '../interpreter/configuration/typ
2929
import { CreateVirtualEnvTool } from './createVirtualEnvTool';
3030
import { ISelectPythonEnvToolArguments, SelectPythonEnvTool } from './selectEnvTool';
3131
import { BaseTool } from './baseTool';
32+
import { traceVerbose } from '../logging';
3233

33-
export class ConfigurePythonEnvTool extends BaseTool<IResourceReference>
34-
implements LanguageModelTool<IResourceReference> {
34+
export interface IConfigurePythonEnvToolArguments extends IResourceReference {
35+
/**
36+
* Optional path to a Python interpreter. When provided, the tool sets this
37+
* interpreter directly without any user interaction (no Quick Pick, no
38+
* create-venv prompt). This is the recommended way for Copilot to call
39+
* the tool in autopilot / bypass-approvals mode.
40+
*/
41+
pythonPath?: string;
42+
}
43+
44+
export class ConfigurePythonEnvTool extends BaseTool<IConfigurePythonEnvToolArguments>
45+
implements LanguageModelTool<IConfigurePythonEnvToolArguments> {
3546
private readonly terminalExecutionService: TerminalCodeExecutionProvider;
3647
private readonly terminalHelper: ITerminalHelper;
3748
private readonly recommendedEnvService: IRecommendedEnvironmentService;
@@ -53,7 +64,7 @@ export class ConfigurePythonEnvTool extends BaseTool<IResourceReference>
5364
}
5465

5566
async invokeImpl(
56-
options: LanguageModelToolInvocationOptions<IResourceReference>,
67+
options: LanguageModelToolInvocationOptions<IConfigurePythonEnvToolArguments>,
5768
resource: Uri | undefined,
5869
token: CancellationToken,
5970
): Promise<LanguageModelToolResult> {
@@ -63,6 +74,11 @@ export class ConfigurePythonEnvTool extends BaseTool<IResourceReference>
6374
return notebookResponse;
6475
}
6576

77+
// Fast path: if the caller provided a pythonPath, set it directly without any UI.
78+
if (options.input.pythonPath) {
79+
return this.setEnvironmentDirectly(options.input.pythonPath, resource, token);
80+
}
81+
6682
const workspaceSpecificEnv = await raceCancellationError(
6783
this.hasAlreadyGotAWorkspaceSpecificEnvironment(resource),
6884
token,
@@ -107,8 +123,31 @@ export class ConfigurePythonEnvTool extends BaseTool<IResourceReference>
107123
}
108124
}
109125

126+
/**
127+
* Sets the given interpreter path directly without user interaction, then
128+
* resolves and returns the environment details.
129+
*/
130+
private async setEnvironmentDirectly(
131+
pythonPath: string,
132+
resource: Uri | undefined,
133+
token: CancellationToken,
134+
): Promise<LanguageModelToolResult> {
135+
traceVerbose(`${ConfigurePythonEnvTool.toolName}: setting environment directly from pythonPath: ${pythonPath}`);
136+
await raceCancellationError(this.api.updateActiveEnvironmentPath(pythonPath, resource), token);
137+
const envPath = this.api.getActiveEnvironmentPath(resource);
138+
const environment = await raceCancellationError(this.api.resolveEnvironment(envPath), token);
139+
return getEnvDetailsForResponse(
140+
environment,
141+
this.api,
142+
this.terminalExecutionService,
143+
this.terminalHelper,
144+
resource,
145+
token,
146+
);
147+
}
148+
110149
async prepareInvocationImpl(
111-
_options: LanguageModelToolInvocationPrepareOptions<IResourceReference>,
150+
_options: LanguageModelToolInvocationPrepareOptions<IConfigurePythonEnvToolArguments>,
112151
_resource: Uri | undefined,
113152
_token: CancellationToken,
114153
): Promise<PreparedToolInvocation> {

src/client/chat/createVirtualEnvTool.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ import { hideEnvCreation } from '../pythonEnvironments/creation/provider/hideEnv
4545
import { BaseTool } from './baseTool';
4646

4747
interface ICreateVirtualEnvToolParams extends IResourceReference {
48-
packageList?: string[]; // Added only becausewe have ability to create a virtual env with list of packages same tool within the in Python Env extension.
48+
packageList?: string[]; // Added only because we have ability to create a virtual env with list of packages same tool within the in Python Env extension.
4949
}
5050

5151
export class CreateVirtualEnvTool extends BaseTool<ICreateVirtualEnvToolParams>
@@ -92,18 +92,24 @@ export class CreateVirtualEnvTool extends BaseTool<ICreateVirtualEnvToolParams>
9292

9393
let createdEnvPath: string | undefined = undefined;
9494
if (useEnvExtension()) {
95-
const result: PythonEnvironment | undefined = await commands.executeCommand('python-envs.createAny', {
96-
quickCreate: true,
97-
additionalPackages: options.input.packageList || [],
98-
uri: workspaceFolder.uri,
99-
selectEnvironment: true,
100-
});
95+
const result: PythonEnvironment | undefined = await raceCancellationError(
96+
Promise.resolve(
97+
commands.executeCommand<PythonEnvironment | undefined>('python-envs.createAny', {
98+
quickCreate: true,
99+
additionalPackages: options.input.packageList || [],
100+
uri: workspaceFolder.uri,
101+
selectEnvironment: true,
102+
}),
103+
),
104+
token,
105+
);
101106
createdEnvPath = result?.environmentPath.fsPath;
102107
} else {
103108
const created = await raceCancellationError(
104109
createVirtualEnvironment({
105110
interpreter: preferredGlobalPythonEnv.id,
106111
workspaceFolder,
112+
installPackages: false,
107113
}),
108114
token,
109115
);
@@ -120,7 +126,7 @@ export class CreateVirtualEnvTool extends BaseTool<ICreateVirtualEnvToolParams>
120126

121127
const stopWatch = new StopWatch();
122128
let env: ResolvedEnvironment | undefined;
123-
while (stopWatch.elapsedTime < 5_000 || !env) {
129+
while (stopWatch.elapsedTime < 5_000 && !env) {
124130
env = await this.api.resolveEnvironment(createdEnvPath);
125131
if (env) {
126132
break;

src/client/chat/selectEnvTool.ts

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
getEnvDetailsForResponse,
2626
getToolResponseIfNotebook,
2727
IResourceReference,
28+
raceCancellationError,
2829
} from './utils';
2930
import { ITerminalHelper } from '../common/terminal/types';
3031
import { raceTimeout } from '../common/utils/async';
@@ -40,6 +41,13 @@ import { BaseTool } from './baseTool';
4041

4142
export interface ISelectPythonEnvToolArguments extends IResourceReference {
4243
reason?: 'cancelled';
44+
/**
45+
* Optional path to a Python interpreter. When provided, the tool sets this
46+
* interpreter directly without showing any Quick Pick UI to the user.
47+
* This prevents the agent from getting stuck waiting for user input in
48+
* autopilot / bypass-approvals mode.
49+
*/
50+
pythonPath?: string;
4351
}
4452

4553
export class SelectPythonEnvTool extends BaseTool<ISelectPythonEnvToolArguments>
@@ -64,23 +72,59 @@ export class SelectPythonEnvTool extends BaseTool<ISelectPythonEnvToolArguments>
6472
resource: Uri | undefined,
6573
token: CancellationToken,
6674
): Promise<LanguageModelToolResult> {
75+
// Fast path: if the caller provided a pythonPath, set it directly without any UI.
76+
if (options.input.pythonPath) {
77+
traceVerbose(
78+
`${SelectPythonEnvTool.toolName}: setting environment directly from pythonPath: ${options.input.pythonPath}`,
79+
);
80+
await raceCancellationError(
81+
this.api.updateActiveEnvironmentPath(options.input.pythonPath, resource),
82+
token,
83+
);
84+
const env = await raceCancellationError(
85+
this.api.resolveEnvironment(this.api.getActiveEnvironmentPath(resource)),
86+
token,
87+
);
88+
if (env) {
89+
return getEnvDetailsForResponse(
90+
env,
91+
this.api,
92+
this.terminalExecutionService,
93+
this.terminalHelper,
94+
resource,
95+
token,
96+
);
97+
}
98+
return new LanguageModelToolResult([
99+
new LanguageModelTextPart(
100+
`The provided pythonPath '${options.input.pythonPath}' could not be resolved to a valid Python environment.`,
101+
),
102+
]);
103+
}
104+
67105
let selected: boolean | undefined = false;
68106
const hasVenvOrCondaEnvInWorkspaceFolder = doesWorkspaceHaveVenvOrCondaEnv(resource, this.api);
69107
if (options.input.reason === 'cancelled' || hasVenvOrCondaEnvInWorkspaceFolder) {
70-
const result = (await Promise.resolve(
71-
commands.executeCommand(Commands.Set_Interpreter, {
72-
hideCreateVenv: false,
73-
showBackButton: false,
74-
}),
75-
)) as SelectEnvironmentResult | undefined;
108+
const result = await raceCancellationError(
109+
Promise.resolve(
110+
commands.executeCommand(Commands.Set_Interpreter, {
111+
hideCreateVenv: false,
112+
showBackButton: false,
113+
}),
114+
) as Promise<SelectEnvironmentResult | undefined>,
115+
token,
116+
);
76117
if (result?.path) {
77118
traceVerbose(`User selected a Python environment ${result.path} in Select Python Tool.`);
78119
selected = true;
79120
} else {
80121
traceWarn(`User did not select a Python environment in Select Python Tool.`);
81122
}
82123
} else {
83-
selected = await showCreateAndSelectEnvironmentQuickPick(resource, this.serviceContainer);
124+
selected = await raceCancellationError(
125+
showCreateAndSelectEnvironmentQuickPick(resource, this.serviceContainer),
126+
token,
127+
);
84128
if (selected) {
85129
traceVerbose(`User selected a Python environment ${selected} in Select Python Tool(2).`);
86130
} else {

0 commit comments

Comments
 (0)