Skip to content

Commit 6a42167

Browse files
committed
Updates
1 parent f2777c5 commit 6a42167

File tree

5 files changed

+118
-181
lines changed

5 files changed

+118
-181
lines changed

package.json

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1466,9 +1466,9 @@
14661466
"languageModelTools": [
14671467
{
14681468
"name": "get_python_environment_info",
1469-
"displayName": "Get Python Environment Information",
1470-
"userDescription": "%python.languageModelTools.python_environment.userDescription%",
1471-
"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. this tool gets details about the Python environment including environment type, Python version, command used to execute Python, and a list of all installed packages with their versions. Use this tool to determine the correct command for executing Python in a terminal.",
1469+
"displayName": "Get Python Environment Info",
1470+
"userDescription": "%python.languageModelTools.get_python_environment_info.userDescription%",
1471+
"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. ",
14721472
"toolReferenceName": "pythonGetEnvironmentInfo",
14731473
"tags": [
14741474
"ms-python.python"
@@ -1486,8 +1486,7 @@
14861486
"required": [
14871487
"resourcePath"
14881488
]
1489-
},
1490-
"when": "false"
1489+
}
14911490
},
14921491
{
14931492
"name": "get_python_executable",
@@ -1509,8 +1508,7 @@
15091508
},
15101509
"description": "The path to the Python file or workspace to get the executable information for. If not provided, the current workspace will be used. Where possible pass the path to the file or workspace.",
15111510
"required": []
1512-
},
1513-
"when": "!pythonEnvExtensionInstalled"
1511+
}
15141512
},
15151513
{
15161514
"name": "list_python_packages",
@@ -1533,7 +1531,7 @@
15331531
"description": "The path to the Python file or workspace to list the packages. If not provided, the current workspace will be used. Where possible pass the path to the file or workspace.",
15341532
"required": []
15351533
},
1536-
"when": "!pythonEnvExtensionInstalled"
1534+
"when": "false"
15371535
},
15381536
{
15391537
"name": "python_install_package",

package.nls.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"python.command.python.startTerminalREPL.title": "Start Terminal REPL",
3-
"python.languageModelTools.python_environment.userDescription": "Get Python Environment info for a file or path, including version, packages, and the command to run it.",
3+
"python.languageModelTools.get_python_environment_info.userDescription": "Get information for a Python Environment, such as Type, Version, Packages, and more.",
44
"python.languageModelTools.python_install_package.userDescription": "Installs Python packages in a Python Environment.",
55
"python.languageModelTools.get_python_executable.userDescription": "Get executable info for a Python Environment",
66
"python.languageModelTools.list_python_packages.userDescription": "Get a list of all installed packages in a Python Environment.",

src/client/chat/getExecutableTool.ts

Lines changed: 37 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,10 @@ export class GetExecutableTool implements LanguageModelTool<IResourceReference>
5656
if (!environment || !environment.version) {
5757
throw new Error('No environment found for the provided resource path: ' + resourcePath?.fsPath);
5858
}
59-
const runCommand = await raceCancellationError(this.getTerminalCommand(environment, resourcePath), token);
59+
const runCommand = await raceCancellationError(
60+
getTerminalCommand(environment, resourcePath, this.terminalExecutionService, this.terminalHelper),
61+
token,
62+
);
6063

6164
const message = [
6265
`Following is the information about the Python environment:`,
@@ -78,37 +81,6 @@ export class GetExecutableTool implements LanguageModelTool<IResourceReference>
7881
}
7982
}
8083

81-
private async getTerminalCommand(environment: ResolvedEnvironment, resource?: Uri) {
82-
let cmd: { command: string; args: string[] };
83-
if (isCondaEnv(environment)) {
84-
cmd =
85-
(await this.getCondaRunCommand(environment)) ||
86-
(await this.terminalExecutionService.getExecutableInfo(resource));
87-
} else {
88-
cmd = await this.terminalExecutionService.getExecutableInfo(resource);
89-
}
90-
return this.terminalHelper.buildCommandForTerminal(TerminalShellType.other, cmd.command, cmd.args);
91-
}
92-
93-
private async getCondaRunCommand(environment: ResolvedEnvironment) {
94-
if (!environment.executable.uri) {
95-
return;
96-
}
97-
const conda = await Conda.getConda();
98-
if (!conda) {
99-
return;
100-
}
101-
const condaEnv = await conda.getCondaEnvironment(environment.executable.uri?.fsPath);
102-
if (!condaEnv) {
103-
return;
104-
}
105-
const cmd = await conda.getRunPythonArgs(condaEnv, true, false);
106-
if (!cmd) {
107-
return;
108-
}
109-
return { command: cmd[0], args: cmd.slice(1) };
110-
}
111-
11284
async prepareInvocation?(
11385
options: LanguageModelToolInvocationPrepareOptions<IResourceReference>,
11486
token: CancellationToken,
@@ -122,3 +94,36 @@ export class GetExecutableTool implements LanguageModelTool<IResourceReference>
12294
};
12395
}
12496
}
97+
98+
export async function getTerminalCommand(
99+
environment: ResolvedEnvironment,
100+
resource: Uri | undefined,
101+
terminalExecutionService: TerminalCodeExecutionProvider,
102+
terminalHelper: ITerminalHelper,
103+
): Promise<string> {
104+
let cmd: { command: string; args: string[] };
105+
if (isCondaEnv(environment)) {
106+
cmd = (await getCondaRunCommand(environment)) || (await terminalExecutionService.getExecutableInfo(resource));
107+
} else {
108+
cmd = await terminalExecutionService.getExecutableInfo(resource);
109+
}
110+
return terminalHelper.buildCommandForTerminal(TerminalShellType.other, cmd.command, cmd.args);
111+
}
112+
async function getCondaRunCommand(environment: ResolvedEnvironment) {
113+
if (!environment.executable.uri) {
114+
return;
115+
}
116+
const conda = await Conda.getConda();
117+
if (!conda) {
118+
return;
119+
}
120+
const condaEnv = await conda.getCondaEnvironment(environment.executable.uri?.fsPath);
121+
if (!condaEnv) {
122+
return;
123+
}
124+
const cmd = await conda.getRunPythonArgs(condaEnv, true, false);
125+
if (!cmd) {
126+
return;
127+
}
128+
return { command: cmd[0], args: cmd.slice(1) };
129+
}

src/client/chat/getPythonEnvTool.ts

Lines changed: 32 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -11,37 +11,27 @@ import {
1111
LanguageModelToolInvocationPrepareOptions,
1212
LanguageModelToolResult,
1313
PreparedToolInvocation,
14-
Uri,
1514
} from 'vscode';
16-
import { PythonExtension, ResolvedEnvironment } from '../api/types';
15+
import { PythonExtension } from '../api/types';
1716
import { IServiceContainer } from '../ioc/types';
1817
import { ICodeExecutionService } from '../terminals/types';
1918
import { TerminalCodeExecutionProvider } from '../terminals/codeExecution/terminalCodeExecution';
20-
import { IProcessService, IProcessServiceFactory, IPythonExecutionFactory } from '../common/process/types';
19+
import { IProcessServiceFactory, IPythonExecutionFactory } from '../common/process/types';
2120
import { raceCancellationError } from './utils';
2221
import { resolveFilePath } from './utils';
23-
import { parsePipList } from './pipListUtils';
24-
import { Conda } from '../pythonEnvironments/common/environmentManagers/conda';
25-
import { traceError } from '../logging';
22+
import { getPythonPackagesResponse } from './listPackagesTool';
23+
import { getTerminalCommand } from './getExecutableTool';
24+
import { ITerminalHelper } from '../common/terminal/types';
2625

2726
export interface IResourceReference {
2827
resourcePath?: string;
2928
}
3029

31-
interface EnvironmentInfo {
32-
type: string; // e.g. conda, venv, virtualenv, sys
33-
version: string;
34-
runCommand: string;
35-
packages: string[] | string; //include versions too
36-
}
37-
38-
/**
39-
* A tool to get the information about the Python environment.
40-
*/
4130
export class GetEnvironmentInfoTool implements LanguageModelTool<IResourceReference> {
4231
private readonly terminalExecutionService: TerminalCodeExecutionProvider;
4332
private readonly pythonExecFactory: IPythonExecutionFactory;
4433
private readonly processServiceFactory: IProcessServiceFactory;
34+
private readonly terminalHelper: ITerminalHelper;
4535
public static readonly toolName = 'get_python_environment_info';
4636
constructor(
4737
private readonly api: PythonExtension['environments'],
@@ -53,6 +43,7 @@ export class GetEnvironmentInfoTool implements LanguageModelTool<IResourceRefere
5343
);
5444
this.pythonExecFactory = this.serviceContainer.get<IPythonExecutionFactory>(IPythonExecutionFactory);
5545
this.processServiceFactory = this.serviceContainer.get<IProcessServiceFactory>(IProcessServiceFactory);
46+
this.terminalHelper = this.serviceContainer.get<ITerminalHelper>(ITerminalHelper);
5647
}
5748
/**
5849
* Invokes the tool to get the information about the Python environment.
@@ -66,53 +57,45 @@ export class GetEnvironmentInfoTool implements LanguageModelTool<IResourceRefere
6657
): Promise<LanguageModelToolResult> {
6758
const resourcePath = resolveFilePath(options.input.resourcePath);
6859

69-
// environment info set to default values
70-
const envInfo: EnvironmentInfo = {
71-
type: 'no type found',
72-
version: 'no version found',
73-
packages: 'no packages found',
74-
runCommand: 'no run command found',
75-
};
76-
7760
try {
7861
// environment
7962
const envPath = this.api.getActiveEnvironmentPath(resourcePath);
8063
const environment = await raceCancellationError(this.api.resolveEnvironment(envPath), token);
8164
if (!environment || !environment.version) {
8265
throw new Error('No environment found for the provided resource path: ' + resourcePath?.fsPath);
8366
}
84-
const cmd = await raceCancellationError(
85-
this.terminalExecutionService.getExecutableInfo(resourcePath),
86-
token,
87-
);
88-
const executable = cmd.pythonExecutable;
89-
envInfo.runCommand = cmd.args.length > 0 ? `${cmd.command} ${cmd.args.join(' ')}` : executable;
90-
envInfo.version = environment.version.sysVersion;
67+
const [packages, runCommand] = await Promise.all([
68+
getPythonPackagesResponse(
69+
environment,
70+
this.pythonExecFactory,
71+
this.processServiceFactory,
72+
resourcePath,
73+
token,
74+
),
75+
raceCancellationError(
76+
getTerminalCommand(environment, resourcePath, this.terminalExecutionService, this.terminalHelper),
77+
token,
78+
),
79+
]);
9180

92-
const isConda = (environment.environment?.type || '').toLowerCase() === 'conda';
93-
envInfo.packages = isConda
94-
? await raceCancellationError(
95-
listCondaPackages(
96-
this.pythonExecFactory,
97-
environment,
98-
resourcePath,
99-
await raceCancellationError(this.processServiceFactory.create(resourcePath), token),
100-
),
101-
token,
102-
)
103-
: await raceCancellationError(listPipPackages(this.pythonExecFactory, resourcePath), token);
81+
const message = [
82+
`Following is the information about the Python environment:`,
83+
`1. Environment Type: ${environment.environment?.type || 'unknown'}`,
84+
`2. Version: ${environment.version.sysVersion || 'unknown'}`,
85+
'',
86+
`3. Command Prefix to run Python in a terminal is: \`${runCommand}\``,
87+
`Instead of running \`Python sample.py\` in the terminal, you will now run: \`${runCommand} sample.py\``,
88+
`Similarly, instead of running \`Python -c "import sys;...."\` in the terminal, you will now run: \`${runCommand} -c "import sys;...."\``,
89+
`4. ${packages}`,
90+
];
10491

105-
// format and return
106-
return new LanguageModelToolResult([BuildEnvironmentInfoContent(envInfo)]);
92+
return new LanguageModelToolResult([new LanguageModelTextPart(message.join('\n'))]);
10793
} catch (error) {
10894
if (error instanceof CancellationError) {
10995
throw error;
11096
}
11197
const errorMessage: string = `An error occurred while fetching environment information: ${error}`;
112-
const partialContent = BuildEnvironmentInfoContent(envInfo);
113-
return new LanguageModelToolResult([
114-
new LanguageModelTextPart(`${errorMessage}\n\n${partialContent.value}`),
115-
]);
98+
return new LanguageModelToolResult([new LanguageModelTextPart(errorMessage)]);
11699
}
117100
}
118101

@@ -125,69 +108,3 @@ export class GetEnvironmentInfoTool implements LanguageModelTool<IResourceRefere
125108
};
126109
}
127110
}
128-
129-
function BuildEnvironmentInfoContent(envInfo: EnvironmentInfo): LanguageModelTextPart {
130-
// Create a formatted string that looks like JSON but preserves comments
131-
const envTypeDescriptor: string = `This environment is managed by ${envInfo.type} environment manager. Use the install tool to install packages into this environment.`;
132-
const content = `{
133-
// ${JSON.stringify(envTypeDescriptor)}
134-
"environmentType": ${JSON.stringify(envInfo.type)},
135-
// Python version of the environment
136-
"pythonVersion": ${JSON.stringify(envInfo.version)},
137-
// Use this command to run Python script or code in the terminal.
138-
"runCommand": ${JSON.stringify(envInfo.runCommand)},
139-
// 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.
140-
"packages": ${JSON.stringify(Array.isArray(envInfo.packages) ? envInfo.packages : envInfo.packages, null, 2)}
141-
}`;
142-
143-
return new LanguageModelTextPart(content);
144-
}
145-
146-
async function listPipPackages(execFactory: IPythonExecutionFactory, resource: Uri | undefined) {
147-
// Add option --format to subcommand list of pip cache, with abspath choice to output the full path of a wheel file. (#8355)
148-
// Added in 202. Thats almost 5 years ago. When Python 3.8 was released.
149-
const exec = await execFactory.createActivatedEnvironment({ allowEnvironmentFetchExceptions: true, resource });
150-
const output = await exec.execModule('pip', ['list'], { throwOnStdErr: false, encoding: 'utf8' });
151-
return parsePipList(output.stdout).map((pkg) => (pkg.version ? `${pkg.name} (${pkg.version})` : pkg.name));
152-
}
153-
154-
async function listCondaPackages(
155-
execFactory: IPythonExecutionFactory,
156-
env: ResolvedEnvironment,
157-
resource: Uri | undefined,
158-
processService: IProcessService,
159-
) {
160-
const conda = await Conda.getConda();
161-
if (!conda) {
162-
traceError('Conda is not installed, falling back to pip packages');
163-
return listPipPackages(execFactory, resource);
164-
}
165-
if (!env.executable.uri) {
166-
traceError('Conda environment executable not found, falling back to pip packages');
167-
return listPipPackages(execFactory, resource);
168-
}
169-
const condaEnv = await conda.getCondaEnvironment(env.executable.uri.fsPath);
170-
if (!condaEnv) {
171-
traceError('Conda environment not found, falling back to pip packages');
172-
return listPipPackages(execFactory, resource);
173-
}
174-
const cmd = await conda.getListPythonPackagesArgs(condaEnv, true);
175-
if (!cmd) {
176-
traceError('Conda list command not found, falling back to pip packages');
177-
return listPipPackages(execFactory, resource);
178-
}
179-
const output = await processService.exec(cmd[0], cmd.slice(1), { shell: true });
180-
if (!output.stdout) {
181-
traceError('Unable to get conda packages, falling back to pip packages');
182-
return listPipPackages(execFactory, resource);
183-
}
184-
const content = output.stdout.split(/\r?\n/).filter((l) => !l.startsWith('#'));
185-
const packages: string[] = [];
186-
content.forEach((l) => {
187-
const parts = l.split(' ').filter((p) => p.length > 0);
188-
if (parts.length === 3) {
189-
packages.push(`${parts[0]} (${parts[1]})`);
190-
}
191-
});
192-
return packages;
193-
}

0 commit comments

Comments
 (0)