forked from DonJayamanne/pythonVSCode
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Expand file tree
/
Copy pathlistPackagesTool.ts
More file actions
174 lines (163 loc) · 7.18 KB
/
listPackagesTool.ts
File metadata and controls
174 lines (163 loc) · 7.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import {
CancellationError,
CancellationToken,
l10n,
LanguageModelTextPart,
LanguageModelTool,
LanguageModelToolInvocationOptions,
LanguageModelToolInvocationPrepareOptions,
LanguageModelToolResult,
PreparedToolInvocation,
Uri,
} from 'vscode';
import { PythonExtension, ResolvedEnvironment } from '../api/types';
import { IServiceContainer } from '../ioc/types';
import { IProcessService, IProcessServiceFactory, IPythonExecutionFactory } from '../common/process/types';
import { getEnvDisplayName, isCondaEnv, raceCancellationError } from './utils';
import { resolveFilePath } from './utils';
import { parsePipList } from './pipListUtils';
import { Conda } from '../pythonEnvironments/common/environmentManagers/conda';
import { traceError } from '../logging';
import { IDiscoveryAPI } from '../pythonEnvironments/base/locator';
import { trackEnvUsedByTool } from './lastUsedEnvs';
export interface IResourceReference {
resourcePath?: string;
}
export class ListPythonPackagesTool implements LanguageModelTool<IResourceReference> {
private readonly pythonExecFactory: IPythonExecutionFactory;
private readonly processServiceFactory: IProcessServiceFactory;
public static readonly toolName = 'list_python_packages';
constructor(
private readonly api: PythonExtension['environments'],
private readonly serviceContainer: IServiceContainer,
private readonly discovery: IDiscoveryAPI,
) {
this.pythonExecFactory = this.serviceContainer.get<IPythonExecutionFactory>(IPythonExecutionFactory);
this.processServiceFactory = this.serviceContainer.get<IProcessServiceFactory>(IProcessServiceFactory);
}
async invoke(
options: LanguageModelToolInvocationOptions<IResourceReference>,
token: CancellationToken,
): Promise<LanguageModelToolResult> {
const resourcePath = resolveFilePath(options.input.resourcePath);
try {
// environment
const envPath = this.api.getActiveEnvironmentPath(resourcePath);
const environment = await raceCancellationError(this.api.resolveEnvironment(envPath), token);
if (!environment) {
throw new Error('No environment found for the provided resource path: ' + resourcePath?.fsPath);
}
const message = await getPythonPackagesResponse(
environment,
this.pythonExecFactory,
this.processServiceFactory,
resourcePath,
token,
);
return new LanguageModelToolResult([new LanguageModelTextPart(message)]);
} catch (error) {
if (error instanceof CancellationError) {
throw error;
}
return new LanguageModelToolResult([
new LanguageModelTextPart(`An error occurred while fetching environment information: ${error}`),
]);
}
}
async prepareInvocation?(
options: LanguageModelToolInvocationPrepareOptions<IResourceReference>,
token: CancellationToken,
): Promise<PreparedToolInvocation> {
const resourcePath = resolveFilePath(options.input.resourcePath);
const envName = await raceCancellationError(getEnvDisplayName(this.discovery, resourcePath, this.api), token);
return {
invocationMessage: envName
? l10n.t('Listing packages in {0}', envName)
: l10n.t('Fetching Python environment information'),
};
}
}
export async function getPythonPackagesResponse(
environment: ResolvedEnvironment,
pythonExecFactory: IPythonExecutionFactory,
processServiceFactory: IProcessServiceFactory,
resourcePath: Uri | undefined,
token: CancellationToken,
): Promise<string> {
const packages = isCondaEnv(environment)
? await raceCancellationError(
listCondaPackages(
pythonExecFactory,
environment,
resourcePath,
await raceCancellationError(processServiceFactory.create(resourcePath), token),
),
token,
)
: await raceCancellationError(listPipPackages(pythonExecFactory, resourcePath), token);
if (!packages.length) {
return 'No packages found';
}
trackEnvUsedByTool(resourcePath, environment);
// 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.
const response = [
'Below is a list of the Python packages, each in the format <name> or <name> (<version>). The version may be omitted if unknown: ',
];
packages.forEach((pkg) => {
const [name, version] = pkg;
response.push(version ? `- ${name} (${version})` : `- ${name}`);
});
return response.join('\n');
}
async function listPipPackages(
execFactory: IPythonExecutionFactory,
resource: Uri | undefined,
): Promise<[string, string][]> {
// Add option --format to subcommand list of pip cache, with abspath choice to output the full path of a wheel file. (#8355)
// Added in 202. Thats almost 5 years ago. When Python 3.8 was released.
const exec = await execFactory.createActivatedEnvironment({ allowEnvironmentFetchExceptions: true, resource });
const output = await exec.execModule('pip', ['list'], { throwOnStdErr: false, encoding: 'utf8' });
return parsePipList(output.stdout).map((pkg) => [pkg.name, pkg.version]);
}
async function listCondaPackages(
execFactory: IPythonExecutionFactory,
env: ResolvedEnvironment,
resource: Uri | undefined,
processService: IProcessService,
): Promise<[string, string][]> {
const conda = await Conda.getConda();
if (!conda) {
traceError('Conda is not installed, falling back to pip packages');
return listPipPackages(execFactory, resource);
}
if (!env.executable.uri) {
traceError('Conda environment executable not found, falling back to pip packages');
return listPipPackages(execFactory, resource);
}
const condaEnv = await conda.getCondaEnvironment(env.executable.uri.fsPath);
if (!condaEnv) {
traceError('Conda environment not found, falling back to pip packages');
return listPipPackages(execFactory, resource);
}
const cmd = await conda.getListPythonPackagesArgs(condaEnv, true);
if (!cmd) {
traceError('Conda list command not found, falling back to pip packages');
return listPipPackages(execFactory, resource);
}
const output = await processService.exec(cmd[0], cmd.slice(1), { shell: true });
if (!output.stdout) {
traceError('Unable to get conda packages, falling back to pip packages');
return listPipPackages(execFactory, resource);
}
const content = output.stdout.split(/\r?\n/).filter((l) => !l.startsWith('#'));
const packages: [string, string][] = [];
content.forEach((l) => {
const parts = l.split(' ').filter((p) => p.length > 0);
if (parts.length >= 3) {
packages.push([parts[0], parts[1]]);
}
});
return packages;
}