Skip to content

Update runInTerminalTool to avoid using state when recommending other tools #259460

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

Merged
merged 1 commit into from
Aug 5, 2025
Merged
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
2 changes: 2 additions & 0 deletions src/vs/workbench/contrib/chat/common/chatService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,8 @@ export interface IChatTerminalToolInvocationData {
userEdited?: string;
toolEdited?: string;
};
/** Message for model recommending the use of an alternative tool */
alternativeRecommendation?: string;
language: string;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import type { ILanguageModelToolsService, IToolResult } from '../../../chat/common/languageModelToolsService.js';
import type { ILanguageModelToolsService } from '../../../chat/common/languageModelToolsService.js';

let previouslyRecommededInSession = false;

Expand All @@ -26,7 +26,7 @@ const terminalCommands: { commands: RegExp[]; tags: string[] }[] = [
}
];

export function getRecommendedToolsOverRunInTerminal(commandLine: string, languageModelToolsService: ILanguageModelToolsService): IToolResult | undefined {
export function getRecommendedToolsOverRunInTerminal(commandLine: string, languageModelToolsService: ILanguageModelToolsService): string | undefined {
const tools = languageModelToolsService.getTools();
if (!tools || previouslyRecommededInSession) {
return;
Expand Down Expand Up @@ -55,12 +55,7 @@ export function getRecommendedToolsOverRunInTerminal(commandLine: string, langua

if (recommendedTools.size) {
previouslyRecommededInSession = true;
return {
content: [{
kind: 'text',
value: messages.join(' \n')
}],
};
return messages.join(' \n');
}

return undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,6 @@ export class RunInTerminalTool extends Disposable implements IToolImpl {
// Immutable window state
protected readonly _osBackend: Promise<OperatingSystem>;

// HACK: Per-tool call state, saved globally
// TODO: These should not be part of the state as different sessions could get confused https://github.com/microsoft/vscode/issues/255889
private _alternativeRecommendation?: IToolResult;

private static readonly _backgroundExecutions = new Map<string, BackgroundTerminalExecution>();
public static getBackgroundOutput(id: string): string {
const backgroundExecution = RunInTerminalTool._backgroundExecutions.get(id);
Expand Down Expand Up @@ -176,8 +172,8 @@ export class RunInTerminalTool extends Disposable implements IToolImpl {
async prepareToolInvocation(context: IToolInvocationPreparationContext, token: CancellationToken): Promise<IPreparedToolInvocation | undefined> {
const args = context.parameters as IRunInTerminalInputParams;

this._alternativeRecommendation = getRecommendedToolsOverRunInTerminal(args.command, this._languageModelToolsService);
const presentation = this._alternativeRecommendation ? 'hidden' : undefined;
const alternativeRecommendation = getRecommendedToolsOverRunInTerminal(args.command, this._languageModelToolsService);
const presentation = alternativeRecommendation ? 'hidden' : undefined;

const os = await this._osBackend;
const shell = await this._terminalProfileResolverService.getDefaultShell({
Expand All @@ -187,7 +183,7 @@ export class RunInTerminalTool extends Disposable implements IToolImpl {
const language = os === OperatingSystem.Windows ? 'pwsh' : 'sh';

let confirmationMessages: IToolConfirmationMessages | undefined;
if (this._alternativeRecommendation) {
if (alternativeRecommendation) {
confirmationMessages = undefined;
} else {
const subCommands = splitCommandLineIntoSubCommands(args.command, shell, os);
Expand Down Expand Up @@ -249,23 +245,27 @@ export class RunInTerminalTool extends Disposable implements IToolImpl {
toolEdited: toolEditedCommand
},
language,
alternativeRecommendation,
}
};
}

async invoke(invocation: IToolInvocation, _countTokens: CountTokensCallback, _progress: ToolProgress, token: CancellationToken): Promise<IToolResult> {
if (this._alternativeRecommendation) {
return this._alternativeRecommendation;
}

const args = invocation.parameters as IRunInTerminalInputParams;

this._logService.debug(`RunInTerminalTool: Invoking with options ${JSON.stringify(args)}`);

const toolSpecificData = invocation.toolSpecificData as IChatTerminalToolInvocationData | undefined;
if (!toolSpecificData) {
throw new Error('toolSpecificData must be provided for this tool');
}
if (toolSpecificData.alternativeRecommendation) {
return {
content: [{
kind: 'text',
value: toolSpecificData.alternativeRecommendation
}]
};
}

const args = invocation.parameters as IRunInTerminalInputParams;
this._logService.debug(`RunInTerminalTool: Invoking with options ${JSON.stringify(args)}`);
let toolResultMessage: string | IMarkdownString | undefined;

const chatSessionId = invocation.context?.sessionId ?? 'no-chat-session';
Expand Down
Loading