Skip to content

Commit a0d7fa7

Browse files
committed
All Python lm tools in Python extension
1 parent ef01ace commit a0d7fa7

File tree

9 files changed

+162
-60
lines changed

9 files changed

+162
-60
lines changed

package.json

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1491,8 +1491,7 @@
14911491
}
14921492
},
14931493
"required": []
1494-
},
1495-
"when": "!pythonEnvExtensionInstalled"
1494+
}
14961495
},
14971496
{
14981497
"name": "get_python_executable_details",
@@ -1516,8 +1515,7 @@
15161515
}
15171516
},
15181517
"required": []
1519-
},
1520-
"when": "!pythonEnvExtensionInstalled"
1518+
}
15211519
},
15221520
{
15231521
"name": "install_python_packages",
@@ -1551,8 +1549,7 @@
15511549
"required": [
15521550
"packageList"
15531551
]
1554-
},
1555-
"when": "!pythonEnvExtensionInstalled"
1552+
}
15561553
},
15571554
{
15581555
"name": "configure_python_environment",
@@ -1575,8 +1572,7 @@
15751572
}
15761573
},
15771574
"required": []
1578-
},
1579-
"when": "!pythonEnvExtensionInstalled"
1575+
}
15801576
},
15811577
{
15821578
"name": "create_virtual_environment",

src/client/api.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { ApiForPylance } from './pylanceApi';
2727
import { getTelemetryReporter } from './telemetry';
2828
import { TensorboardExtensionIntegration } from './tensorBoard/tensorboardIntegration';
2929
import { getDebugpyPath } from './debugger/pythonDebugger';
30+
import { registerApi } from './envExt/api.internal';
3031

3132
export function buildApi(
3233
ready: Promise<void>,
@@ -61,6 +62,12 @@ export function buildApi(
6162
jupyter: {
6263
registerHooks(): void;
6364
};
65+
/**
66+
* Internal API just for Python Env Extension untill the API is stabilized.
67+
*/
68+
pythonEnvironment: {
69+
registerApi: typeof registerApi;
70+
};
6471
/**
6572
* Internal API just for Tensorboard, hence don't include in the official types.
6673
*/
@@ -117,6 +124,9 @@ export function buildApi(
117124
jupyter: {
118125
registerHooks: () => jupyterIntegration.integrateWithJupyterExtension(),
119126
},
127+
pythonEnvironment: {
128+
registerApi: registerApi,
129+
},
120130
tensorboard: {
121131
registerHooks: () => tensorboardIntegration.integrateWithTensorboardExtension(),
122132
},

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: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import { getEnvironmentDetails, getToolResponseIfNotebook, IResourceReference, r
2020
import { resolveFilePath } from './utils';
2121
import { getPythonPackagesResponse } from './listPackagesTool';
2222
import { ITerminalHelper } from '../common/terminal/types';
23+
import { isPrivateApiRegistered, listPackages, useEnvExtension } from '../envExt/api.internal';
24+
import { traceError } from '../logging';
2325

2426
export class GetEnvironmentInfoTool implements LanguageModelTool<IResourceReference> {
2527
private readonly terminalExecutionService: TerminalCodeExecutionProvider;
@@ -56,14 +58,35 @@ export class GetEnvironmentInfoTool implements LanguageModelTool<IResourceRefere
5658
if (!environment || !environment.version) {
5759
throw new Error('No environment found for the provided resource path: ' + resourcePath?.fsPath);
5860
}
59-
const packages = await getPythonPackagesResponse(
60-
environment,
61-
this.pythonExecFactory,
62-
this.processServiceFactory,
63-
resourcePath,
64-
token,
65-
);
6661

62+
let packages = '';
63+
if (useEnvExtension() && isPrivateApiRegistered()) {
64+
try {
65+
const pkgs = await listPackages(resourcePath, token);
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+
} catch (ex) {
78+
traceError(`Error invoking list_install_python_package_ex tool: ${ex}`);
79+
}
80+
}
81+
if (!packages) {
82+
packages = await getPythonPackagesResponse(
83+
environment,
84+
this.pythonExecFactory,
85+
this.processServiceFactory,
86+
resourcePath,
87+
token,
88+
);
89+
}
6790
const message = await getEnvironmentDetails(
6891
resourcePath,
6992
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: 7 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 { installPackages, isPrivateApiRegistered, useEnvExtension } from '../envExt/api.internal';
2829

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

54+
if (useEnvExtension() && isPrivateApiRegistered()) {
55+
await installPackages(resourcePath, options.input.packageList, token);
56+
const resultMessage = `Successfully installed ${packagePlurality}: ${options.input.packageList.join(', ')}`;
57+
return new LanguageModelToolResult([new LanguageModelTextPart(resultMessage)]);
58+
}
59+
5360
try {
5461
// environment
5562
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)