Skip to content

Commit 9b17438

Browse files
authored
All Python lm tools in Python extension (#25116)
1 parent ef01ace commit 9b17438

File tree

10 files changed

+295
-184
lines changed

10 files changed

+295
-184
lines changed

package.json

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1474,9 +1474,10 @@
14741474
"displayName": "Get Python Environment Info",
14751475
"userDescription": "%python.languageModelTools.get_python_environment_details.userDescription%",
14761476
"modelDescription": "This tool will retrieve the details of the Python Environment for the specified file or workspace. The details returned include the 1. Type of Environment (conda, venv, etec), 2. Version of Python, 3. List of all installed packages with their versions. ALWAYS call configure_python_environment before using this tool.",
1477-
"toolReferenceName": "pythonGetEnvironmentInfo",
1477+
"toolReferenceName": "getPythonEnvironmentInfo",
14781478
"tags": [
14791479
"python",
1480+
"python environment",
14801481
"extension_installed_by_tool",
14811482
"enable_other_tool_configure_python_environment"
14821483
],
@@ -1491,17 +1492,17 @@
14911492
}
14921493
},
14931494
"required": []
1494-
},
1495-
"when": "!pythonEnvExtensionInstalled"
1495+
}
14961496
},
14971497
{
14981498
"name": "get_python_executable_details",
14991499
"displayName": "Get Python Executable",
15001500
"userDescription": "%python.languageModelTools.get_python_executable_details.userDescription%",
15011501
"modelDescription": "This tool will retrieve the details of the Python Environment for the specified file or workspace. ALWAYS use this tool before executing any Python command in the terminal. This tool returns the details of how to construct the fully qualified path and or command including details such as arguments required to run Python in a terminal. Note: Instead of executing `python --version` or `python -c 'import sys; print(sys.executable)'`, use this tool to get the Python executable path to replace the `python` command. E.g. instead of using `python -c 'import sys; print(sys.executable)'`, use this tool to build the command `conda run -n <env_name> -c 'import sys; print(sys.executable)'`. ALWAYS call configure_python_environment before using this tool.",
1502-
"toolReferenceName": "pythonExecutableCommand",
1502+
"toolReferenceName": "getPythonExecutableCommand",
15031503
"tags": [
15041504
"python",
1505+
"python environment",
15051506
"extension_installed_by_tool",
15061507
"enable_other_tool_configure_python_environment"
15071508
],
@@ -1516,17 +1517,17 @@
15161517
}
15171518
},
15181519
"required": []
1519-
},
1520-
"when": "!pythonEnvExtensionInstalled"
1520+
}
15211521
},
15221522
{
15231523
"name": "install_python_packages",
15241524
"displayName": "Install Python Package",
15251525
"userDescription": "%python.languageModelTools.install_python_packages.userDescription%",
15261526
"modelDescription": "Installs Python packages in the given workspace. Use this tool to install packages in the user's chosen environment. ALWAYS call configure_python_environment before using this tool.",
1527-
"toolReferenceName": "pythonInstallPackage",
1527+
"toolReferenceName": "installPythonPackage",
15281528
"tags": [
15291529
"python",
1530+
"python environment",
15301531
"install python package",
15311532
"extension_installed_by_tool",
15321533
"enable_other_tool_configure_python_environment"
@@ -1551,8 +1552,7 @@
15511552
"required": [
15521553
"packageList"
15531554
]
1554-
},
1555-
"when": "!pythonEnvExtensionInstalled"
1555+
}
15561556
},
15571557
{
15581558
"name": "configure_python_environment",
@@ -1562,6 +1562,7 @@
15621562
"toolReferenceName": "configurePythonEnvironment",
15631563
"tags": [
15641564
"python",
1565+
"python environment",
15651566
"extension_installed_by_tool"
15661567
],
15671568
"icon": "$(gear)",
@@ -1575,8 +1576,7 @@
15751576
}
15761577
},
15771578
"required": []
1578-
},
1579-
"when": "!pythonEnvExtensionInstalled"
1579+
}
15801580
},
15811581
{
15821582
"name": "create_virtual_environment",

src/client/chat/configurePythonEnvTool.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import { ITerminalHelper } from '../common/terminal/types';
2828
import { IRecommendedEnvironmentService } from '../interpreter/configuration/types';
2929
import { CreateVirtualEnvTool } from './createVirtualEnvTool';
3030
import { ISelectPythonEnvToolArguments, SelectPythonEnvTool } from './selectEnvTool';
31-
import { useEnvExtension } from '../envExt/api.internal';
3231

3332
export class ConfigurePythonEnvTool implements LanguageModelTool<IResourceReference> {
3433
private readonly terminalExecutionService: TerminalCodeExecutionProvider;
@@ -78,10 +77,7 @@ export class ConfigurePythonEnvTool implements LanguageModelTool<IResourceRefere
7877

7978
if (await this.createEnvTool.shouldCreateNewVirtualEnv(resource, token)) {
8079
try {
81-
// If the Python Env extension is available, then use that.
82-
// create_quick_virtual_environment
83-
const toolName = useEnvExtension() ? 'create_quick_virtual_environment' : CreateVirtualEnvTool.toolName;
84-
return await lm.invokeTool(toolName, options, token);
80+
return await lm.invokeTool(CreateVirtualEnvTool.toolName, options, token);
8581
} catch (ex) {
8682
if (isCancellationError(ex)) {
8783
const input: ISelectPythonEnvToolArguments = {

src/client/chat/createVirtualEnvTool.ts

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import {
55
CancellationError,
66
CancellationToken,
7+
commands,
78
l10n,
89
LanguageModelTool,
910
LanguageModelToolInvocationOptions,
@@ -39,6 +40,8 @@ import { isStableVersion } from '../pythonEnvironments/info/pythonVersion';
3940
import { createVirtualEnvironment } from '../pythonEnvironments/creation/createEnvApi';
4041
import { traceError, traceVerbose, traceWarn } from '../logging';
4142
import { StopWatch } from '../common/utils/stopWatch';
43+
import { useEnvExtension } from '../envExt/api.internal';
44+
import { PythonEnvironment } from '../envExt/types';
4245

4346
interface ICreateVirtualEnvToolParams extends IResourceReference {
4447
packageList?: string[]; // Added only becausewe have ability to create a virtual env with list of packages same tool within the in Python Env extension.
@@ -66,7 +69,7 @@ export class CreateVirtualEnvTool implements LanguageModelTool<ICreateVirtualEnv
6669
}
6770

6871
async invoke(
69-
options: LanguageModelToolInvocationOptions<IResourceReference>,
72+
options: LanguageModelToolInvocationOptions<ICreateVirtualEnvToolParams>,
7073
token: CancellationToken,
7174
): Promise<LanguageModelToolResult> {
7275
const resource = resolveFilePath(options.input.resourcePath);
@@ -83,14 +86,26 @@ export class CreateVirtualEnvTool implements LanguageModelTool<ICreateVirtualEnv
8386
disposables.add(interpreterPathService.onDidChange(() => resolve()));
8487
});
8588

86-
const created = await raceCancellationError(
87-
createVirtualEnvironment({
88-
interpreter: preferredGlobalPythonEnv.id,
89-
workspaceFolder,
90-
}),
91-
token,
92-
);
93-
if (!created?.path) {
89+
let createdEnvPath: string | undefined = undefined;
90+
if (useEnvExtension()) {
91+
const result: PythonEnvironment | undefined = await commands.executeCommand('python-envs.createAny', {
92+
quickCreate: true,
93+
additionalPackages: options.input.packageList || [],
94+
uri: workspaceFolder.uri,
95+
selectEnvironment: true,
96+
});
97+
createdEnvPath = result?.environmentPath.fsPath;
98+
} else {
99+
const created = await raceCancellationError(
100+
createVirtualEnvironment({
101+
interpreter: preferredGlobalPythonEnv.id,
102+
workspaceFolder,
103+
}),
104+
token,
105+
);
106+
createdEnvPath = created?.path;
107+
}
108+
if (!createdEnvPath) {
94109
traceWarn(`${CreateVirtualEnvTool.toolName} tool not invoked, virtual env not created.`);
95110
throw new CancellationError();
96111
}
@@ -102,7 +117,7 @@ export class CreateVirtualEnvTool implements LanguageModelTool<ICreateVirtualEnv
102117
const stopWatch = new StopWatch();
103118
let env: ResolvedEnvironment | undefined;
104119
while (stopWatch.elapsedTime < 5_000 || !env) {
105-
env = await this.api.resolveEnvironment(created.path);
120+
env = await this.api.resolveEnvironment(createdEnvPath);
106121
if (env) {
107122
break;
108123
} else {
@@ -150,7 +165,7 @@ export class CreateVirtualEnvTool implements LanguageModelTool<ICreateVirtualEnv
150165
}
151166

152167
async prepareInvocation?(
153-
options: LanguageModelToolInvocationPrepareOptions<IResourceReference>,
168+
options: LanguageModelToolInvocationPrepareOptions<ICreateVirtualEnvToolParams>,
154169
token: CancellationToken,
155170
): Promise<PreparedToolInvocation> {
156171
const resource = resolveFilePath(options.input.resourcePath);

src/client/chat/getPythonEnvTool.ts

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { getEnvironmentDetails, getToolResponseIfNotebook, IResourceReference, r
2020
import { resolveFilePath } from './utils';
2121
import { getPythonPackagesResponse } from './listPackagesTool';
2222
import { ITerminalHelper } from '../common/terminal/types';
23+
import { getEnvExtApi, useEnvExtension } from '../envExt/api.internal';
2324

2425
export class GetEnvironmentInfoTool implements LanguageModelTool<IResourceReference> {
2526
private readonly terminalExecutionService: TerminalCodeExecutionProvider;
@@ -56,14 +57,33 @@ export class GetEnvironmentInfoTool implements LanguageModelTool<IResourceRefere
5657
if (!environment || !environment.version) {
5758
throw new Error('No environment found for the provided resource path: ' + resourcePath?.fsPath);
5859
}
59-
const packages = await getPythonPackagesResponse(
60-
environment,
61-
this.pythonExecFactory,
62-
this.processServiceFactory,
63-
resourcePath,
64-
token,
65-
);
6660

61+
let packages = '';
62+
if (useEnvExtension()) {
63+
const api = await getEnvExtApi();
64+
const env = await api.getEnvironment(resourcePath);
65+
const pkgs = env ? await api.getPackages(env) : [];
66+
if (pkgs && pkgs.length > 0) {
67+
// Installed Python packages, each in the format <name> or <name> (<version>). The version may be omitted if unknown. Returns an empty array if no packages are installed.
68+
const response = [
69+
'Below is a list of the Python packages, each in the format <name> or <name> (<version>). The version may be omitted if unknown: ',
70+
];
71+
pkgs.forEach((pkg) => {
72+
const version = pkg.version;
73+
response.push(version ? `- ${pkg.name} (${version})` : `- ${pkg.name}`);
74+
});
75+
packages = response.join('\n');
76+
}
77+
}
78+
if (!packages) {
79+
packages = await getPythonPackagesResponse(
80+
environment,
81+
this.pythonExecFactory,
82+
this.processServiceFactory,
83+
resourcePath,
84+
token,
85+
);
86+
}
6787
const message = await getEnvironmentDetails(
6888
resourcePath,
6989
this.api,

src/client/chat/index.ts

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4-
import { commands, extensions, lm } from 'vscode';
4+
import { lm } from 'vscode';
55
import { PythonExtension } from '../api/types';
66
import { IServiceContainer } from '../ioc/types';
77
import { InstallPackagesTool } from './installPackagesTool';
88
import { IExtensionContext } from '../common/types';
99
import { DisposableStore } from '../common/utils/resourceLifecycle';
10-
import { ENVS_EXTENSION_ID } from '../envExt/api.internal';
1110
import { IDiscoveryAPI } from '../pythonEnvironments/base/locator';
1211
import { GetExecutableTool } from './getExecutableTool';
1312
import { GetEnvironmentInfoTool } from './getPythonEnvTool';
@@ -21,11 +20,6 @@ export function registerTools(
2120
environmentsApi: PythonExtension['environments'],
2221
serviceContainer: IServiceContainer,
2322
) {
24-
if (extensions.getExtension(ENVS_EXTENSION_ID)) {
25-
return;
26-
}
27-
const contextKey = 'pythonEnvExtensionInstalled';
28-
commands.executeCommand('setContext', contextKey, false);
2923
const ourTools = new DisposableStore();
3024
context.subscriptions.push(ourTools);
3125

@@ -55,14 +49,4 @@ export function registerTools(
5549
new ConfigurePythonEnvTool(environmentsApi, serviceContainer, createVirtualEnvTool),
5650
),
5751
);
58-
ourTools.add(
59-
extensions.onDidChange(() => {
60-
const envExtension = extensions.getExtension(ENVS_EXTENSION_ID);
61-
if (envExtension) {
62-
envExtension.activate();
63-
commands.executeCommand('setContext', contextKey, true);
64-
ourTools.dispose();
65-
}
66-
}),
67-
);
6852
}

src/client/chat/installPackagesTool.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { resolveFilePath } from './utils';
2525
import { IModuleInstaller } from '../common/installer/types';
2626
import { ModuleInstallerType } from '../pythonEnvironments/info';
2727
import { IDiscoveryAPI } from '../pythonEnvironments/base/locator';
28+
import { getEnvExtApi, useEnvExtension } from '../envExt/api.internal';
2829

2930
export interface IInstallPackageArgs extends IResourceReference {
3031
packageList: string[];
@@ -50,6 +51,24 @@ export class InstallPackagesTool implements LanguageModelTool<IInstallPackageArg
5051
return notebookResponse;
5152
}
5253

54+
if (useEnvExtension()) {
55+
const api = await getEnvExtApi();
56+
const env = await api.getEnvironment(resourcePath);
57+
if (env) {
58+
await raceCancellationError(api.managePackages(env, { install: options.input.packageList }), token);
59+
const resultMessage = `Successfully installed ${packagePlurality}: ${options.input.packageList.join(
60+
', ',
61+
)}`;
62+
return new LanguageModelToolResult([new LanguageModelTextPart(resultMessage)]);
63+
} else {
64+
return new LanguageModelToolResult([
65+
new LanguageModelTextPart(
66+
`Packages not installed. No environment found for: ${resourcePath?.fsPath}`,
67+
),
68+
]);
69+
}
70+
}
71+
5372
try {
5473
// environment
5574
const envPath = this.api.getActiveEnvironmentPath(resourcePath);

src/client/chat/utils.ts

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { TerminalCodeExecutionProvider } from '../terminals/codeExecution/termin
1717
import { Conda } from '../pythonEnvironments/common/environmentManagers/conda';
1818
import { JUPYTER_EXTENSION_ID, NotebookCellScheme } from '../common/constants';
1919
import { dirname, join } from 'path';
20+
import { resolveEnvironment, useEnvExtension } from '../envExt/api.internal';
2021

2122
export interface IResourceReference {
2223
resourcePath?: string;
@@ -76,18 +77,46 @@ export async function getEnvironmentDetails(
7677
): Promise<string> {
7778
// environment
7879
const envPath = api.getActiveEnvironmentPath(resourcePath);
79-
const environment = await raceCancellationError(api.resolveEnvironment(envPath), token);
80-
if (!environment || !environment.version) {
81-
throw new Error('No environment found for the provided resource path: ' + resourcePath?.fsPath);
80+
let envType = '';
81+
let envVersion = '';
82+
let runCommand = '';
83+
if (useEnvExtension()) {
84+
const environment =
85+
(await raceCancellationError(resolveEnvironment(envPath.id), token)) ||
86+
(await raceCancellationError(resolveEnvironment(envPath.path), token));
87+
if (!environment || !environment.version) {
88+
throw new Error('No environment found for the provided resource path: ' + resourcePath?.fsPath);
89+
}
90+
envVersion = environment.version;
91+
try {
92+
const managerId = environment.envId.managerId;
93+
envType =
94+
(!managerId.endsWith(':') && managerId.includes(':') ? managerId.split(':').reverse()[0] : '') ||
95+
'unknown';
96+
} catch {
97+
envType = 'unknown';
98+
}
99+
100+
const execInfo = environment.execInfo;
101+
const executable = execInfo?.activatedRun?.executable ?? execInfo?.run.executable ?? 'python';
102+
const args = execInfo?.activatedRun?.args ?? execInfo?.run.args ?? [];
103+
runCommand = terminalHelper.buildCommandForTerminal(TerminalShellType.other, executable, args);
104+
} else {
105+
const environment = await raceCancellationError(api.resolveEnvironment(envPath), token);
106+
if (!environment || !environment.version) {
107+
throw new Error('No environment found for the provided resource path: ' + resourcePath?.fsPath);
108+
}
109+
envType = environment.environment?.type || 'unknown';
110+
envVersion = environment.version.sysVersion || 'unknown';
111+
runCommand = await raceCancellationError(
112+
getTerminalCommand(environment, resourcePath, terminalExecutionService, terminalHelper),
113+
token,
114+
);
82115
}
83-
const runCommand = await raceCancellationError(
84-
getTerminalCommand(environment, resourcePath, terminalExecutionService, terminalHelper),
85-
token,
86-
);
87116
const message = [
88117
`Following is the information about the Python environment:`,
89-
`1. Environment Type: ${environment.environment?.type || 'unknown'}`,
90-
`2. Version: ${environment.version.sysVersion || 'unknown'}`,
118+
`1. Environment Type: ${envType}`,
119+
`2. Version: ${envVersion}`,
91120
'',
92121
`3. Command Prefix to run Python in a terminal is: \`${runCommand}\``,
93122
`Instead of running \`Python sample.py\` in the terminal, you will now run: \`${runCommand} sample.py\``,
@@ -183,7 +212,8 @@ export function doesWorkspaceHaveVenvOrCondaEnv(resource: Uri | undefined, api:
183212
env.environment?.folderUri &&
184213
env.executable.sysPrefix &&
185214
dirname(env.executable.sysPrefix) === workspaceFolder.uri.fsPath &&
186-
env.environment.name === '.venv' &&
215+
((env.environment.name || '').startsWith('.venv') ||
216+
env.executable.sysPrefix === join(workspaceFolder.uri.fsPath, '.venv')) &&
187217
env.environment.type === 'VirtualEnvironment'
188218
);
189219
};
@@ -192,7 +222,8 @@ export function doesWorkspaceHaveVenvOrCondaEnv(resource: Uri | undefined, api:
192222
env.environment?.folderUri &&
193223
env.executable.sysPrefix &&
194224
dirname(env.executable.sysPrefix) === workspaceFolder.uri.fsPath &&
195-
env.environment.folderUri.fsPath === join(workspaceFolder.uri.fsPath, '.conda') &&
225+
(env.environment.folderUri.fsPath === join(workspaceFolder.uri.fsPath, '.conda') ||
226+
env.executable.sysPrefix === join(workspaceFolder.uri.fsPath, '.conda')) &&
196227
env.environment.type === 'Conda'
197228
);
198229
};

0 commit comments

Comments
 (0)