Skip to content

Commit b5b809e

Browse files
authored
Filter copilot tools (#10742)
Addresses #9770 #10719 Fix copilot tools being included after 1.106 merge Filter copilot tools from Configure Tools quick pick using the include copilot tools setting <!-- Thank you for submitting a pull request. If this is your first pull request you can find information about contributing here: * https://github.com/posit-dev/positron/blob/main/CONTRIBUTING.md We recommend synchronizing your branch with the latest changes in the main branch by either pulling or rebasing. --> <!-- Describe briefly what problem this pull request resolves, or what new feature it introduces. Include screenshots of any new or altered UI. Link to any GitHub issues but avoid "magic" keywords that will automatically close the issue. If there are any details about your approach that are unintuitive or you want to draw attention to, please describe them here. --> ### Release Notes Fixes the 1.106 merge that changed how the Copilot tools are included. They are now built-in tools so we no longer can filter by using the Copilot extension. All the tools are filtered by the `copilot_` prefix on the tool name. Added the same logic to the quick pick UI. If the selected model is not a Copilot model, the setting is used to determine if the Copilot tools are shown in the list. Selected chat model is not Copilot and setting does not include Copilot tools <img width="624" height="464" alt="image" src="https://github.com/user-attachments/assets/f1c42152-cc72-4e2e-9f7b-1a85e3fde879" /> Selected chat model is not Copilot and setting does include Copilot tools <img width="619" height="601" alt="image" src="https://github.com/user-attachments/assets/420844de-adbd-4741-8ec6-2e72155c57c4" /> <!-- Optionally, replace `N/A` with text to be included in the next release notes. The `N/A` bullets are ignored. If you refer to one or more Positron issues, these issues are used to collect information about the feature or bugfix, such as the relevant language pack as determined by Github labels of type `lang: `. The note will automatically be tagged with the language. These notes are typically filled by the Positron team. If you are an external contributor, you may ignore this section. --> #### New Features - N/A #### Bug Fixes - Fix Copilot tools included in chat requests not using a Copilot model and filtering the tools from the Configure Tools quick pick ### QA Notes The Configure Tools action in the chat input shows the quick pick. Copilot tools are no longer in their own category. They're included in the Built-In category after the 1.106 upstream merge. <!-- Positron team members: please add relevant e2e test tags, so the tests can be run when you open this pull request. - Instructions: https://github.com/posit-dev/positron/blob/main/test/e2e/README.md#pull-requests-and-test-tags - Available tags: https://github.com/posit-dev/positron/blob/main/test/e2e/infra/test-runner/test-tags.ts --> <!-- Add additional information for QA on how to validate the change, paying special attention to the level of risk, adjacent areas that could be affected by the change, and any important contextual information not present in the linked issues. -->
1 parent cacc509 commit b5b809e

File tree

7 files changed

+125
-3
lines changed

7 files changed

+125
-3
lines changed

extensions/positron-assistant/src/api.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ export function getEnabledTools(
341341
// Check if the user has opted-in to always include Copilot tools.
342342
const alwaysIncludeCopilotTools = vscode.workspace.getConfiguration('positron.assistant').get('alwaysIncludeCopilotTools', false);
343343
// Check if the tool is provided by Copilot.
344-
const copilotTool = tool.source instanceof vscode.LanguageModelToolExtensionSource && tool.source.id === 'GitHub.copilot-chat';
344+
const copilotTool = tool.name.startsWith('copilot_');
345345

346346
// Disable Copilot notebook tools when Positron notebook mode is active
347347
// to avoid conflicts with Positron's specialized notebook tools.

src/vs/workbench/contrib/chat/browser/actions/chatContext.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ import { ITerminalService } from '../../../terminal/browser/terminal.js';
3333
import { URI } from '../../../../../base/common/uri.js';
3434
import { ITerminalCommand, TerminalCapability } from '../../../../../platform/terminal/common/capabilities/capabilities.js';
3535

36+
// --- Start Positron ---
37+
import { ILanguageModelToolsService } from '../../common/languageModelToolsService.js';
38+
// --- End Positron ---
3639

3740
export class ChatContextContributions extends Disposable implements IWorkbenchContribution {
3841

@@ -68,6 +71,12 @@ class ToolsContextPickerPick implements IChatContextPickerItem {
6871
readonly icon: ThemeIcon = Codicon.tools;
6972
readonly ordinal = -500;
7073

74+
// --- Start Positron ---
75+
constructor(
76+
@ILanguageModelToolsService private readonly _languageModelToolsService: ILanguageModelToolsService,
77+
) { }
78+
// --- End Positron ---
79+
7180
isEnabled(widget: IChatWidget): boolean {
7281
return !!widget.attachmentCapabilities.supportsToolAttachments;
7382
}
@@ -77,16 +86,40 @@ class ToolsContextPickerPick implements IChatContextPickerItem {
7786
type Pick = IChatContextPickerPickItem & { toolInfo: { ordinal: number; label: string } };
7887
const items: Pick[] = [];
7988

89+
// --- Start Positron ---
90+
const selectedLanguageModel = widget.input.selectedLanguageModel;
91+
// --- End Positron ---
92+
8093
for (const [entry, enabled] of widget.input.selectedToolsModel.entriesMap.get()) {
8194
if (enabled) {
8295
if (entry instanceof ToolSet) {
96+
// --- Start Positron ---
97+
// If no tools in the set are enabled for the selected model, skip the set
98+
const tools = entry.getTools();
99+
let hasEnabledTools = false;
100+
for (const tool of tools) {
101+
if (this._languageModelToolsService.isToolEnabledForModel(tool.id, selectedLanguageModel)) {
102+
hasEnabledTools = true;
103+
break;
104+
}
105+
}
106+
if (!hasEnabledTools) {
107+
continue;
108+
}
109+
// --- End Positron ---
83110
items.push({
84111
toolInfo: ToolDataSource.classify(entry.source),
85112
label: entry.referenceName,
86113
description: entry.description,
87114
asAttachment: (): IChatRequestToolSetEntry => toToolSetVariableEntry(entry)
88115
});
89116
} else {
117+
// --- Start Positron ---
118+
// Filter out tools not enabled for the selected model
119+
if (!this._languageModelToolsService.isToolEnabledForModel(entry.id, selectedLanguageModel)) {
120+
continue;
121+
}
122+
// --- End Positron ---
90123
items.push({
91124
toolInfo: ToolDataSource.classify(entry.source),
92125
label: entry.toolReferenceName ?? entry.displayName,

src/vs/workbench/contrib/chat/browser/actions/chatToolActions.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,10 @@ class ConfigureToolsAction extends Action2 {
176176

177177
}
178178

179-
const result = await instaService.invokeFunction(showToolsPicker, placeholder, description, () => entriesMap.get());
179+
// --- Start Positron ---
180+
// Add selected language model so we can filter tools based on provider compatibility
181+
const result = await instaService.invokeFunction(showToolsPicker, placeholder, description, () => entriesMap.get(), widget.input.selectedLanguageModel);
182+
// --- End Positron ---
180183
if (result) {
181184
widget.input.selectedToolsModel.set(result, false);
182185
}

src/vs/workbench/contrib/chat/browser/actions/chatToolPicker.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ import { ChatContextKeys } from '../../common/chatContextKeys.js';
2626
import { ILanguageModelToolsService, IToolData, ToolDataSource, ToolSet } from '../../common/languageModelToolsService.js';
2727
import { ConfigureToolSets } from '../tools/toolSetsContribution.js';
2828

29+
// --- Start Positron ---
30+
import { ILanguageModelChatMetadataAndIdentifier } from '../../common/languageModels.js';
31+
// --- End Positron ---
32+
2933
const enum BucketOrdinal { User, BuiltIn, Mcp, Extension }
3034

3135
// Legacy QuickPick types (existing implementation)
@@ -183,14 +187,18 @@ function createToolSetTreeItem(toolset: ToolSet, checked: boolean, editorService
183187
* @param placeHolder - Placeholder text shown in the picker
184188
* @param description - Optional description text shown in the picker
185189
* @param toolsEntries - Optional initial selection state for tools and toolsets
190+
* @param selectedLanguageModel - Optional selected language model to filter tools for
186191
* @param onUpdate - Optional callback fired when the selection changes
187192
* @returns Promise resolving to the final selection map, or undefined if cancelled
188193
*/
189194
export async function showToolsPicker(
190195
accessor: ServicesAccessor,
191196
placeHolder: string,
192197
description?: string,
193-
getToolsEntries?: () => ReadonlyMap<ToolSet | IToolData, boolean>
198+
// --- Start Positron ---
199+
getToolsEntries?: () => ReadonlyMap<ToolSet | IToolData, boolean>,
200+
selectedLanguageModel?: ILanguageModelChatMetadataAndIdentifier
201+
// --- End Positron ---
194202
): Promise<ReadonlyMap<ToolSet | IToolData, boolean> | undefined> {
195203

196204
const quickPickService = accessor.get(IQuickInputService);
@@ -378,6 +386,10 @@ export async function showToolsPicker(
378386
return bucket;
379387
};
380388

389+
// --- Start Positron ---
390+
const languageModelToolsService = accessor.get(ILanguageModelToolsService);
391+
// --- End Positron ---
392+
381393
for (const toolSet of toolsService.toolSets.get()) {
382394
if (!toolsEntries.has(toolSet)) {
383395
continue;
@@ -388,13 +400,32 @@ export async function showToolsPicker(
388400
}
389401
const toolSetChecked = toolsEntries.get(toolSet) === true;
390402
if (toolSet.source.type === 'mcp') {
403+
// --- Start Positron ---
404+
// Check if any tools in this MCP toolset are enabled for the selected model
405+
const hasEnabledTools = [...toolSet.getTools()].some(tool =>
406+
languageModelToolsService.isToolEnabledForModel(tool.id, selectedLanguageModel)
407+
);
408+
if (!hasEnabledTools) {
409+
continue;
410+
}
411+
// --- End Positron ---
391412
// bucket represents the toolset
392413
bucket.toolset = toolSet;
393414
if (toolSetChecked) {
394415
bucket.checked = toolSetChecked;
395416
}
396417
// all mcp tools are part of toolsService.getTools()
397418
} else {
419+
// --- Start Positron ---
420+
// Filter tools first to check if any are enabled for the selected model
421+
const enabledTools = [...toolSet.getTools()].filter(tool =>
422+
languageModelToolsService.isToolEnabledForModel(tool.id, selectedLanguageModel)
423+
);
424+
// Skip toolset entirely if no tools are enabled for the selected model
425+
if (enabledTools.length === 0) {
426+
continue;
427+
}
428+
// --- End Positron ---
398429
const treeItem = createToolSetTreeItem(toolSet, toolSetChecked, editorService);
399430
bucket.children.push(treeItem);
400431
const children = [];
@@ -412,6 +443,12 @@ export async function showToolsPicker(
412443
if (!tool.canBeReferencedInPrompt || !toolsEntries.has(tool)) {
413444
continue;
414445
}
446+
// --- Start Positron ---
447+
// Filter out tools not enabled for the selected model
448+
if (!languageModelToolsService.isToolEnabledForModel(tool.id, selectedLanguageModel)) {
449+
continue;
450+
}
451+
// --- End Positron ---
415452
const bucket = getBucket(tool.source);
416453
if (!bucket) {
417454
continue;

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ import { CountTokensCallback, createToolSchemaUri, GithubCopilotToolReference, I
4444
import { Target } from '../common/promptSyntax/promptFileParser.js';
4545
import { getToolConfirmationAlert } from './chatAccessibilityProvider.js';
4646

47+
// --- Start Positron ---
48+
import { ILanguageModelChatMetadataAndIdentifier } from '../common/languageModels.js';
49+
// --- End Positron ---
50+
4751
const jsonSchemaRegistry = Registry.as<JSONContributionRegistry.IJSONContributionRegistry>(JSONContributionRegistry.Extensions.JSONContribution);
4852

4953
interface IToolEntry {
@@ -672,6 +676,33 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo
672676
return result;
673677
}
674678

679+
// --- Start Positron ---
680+
// Determines if a tool should be enabled for the currently selected language model.
681+
// Based on the logic from chatToolPicker.ts
682+
isToolEnabledForModel(toolId: string, selectedLanguageModel: ILanguageModelChatMetadataAndIdentifier | undefined): boolean {
683+
// If no model is selected, enable all tools
684+
if (!selectedLanguageModel) {
685+
return true;
686+
}
687+
688+
// Check if the user is using a Copilot model
689+
const usingCopilotModel = selectedLanguageModel.metadata.vendor === 'copilot';
690+
// Check if the user has opted-in to always include Copilot tools.
691+
const alwaysIncludeCopilotTools = this._configurationService.getValue('positron.assistant.alwaysIncludeCopilotTools') as boolean;
692+
// Check if the tool is provided by Copilot
693+
const copilotTool = toolId.startsWith('copilot_');
694+
695+
// Enable Copilot tools only if using a Copilot model;
696+
// otherwise, enable all non-Copilot tools
697+
if (copilotTool) {
698+
return usingCopilotModel || alwaysIncludeCopilotTools;
699+
}
700+
701+
// All non-Copilot tools are enabled for all models
702+
return true;
703+
};
704+
// --- End Positron ---
705+
675706
toQualifiedToolNames(map: IToolAndToolSetEnablementMap): string[] {
676707
const result: string[] = [];
677708
const toolsCoveredByEnabledToolSet = new Set<IToolData>();

src/vs/workbench/contrib/chat/common/languageModelToolsService.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ import { ChatRequestToolReferenceEntry } from './chatVariableEntries.js';
2929
import { LanguageModelPartAudience } from './languageModels.js';
3030
import { PromptElementJSON, stringifyPromptElementJSON } from './tools/promptTsxTypes.js';
3131

32+
// --- Start Positron ---
33+
import { ILanguageModelChatMetadataAndIdentifier } from '../common/languageModels.js';
34+
// --- End Positron ---
35+
3236
export interface IToolData {
3337
id: string;
3438
source: ToolDataSource;
@@ -371,6 +375,10 @@ export interface ILanguageModelToolsService {
371375
toToolAndToolSetEnablementMap(qualifiedToolOrToolSetNames: readonly string[], target: string | undefined): IToolAndToolSetEnablementMap;
372376
toQualifiedToolNames(map: IToolAndToolSetEnablementMap): string[];
373377
toToolReferences(variableReferences: readonly IVariableReference[]): ChatRequestToolReferenceEntry[];
378+
379+
// --- Start Positron ---
380+
isToolEnabledForModel(toolId: string, selectedLanguageModel: ILanguageModelChatMetadataAndIdentifier | undefined): boolean;
381+
// --- End Positron ---
374382
}
375383

376384
export function createToolInputUri(toolCallId: string): URI {

src/vs/workbench/contrib/chat/test/common/mockLanguageModelToolsService.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ import { IVariableReference } from '../../common/chatModes.js';
1212
import { ChatRequestToolReferenceEntry } from '../../common/chatVariableEntries.js';
1313
import { CountTokensCallback, ILanguageModelToolsService, IToolAndToolSetEnablementMap, IToolData, IToolImpl, IToolInvocation, IToolResult, ToolSet } from '../../common/languageModelToolsService.js';
1414

15+
// --- Start Positron ---
16+
import { ILanguageModelChatMetadataAndIdentifier } from '../../common/languageModels.js';
17+
// --- End Positron ---
18+
1519
export class MockLanguageModelToolsService implements ILanguageModelToolsService {
1620
_serviceBrand: undefined;
1721

@@ -100,6 +104,12 @@ export class MockLanguageModelToolsService implements ILanguageModelToolsService
100104
throw new Error('Method not implemented.');
101105
}
102106

107+
// --- Start Positron ---
108+
isToolEnabledForModel(toolId: string, languageModel: ILanguageModelChatMetadataAndIdentifier | undefined): boolean {
109+
throw new Error('Method not implemented.');
110+
}
111+
// --- End Positron ---
112+
103113
toToolReferences(variableReferences: readonly IVariableReference[]): ChatRequestToolReferenceEntry[] {
104114
throw new Error('Method not implemented.');
105115
}

0 commit comments

Comments
 (0)