Skip to content

Commit 280d32b

Browse files
committed
Introduce a ICopilotToolExtension to support override existing built-in tools and provide alternative definition.
1 parent 09bc257 commit 280d32b

File tree

6 files changed

+89
-13
lines changed

6 files changed

+89
-13
lines changed

src/extension/tools/common/toolsRegistry.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export enum CopilotToolMode {
2626
FullContext,
2727
}
2828

29-
export interface ICopilotTool<T> extends vscode.LanguageModelTool<T> {
29+
export interface ICopilotToolExtension<T> {
3030
/**
3131
* Called when edits are made in a tool call response. The tool can return
3232
* a confirmation that will be shown to the user before edits are applied.
@@ -55,10 +55,16 @@ export interface ICopilotTool<T> extends vscode.LanguageModelTool<T> {
5555
* parameters because the alternative definition will only be applied within
5656
* the Copilot extension, not other extensions' usages via `vscode.lm.tools`.
5757
*
58+
* @param tool The original tool definition.
5859
* @param endpoint Optional information about the currently selected language model endpoint.
5960
* If provided, allows customizing the tool definition per endpoint.
61+
*
62+
* @return An overridden tool definition.
6063
*/
61-
alternativeDefinition?(endpoint?: IChatEndpoint): vscode.LanguageModelToolInformation | undefined;
64+
alternativeDefinition?(tool: vscode.LanguageModelToolInformation, endpoint?: IChatEndpoint): vscode.LanguageModelToolInformation;
65+
}
66+
67+
export interface ICopilotTool<T> extends vscode.LanguageModelTool<T>, ICopilotToolExtension<T> {
6268
}
6369

6470

@@ -67,8 +73,14 @@ export interface ICopilotToolCtor {
6773
new(...args: any[]): ICopilotTool<any>;
6874
}
6975

76+
export interface ICopilotToolExtensionCtor {
77+
readonly toolName: ToolName;
78+
new(...args: any[]): ICopilotToolExtension<any>;
79+
}
80+
7081
export const ToolRegistry = new class {
7182
private _tools: ICopilotToolCtor[] = [];
83+
private _toolExtensions: ICopilotToolExtensionCtor[] = [];
7284

7385
public registerTool(tool: ICopilotToolCtor) {
7486
this._tools.push(tool);
@@ -77,4 +89,12 @@ export const ToolRegistry = new class {
7789
public getTools(): readonly ICopilotToolCtor[] {
7890
return this._tools;
7991
}
92+
93+
public registerToolExtension(tool: ICopilotToolExtensionCtor) {
94+
this._toolExtensions.push(tool);
95+
}
96+
97+
public getToolExtensions(): readonly ICopilotToolExtensionCtor[] {
98+
return this._toolExtensions;
99+
}
80100
}();
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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+
import type * as vscode from 'vscode';
6+
import { ILogService } from '../../../platform/log/common/logService';
7+
import { IChatEndpoint } from '../../../platform/networking/common/networking';
8+
import { ToolName } from '../common/toolNames';
9+
import { ICopilotToolExtension, ToolRegistry } from '../common/toolsRegistry';
10+
11+
interface IManageTodoListParams {
12+
operation: 'write' | 'read';
13+
todoList?: readonly {
14+
readonly id: number;
15+
readonly title: string;
16+
readonly description: string;
17+
readonly status: 'not-started' | 'in-progress' | 'completed';
18+
}[];
19+
}
20+
21+
22+
/**
23+
* A thin wrapper tool to provide custom behavior on top of the internal manage_todo_list tool.
24+
* This allows the extension to override the tool definition based on the model or other factors.
25+
*/
26+
class ManageTodoListToolExtension implements ICopilotToolExtension<IManageTodoListParams> {
27+
static readonly toolName = ToolName.CoreManageTodoList;
28+
constructor(
29+
@ILogService readonly _logService: ILogService
30+
) { }
31+
32+
alternativeDefinition(originTool: vscode.LanguageModelToolInformation, chatEndpoint: IChatEndpoint | undefined): vscode.LanguageModelToolInformation {
33+
// specialize the tool definition for gpt-5 to reduce the frequency
34+
if (chatEndpoint?.model === 'gpt-5') {
35+
return {
36+
...originTool,
37+
description: originTool.description?.replace('VERY frequently ', ''),
38+
};
39+
}
40+
41+
return originTool;
42+
}
43+
}
44+
45+
ToolRegistry.registerToolExtension(ManageTodoListToolExtension);

src/extension/tools/node/readFileTool.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,10 +178,12 @@ export class ReadFileTool implements ICopilotTool<ReadFileParams> {
178178
};
179179
}
180180

181-
public alternativeDefinition(): vscode.LanguageModelToolInformation | undefined {
181+
public alternativeDefinition(originTool: vscode.LanguageModelToolInformation): vscode.LanguageModelToolInformation {
182182
if (this.configurationService.getExperimentBasedConfig<boolean>(ConfigKey.Internal.EnableReadFileV2, this.experimentationService)) {
183183
return readFileV2Description;
184184
}
185+
186+
return originTool;
185187
}
186188

187189
private async getSnapshot(uri: URI) {

src/extension/tools/node/test/testToolsService.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export class TestToolsService extends BaseToolsService implements IToolsService
3636
get tools(): LanguageModelToolInformation[] {
3737
return Array.from(this._tools.values()).map(tool => {
3838
const owned = this._copilotTools.get(getToolName(tool.name) as ToolName);
39-
return owned?.value.alternativeDefinition?.() ?? tool;
39+
return owned?.value.alternativeDefinition?.(tool) ?? tool;
4040
});
4141
}
4242

@@ -149,7 +149,7 @@ export class TestToolsService extends BaseToolsService implements IToolsService
149149
// Apply model-specific alternative if available via alternativeDefinition
150150
const owned = this._copilotTools.get(getToolName(tool.name) as ToolName);
151151
if (owned?.value?.alternativeDefinition) {
152-
const alternative = owned.value.alternativeDefinition(endpoint);
152+
const alternative = owned.value.alternativeDefinition(tool, endpoint);
153153
if (alternative) {
154154
return alternative;
155155
}

src/extension/tools/vscode-node/toolsService.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,17 @@ import { equals as arraysEqual } from '../../../util/vs/base/common/arrays';
1010
import { Lazy } from '../../../util/vs/base/common/lazy';
1111
import { IInstantiationService } from '../../../util/vs/platform/instantiation/common/instantiation';
1212
import { getContributedToolName, getToolName, mapContributedToolNamesInSchema, mapContributedToolNamesInString, ToolName } from '../common/toolNames';
13-
import { ICopilotTool, ToolRegistry } from '../common/toolsRegistry';
13+
import { ICopilotTool, ICopilotToolExtension, ToolRegistry } from '../common/toolsRegistry';
1414
import { BaseToolsService } from '../common/toolsService';
1515

1616
export class ToolsService extends BaseToolsService {
1717
declare _serviceBrand: undefined;
1818

1919
private readonly _copilotTools: Lazy<Map<ToolName, ICopilotTool<any>>>;
20+
21+
// Extensions to override definitions for existing tools.
22+
private readonly _toolExtensions: Lazy<Map<ToolName, ICopilotToolExtension<any>>>;
23+
2024
private readonly _contributedToolCache: {
2125
input: readonly vscode.LanguageModelToolInformation[];
2226
output: readonly vscode.LanguageModelToolInformation[];
@@ -43,7 +47,7 @@ export class ToolsService extends BaseToolsService {
4347
})
4448
.map(tool => {
4549
const owned = this._copilotTools.value.get(getToolName(tool.name) as ToolName);
46-
return owned?.alternativeDefinition?.() ?? tool;
50+
return owned?.alternativeDefinition?.(tool) ?? tool;
4751
});
4852

4953
const result: vscode.LanguageModelToolInformation[] = contributedTools.map(tool => {
@@ -71,6 +75,7 @@ export class ToolsService extends BaseToolsService {
7175
) {
7276
super(logService);
7377
this._copilotTools = new Lazy(() => new Map(ToolRegistry.getTools().map(t => [t.toolName, instantiationService.createInstance(t)] as const)));
78+
this._toolExtensions = new Lazy(() => new Map(ToolRegistry.getToolExtensions().map(t => [t.toolName, instantiationService.createInstance(t)] as const)));
7479
}
7580

7681
invokeTool(name: string | ToolName, options: vscode.LanguageModelToolInvocationOptions<Object>, token: vscode.CancellationToken): Thenable<vscode.LanguageModelToolResult | vscode.LanguageModelToolResult2> {
@@ -99,13 +104,17 @@ export class ToolsService extends BaseToolsService {
99104
.map(tool => {
100105
// Apply model-specific alternative if available via alternativeDefinition
101106
const owned = this._copilotTools.value.get(getToolName(tool.name) as ToolName);
107+
let resultTool = tool;
102108
if (owned?.alternativeDefinition) {
103-
const alternative = owned.alternativeDefinition(endpoint);
104-
if (alternative) {
105-
return alternative;
106-
}
109+
resultTool = owned.alternativeDefinition(resultTool, endpoint);
107110
}
108-
return tool;
111+
112+
const extension = this._toolExtensions.value.get(getToolName(tool.name) as ToolName);
113+
if (extension?.alternativeDefinition) {
114+
resultTool = extension.alternativeDefinition(resultTool, endpoint);
115+
}
116+
117+
return resultTool;
109118
})
110119
.filter(tool => {
111120
// 0. Check if the tool was disabled via the tool picker. If so, it must be disabled here

test/base/extHostContext/simulationExtHostToolsService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ export class SimulationExtHostToolsService extends BaseToolsService implements I
128128
// Apply model-specific alternative if available via alternativeDefinition
129129
const owned = this.copilotTools.get(getToolName(tool.name) as ToolName);
130130
if (owned?.alternativeDefinition) {
131-
const alternative = owned.alternativeDefinition(endpoint);
131+
const alternative = owned.alternativeDefinition(tool, endpoint);
132132
if (alternative) {
133133
return alternative;
134134
}

0 commit comments

Comments
 (0)