Skip to content

Handle cases when failing to resolve env #25095

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 55 additions & 44 deletions src/client/chat/configurePythonEnvTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { PythonExtension, ResolvedEnvironment } from '../api/types';
import { IServiceContainer } from '../ioc/types';
import { ICodeExecutionService } from '../terminals/types';
import { TerminalCodeExecutionProvider } from '../terminals/codeExecution/terminalCodeExecution';
import { getEnvironmentDetails, raceCancellationError } from './utils';
import { getEnvironmentDetails, NoEnvironmentError, raceCancellationError } from './utils';
import { resolveFilePath } from './utils';
import { IRecommendedEnvironmentService } from '../interpreter/configuration/types';
import { ITerminalHelper } from '../common/terminal/types';
Expand Down Expand Up @@ -67,33 +67,51 @@ export class ConfigurePythonEnvTool implements LanguageModelTool<IResourceRefere
options: LanguageModelToolInvocationOptions<IResourceReference>,
token: CancellationToken,
): Promise<LanguageModelToolResult> {
const resource = resolveFilePath(options.input.resourcePath);
const recommededEnv = await this.recommendedEnvService.getRecommededEnvironment(resource);
// Already selected workspace env, hence nothing to do.
if (recommededEnv?.reason === 'workspaceUserSelected' && workspace.workspaceFolders?.length) {
return await getEnvDetailsForResponse(
recommededEnv.environment,
this.api,
this.terminalExecutionService,
this.terminalHelper,
resource,
token,
);
}
// No workspace folders, and the user selected a global environment.
if (recommededEnv?.reason === 'globalUserSelected' && !workspace.workspaceFolders?.length) {
return await getEnvDetailsForResponse(
recommededEnv.environment,
this.api,
this.terminalExecutionService,
this.terminalHelper,
resource,
token,
);
}
try {
const resource = resolveFilePath(options.input.resourcePath);
const recommededEnv = await this.recommendedEnvService.getRecommededEnvironment(resource);
// Already selected workspace env, hence nothing to do.
if (recommededEnv?.reason === 'workspaceUserSelected' && workspace.workspaceFolders?.length) {
return await getEnvDetailsForResponse(
recommededEnv.environment,
this.api,
this.terminalExecutionService,
this.terminalHelper,
resource,
token,
);
}
// No workspace folders, and the user selected a global environment.
if (recommededEnv?.reason === 'globalUserSelected' && !workspace.workspaceFolders?.length) {
return await getEnvDetailsForResponse(
recommededEnv.environment,
this.api,
this.terminalExecutionService,
this.terminalHelper,
resource,
token,
);
}

if (!workspace.workspaceFolders?.length) {
const selected = await Promise.resolve(commands.executeCommand(Commands.Set_Interpreter));
if (!workspace.workspaceFolders?.length) {
const selected = await Promise.resolve(commands.executeCommand(Commands.Set_Interpreter));
const env = await this.api.resolveEnvironment(this.api.getActiveEnvironmentPath(resource));
if (selected && env) {
return await getEnvDetailsForResponse(
env,
this.api,
this.terminalExecutionService,
this.terminalHelper,
resource,
token,
);
}
return new LanguageModelToolResult([
new LanguageModelTextPart('User did not select a Python environment.'),
]);
}

const selected = await showCreateAndSelectEnvironmentQuickPick(resource, this.serviceContainer);
const env = await this.api.resolveEnvironment(this.api.getActiveEnvironmentPath(resource));
if (selected && env) {
return await getEnvDetailsForResponse(
Expand All @@ -106,25 +124,18 @@ export class ConfigurePythonEnvTool implements LanguageModelTool<IResourceRefere
);
}
return new LanguageModelToolResult([
new LanguageModelTextPart('User did not select a Python environment.'),
new LanguageModelTextPart('User did not create nor select a Python environment.'),
]);
} catch (ex) {
if (ex instanceof NoEnvironmentError) {
return new LanguageModelToolResult([
new LanguageModelTextPart(
'Failed to configure a Python Environment, as the environment could not be found.',
),
]);
}
throw ex;
}

const selected = await showCreateAndSelectEnvironmentQuickPick(resource, this.serviceContainer);
const env = await this.api.resolveEnvironment(this.api.getActiveEnvironmentPath(resource));
if (selected && env) {
return await getEnvDetailsForResponse(
env,
this.api,
this.terminalExecutionService,
this.terminalHelper,
resource,
token,
);
}
return new LanguageModelToolResult([
new LanguageModelTextPart('User did not create nor select a Python environment.'),
]);
}

async prepareInvocation?(
Expand Down
10 changes: 9 additions & 1 deletion src/client/chat/getExecutableTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { PythonExtension } from '../api/types';
import { IServiceContainer } from '../ioc/types';
import { ICodeExecutionService } from '../terminals/types';
import { TerminalCodeExecutionProvider } from '../terminals/codeExecution/terminalCodeExecution';
import { getEnvDisplayName, getEnvironmentDetails, raceCancellationError } from './utils';
import { getEnvDisplayName, getEnvironmentDetails, NoEnvironmentError, raceCancellationError } from './utils';
import { resolveFilePath } from './utils';
import { traceError } from '../logging';
import { ITerminalHelper } from '../common/terminal/types';
Expand Down Expand Up @@ -58,6 +58,14 @@ export class GetExecutableTool implements LanguageModelTool<IResourceReference>
);
return new LanguageModelToolResult([new LanguageModelTextPart(message)]);
} catch (error) {
if (error instanceof NoEnvironmentError) {
return new LanguageModelToolResult([
new LanguageModelTextPart(
'Failed to configure a Python Environment, as the environment could not be found.',
),
]);
}

if (error instanceof CancellationError) {
throw error;
}
Expand Down
9 changes: 8 additions & 1 deletion src/client/chat/getPythonEnvTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { IServiceContainer } from '../ioc/types';
import { ICodeExecutionService } from '../terminals/types';
import { TerminalCodeExecutionProvider } from '../terminals/codeExecution/terminalCodeExecution';
import { IProcessServiceFactory, IPythonExecutionFactory } from '../common/process/types';
import { getEnvironmentDetails, raceCancellationError } from './utils';
import { getEnvironmentDetails, NoEnvironmentError, raceCancellationError } from './utils';
import { resolveFilePath } from './utils';
import { getPythonPackagesResponse } from './listPackagesTool';
import { ITerminalHelper } from '../common/terminal/types';
Expand Down Expand Up @@ -82,6 +82,13 @@ export class GetEnvironmentInfoTool implements LanguageModelTool<IResourceRefere

return new LanguageModelToolResult([new LanguageModelTextPart(message)]);
} catch (error) {
if (error instanceof NoEnvironmentError) {
return new LanguageModelToolResult([
new LanguageModelTextPart(
'Failed to configure a Python Environment, as the environment could not be found.',
),
]);
}
if (error instanceof CancellationError) {
throw error;
}
Expand Down
12 changes: 10 additions & 2 deletions src/client/chat/installPackagesTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
} from 'vscode';
import { PythonExtension } from '../api/types';
import { IServiceContainer } from '../ioc/types';
import { getEnvDisplayName, raceCancellationError } from './utils';
import { getEnvDisplayName, NoEnvironmentError, raceCancellationError } from './utils';
import { resolveFilePath } from './utils';
import { IModuleInstaller } from '../common/installer/types';
import { ModuleInstallerType } from '../pythonEnvironments/info';
Expand Down Expand Up @@ -51,7 +51,7 @@ export class InstallPackagesTool implements LanguageModelTool<IInstallPackageArg
const envPath = this.api.getActiveEnvironmentPath(resourcePath);
const environment = await raceCancellationError(this.api.resolveEnvironment(envPath), token);
if (!environment || !environment.version) {
throw new Error('No environment found for the provided resource path: ' + resourcePath?.fsPath);
throw new NoEnvironmentError('No environment found');
}
const isConda = (environment.environment?.type || '').toLowerCase() === 'conda';
const installers = this.serviceContainer.getAll<IModuleInstaller>(IModuleInstaller);
Expand All @@ -70,6 +70,14 @@ export class InstallPackagesTool implements LanguageModelTool<IInstallPackageArg
const resultMessage = `Successfully installed ${packagePlurality}: ${options.input.packageList.join(', ')}`;
return new LanguageModelToolResult([new LanguageModelTextPart(resultMessage)]);
} catch (error) {
if (error instanceof NoEnvironmentError) {
return new LanguageModelToolResult([
new LanguageModelTextPart(
'Failed to configure a Python Environment, as the environment could not be found.',
),
]);
}

if (error instanceof CancellationError) {
throw error;
}
Expand Down
30 changes: 28 additions & 2 deletions src/client/chat/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import { CancellationError, CancellationToken, Uri, workspace } from 'vscode';
import {
CancellationError,
CancellationToken,
LanguageModelTextPart,
LanguageModelToolResult,
Uri,
workspace,
} from 'vscode';
import { IDiscoveryAPI } from '../pythonEnvironments/base/locator';
import { PythonExtension, ResolvedEnvironment } from '../api/types';
import { ITerminalHelper, TerminalShellType } from '../common/terminal/types';
Expand Down Expand Up @@ -52,6 +59,12 @@ export function isCondaEnv(env: ResolvedEnvironment) {
return (env.environment?.type || '').toLowerCase() === 'conda';
}

export class NoEnvironmentError extends Error {
constructor(message: string) {
super(message);
}
}

export async function getEnvironmentDetails(
resourcePath: Uri | undefined,
api: PythonExtension['environments'],
Expand All @@ -64,7 +77,7 @@ export async function getEnvironmentDetails(
const envPath = api.getActiveEnvironmentPath(resourcePath);
const environment = await raceCancellationError(api.resolveEnvironment(envPath), token);
if (!environment || !environment.version) {
throw new Error('No environment found for the provided resource path: ' + resourcePath?.fsPath);
throw new NoEnvironmentError('No environment found for the provided resource path: ' + resourcePath?.fsPath);
}
const runCommand = await raceCancellationError(
getTerminalCommand(environment, resourcePath, terminalExecutionService, terminalHelper),
Expand Down Expand Up @@ -115,3 +128,16 @@ async function getCondaRunCommand(environment: ResolvedEnvironment) {
}
return { command: cmd[0], args: cmd.slice(1) };
}

function throwErrorIfUriIsNotebook(uri: Uri | undefined) {
if (!uri) {
return;
}
if (
uri.scheme === 'vscode-notebook-cell' ||
uri.fsPath.endsWith('.ipynb') ||
workspace.notebookDocuments.some((doc) => doc.uri.fsPath === uri.fsPath)
) {
throw new Error('This tool does not support Notebooks, use a relevant tool instead.');
}
}
Loading