Skip to content

Commit db8e3d2

Browse files
authored
Fix EditFileTool in remote windows (microsoft#239250)
Map the filePath to a URI in the EH, then uriTransformer runs, tool gets a proper URI.
1 parent c894de7 commit db8e3d2

File tree

4 files changed

+76
-34
lines changed

4 files changed

+76
-34
lines changed

src/vs/workbench/api/common/extHostLanguageModelTools.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import { IPreparedToolInvocation, isToolInvocationContext, IToolInvocation, IToo
1515
import { checkProposedApiEnabled, isProposedApiEnabled } from '../../services/extensions/common/extensions.js';
1616
import { ExtHostLanguageModelToolsShape, IMainContext, IToolDataDto, MainContext, MainThreadLanguageModelToolsShape } from './extHost.protocol.js';
1717
import * as typeConvert from './extHostTypeConverters.js';
18+
import { IToolInputProcessor } from '../../contrib/chat/common/tools/tools.js';
19+
import { EditToolData, EditToolInputProcessor } from '../../contrib/chat/common/tools/editFileTool.js';
1820

1921
export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape {
2022
/** A map of tools that were registered in this EH */
@@ -25,6 +27,8 @@ export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape
2527
/** A map of all known tools, from other EHs or registered in vscode core */
2628
private readonly _allTools = new Map<string, IToolDataDto>();
2729

30+
private readonly _toolInputProcessors = new Map<string, IToolInputProcessor>();
31+
2832
constructor(mainContext: IMainContext) {
2933
this._proxy = mainContext.getProxy(MainContext.MainThreadLanguageModelTools);
3034

@@ -33,6 +37,8 @@ export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape
3337
this._allTools.set(tool.id, revive(tool));
3438
}
3539
});
40+
41+
this._toolInputProcessors.set(EditToolData.id, new EditToolInputProcessor());
3642
}
3743

3844
async $countTokensForInvocation(callId: string, input: string, token: CancellationToken): Promise<number> {
@@ -60,10 +66,11 @@ export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape
6066
}
6167

6268
// Making the round trip here because not all tools were necessarily registered in this EH
69+
const processedInput = this._toolInputProcessors.get(toolId)?.processInput(options.input) ?? options.input;
6370
const result = await this._proxy.$invokeTool({
6471
toolId,
6572
callId,
66-
parameters: options.input,
73+
parameters: processedInput,
6774
tokenBudget: options.tokenizationOptions?.tokenBudget,
6875
context: options.toolInvocationToken as IToolInvocationContext | undefined,
6976
chatRequestId: isProposedApiEnabled(extension, 'chatParticipantPrivate') ? options.chatRequestId : undefined,

src/vs/workbench/contrib/chat/browser/chat.contribution.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,11 @@ import { ChatGettingStartedContribution } from './actions/chatGettingStarted.js'
7979
import { Extensions, IConfigurationMigrationRegistry } from '../../../common/configuration.js';
8080
import { ChatRelatedFilesContribution } from './contrib/chatInputRelatedFilesContrib.js';
8181
import { ChatQuotasService, ChatQuotasStatusBarEntry, IChatQuotasService } from './chatQuotasService.js';
82-
import { BuiltinToolsContribution } from './tools/tools.js';
8382
import { ChatSetupContribution } from './chatSetup.js';
8483
import { ChatEditorOverlayController } from './chatEditorOverlay.js';
8584
import '../common/promptSyntax/languageFeatures/promptLinkProvider.js';
8685
import { PromptFilesConfig } from '../common/promptSyntax/config.js';
86+
import { BuiltinToolsContribution } from '../common/tools/tools.js';
8787

8888
// Register configuration
8989
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);

src/vs/workbench/contrib/chat/browser/tools/tools.ts renamed to src/vs/workbench/contrib/chat/common/tools/editFileTool.ts

Lines changed: 37 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,43 +5,20 @@
55

66
import { CancellationToken } from '../../../../../base/common/cancellation.js';
77
import { MarkdownString } from '../../../../../base/common/htmlContent.js';
8-
import { Disposable, IDisposable } from '../../../../../base/common/lifecycle.js';
8+
import { IDisposable } from '../../../../../base/common/lifecycle.js';
99
import { autorun } from '../../../../../base/common/observable.js';
10-
import { URI } from '../../../../../base/common/uri.js';
10+
import { URI, UriComponents } from '../../../../../base/common/uri.js';
1111
import { localize } from '../../../../../nls.js';
12-
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
1312
import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js';
14-
import { IWorkbenchContribution } from '../../../../common/contributions.js';
1513
import { SaveReason } from '../../../../common/editor.js';
1614
import { ITextFileService } from '../../../../services/textfile/common/textfiles.js';
1715
import { ICodeMapperService } from '../../common/chatCodeMapperService.js';
1816
import { IChatEditingService } from '../../common/chatEditingService.js';
1917
import { ChatModel } from '../../common/chatModel.js';
2018
import { IChatService } from '../../common/chatService.js';
2119
import { ILanguageModelIgnoredFilesService } from '../../common/ignoredFiles.js';
22-
import { CountTokensCallback, ILanguageModelToolsService, IToolData, IToolImpl, IToolInvocation, IToolResult } from '../../common/languageModelToolsService.js';
23-
24-
export class BuiltinToolsContribution extends Disposable implements IWorkbenchContribution {
25-
26-
static readonly ID = 'chat.builtinTools';
27-
28-
constructor(
29-
@ILanguageModelToolsService toolsService: ILanguageModelToolsService,
30-
@IInstantiationService instantiationService: IInstantiationService,
31-
) {
32-
super();
33-
34-
const editTool = instantiationService.createInstance(EditTool);
35-
this._register(toolsService.registerToolData(editToolData));
36-
this._register(toolsService.registerToolImplementation(editToolData.id, editTool));
37-
}
38-
}
39-
40-
interface EditToolParams {
41-
filePath: string;
42-
explanation: string;
43-
code: string;
44-
}
20+
import { CountTokensCallback, IToolData, IToolImpl, IToolInvocation, IToolResult } from '../../common/languageModelToolsService.js';
21+
import { IToolInputProcessor } from './tools.js';
4522

4623
const codeInstructions = `
4724
The user is very smart and can understand how to apply your edits to their files, you just need to provide minimal hints.
@@ -63,7 +40,7 @@ class Person {
6340
}
6441
`;
6542

66-
const editToolData: IToolData = {
43+
export const EditToolData: IToolData = {
6744
id: 'vscode_editFile',
6845
tags: ['vscode_editing'],
6946
displayName: localize('chat.tools.editFile', "Edit File"),
@@ -88,7 +65,7 @@ const editToolData: IToolData = {
8865
}
8966
};
9067

91-
class EditTool implements IToolImpl {
68+
export class EditTool implements IToolImpl {
9269

9370
constructor(
9471
@IChatService private readonly chatService: IChatService,
@@ -105,13 +82,13 @@ class EditTool implements IToolImpl {
10582
}
10683

10784
const parameters = invocation.parameters as EditToolParams;
108-
const uri = URI.file(parameters.filePath);
85+
const uri = URI.revive(parameters.file); // TODO@roblourens do revive in MainThreadLanguageModelTools
10986
if (!this.workspaceContextService.isInsideWorkspace(uri)) {
110-
throw new Error(`File ${parameters.filePath} can't be edited because it's not inside the current workspace`);
87+
throw new Error(`File ${uri.fsPath} can't be edited because it's not inside the current workspace`);
11188
}
11289

11390
if (await this.ignoredFilesService.fileIsIgnored(uri, token)) {
114-
throw new Error(`File ${parameters.filePath} can't be edited because it is configured to be ignored by Copilot`);
91+
throw new Error(`File ${uri.fsPath} can't be edited because it is configured to be ignored by Copilot`);
11592
}
11693

11794
const model = this.chatService.getSession(invocation.context?.sessionId) as ChatModel;
@@ -182,3 +159,31 @@ class EditTool implements IToolImpl {
182159
};
183160
}
184161
}
162+
163+
export interface EditToolParams {
164+
file: UriComponents;
165+
explanation: string;
166+
code: string;
167+
}
168+
169+
export interface EditToolRawParams {
170+
filePath: string;
171+
explanation: string;
172+
code: string;
173+
}
174+
175+
export class EditToolInputProcessor implements IToolInputProcessor {
176+
processInput(input: EditToolRawParams): EditToolParams {
177+
if (!input.filePath) {
178+
// Tool name collision, or input wasn't properly validated upstream
179+
return input as any;
180+
}
181+
182+
// Runs in EH, will be mapped
183+
return {
184+
file: URI.file(input.filePath),
185+
explanation: input.explanation,
186+
code: input.code,
187+
};
188+
}
189+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { Disposable } from '../../../../../base/common/lifecycle.js';
7+
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
8+
import { IWorkbenchContribution } from '../../../../common/contributions.js';
9+
import { ILanguageModelToolsService } from '../../common/languageModelToolsService.js';
10+
import { EditTool, EditToolData } from './editFileTool.js';
11+
12+
export class BuiltinToolsContribution extends Disposable implements IWorkbenchContribution {
13+
14+
static readonly ID = 'chat.builtinTools';
15+
16+
constructor(
17+
@ILanguageModelToolsService toolsService: ILanguageModelToolsService,
18+
@IInstantiationService instantiationService: IInstantiationService,
19+
) {
20+
super();
21+
22+
const editTool = instantiationService.createInstance(EditTool);
23+
this._register(toolsService.registerToolData(EditToolData));
24+
this._register(toolsService.registerToolImplementation(EditToolData.id, editTool));
25+
}
26+
}
27+
28+
export interface IToolInputProcessor {
29+
processInput(input: any): any;
30+
}

0 commit comments

Comments
 (0)