Skip to content

Support tool filtering by model#278904

Merged
connor4312 merged 21 commits into
mainfrom
dev/bhavyau/tools-model
Jan 22, 2026
Merged

Support tool filtering by model#278904
connor4312 merged 21 commits into
mainfrom
dev/bhavyau/tools-model

Conversation

@bhavyaus
Copy link
Copy Markdown
Collaborator

@bhavyaus bhavyaus commented Nov 22, 2025

For #275679

Ensures ChatRequest.tools correctly reflects tool availability and enablement state by filtering based on the active model's compatibility.

Key changes:

  • Added supportedModels field to IToolDataDto and passed through main-to-exthost communication
  • Updated ExtHostChatAgents2.getToolsForRequest() to accept modelId parameter and filter tools using isToolAvailableForModel()
  • Modified tool map to include all model-compatible tools with correct enablement state (defaults to enabled if not explicitly set)
  • Updated all getToolsForRequest() call sites to pass the active model ID

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request implements support for filtering language model tools based on the model being used, addressing issue #275679. The feature allows tool authors to specify which models can use their tools through a new supportedModels configuration field.

  • Added supportedModels?: string[] field to tool data structures throughout the codebase
  • Implemented filtering logic using isToolAvailableForModel() helper function to check model compatibility
  • Updated the API to use Map<LanguageModelToolInformation, boolean> instead of Map<string, boolean> for better type safety

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts Updated ChatRequest.tools to use LanguageModelToolInformation as map key instead of string for better type safety
src/vs/workbench/contrib/chat/common/tools/languageModelToolsContribution.ts Added supportedModels field to tool contribution schema and tool data interface
src/vs/workbench/contrib/chat/common/languageModelToolsService.ts Added supportedModels field to IToolData and implemented isToolAvailableForModel() helper function for model-based filtering
src/vs/workbench/contrib/chat/browser/actions/chatToolPicker.ts Integrated model ID filtering in showToolsPicker using isToolAvailableForModel to filter out incompatible tools
src/vs/workbench/contrib/chat/browser/actions/chatToolActions.ts Passed selected model ID to showToolsPicker for filtering
src/vs/workbench/api/common/extHostTypeConverters.ts Updated ChatAgentRequest.to signature to accept Map<LanguageModelToolInformation, boolean>
src/vs/workbench/api/common/extHostLanguageModelTools.ts Implemented isToolAvailableForModel method to check tool availability based on supportedModels
src/vs/workbench/api/common/extHostChatAgents2.ts Updated getToolsForRequest to filter tools by model ID and return Map<LanguageModelToolInformation, boolean>
src/vs/workbench/api/common/extHost.protocol.ts Added supportedModels field to IToolDataDto interface
src/vs/workbench/api/browser/mainThreadLanguageModelTools.ts Updated tool data DTO mapping to include supportedModels field

type: 'string',
pattern: '^(?!copilot_|vscode_)'
}
},
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is a static contribution flexible enough? It wouldn't let us control this with a setting/exp, or do more flexible checks like family.contains('-codex') that we do in code.

Also, going along with wanting to do other prompt tweaks in a more centralized place, it would be nice to have one place in code where I can determine the prompts and tools enabled for a certain model family.

How do you see this working for websearch, do you register a tool in api that doesn't have a invoke implementation but can show up in UI?

Copy link
Copy Markdown
Collaborator Author

@bhavyaus bhavyaus Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking of piggy backing off ICopilotToolExtension that Bryan added to support alrernativeDefintions and have that dynamically compute the supported models for now.

How do you see this working for websearch, do you register a tool in api that doesn't have a invoke implementation but can show up in UI?

Yes. But I don't think you can't NOT have an invoke with the way things are setup currently though.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that we probably want more flexibility here. Otherwise every single release of every model will require changes. In the related issue #277472 I brought up the notion of using a when clause.

This does not necessarily mean you need to drive it off global context keys (though maybe using when would overload it, perhaps use a different name?) as there is a standalone Parser in contextkey.ts that returns an expression you can easily evaluate by passing an object with a getValue method. So having something specific there that is evaluated when a request is sent with information about the model, family, etc. is doable.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about updating LanguageModelTool to allow tools to dynamically specify which models they support?

declare module 'vscode' {

	export interface LanguageModelTool<T> {
		/**
		 * Called to check if this tool supports a specific language model. If this method is not implemented,
		 * the tool is assumed to support all models.
		 *
		 * This method allows extensions to dynamically determine which models a tool can work with,
		 * enabling fine-grained control over tool availability based on model capabilities.
		 *
		 * @param modelId The identifier of the language model (e.g., 'gpt-4o', 'claude-3-5-sonnet')
		 * @returns `true` if the tool supports the given model, `false` otherwise
		 */
		supportsModel?(modelId: string): Thenable<boolean>;
	}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While that would definitely be a bit easier to author, the downside with that approach is that we must activate every extension that provides language model tools when a chat message is sent in order to filter them appropriately. Previously we've only needed to activate tool-providing extensions when a tool they provide is actually invoked.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you expecting this to be something that would be useful as real API? I was only thinking of this for our core tools. Is MCP considering something like this?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was only looking to support this for the builit-in tools we expose. @connor4312 do you anticipate needing this per model activation for MCP tools ?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For MCP there are two scenarios:

  1. Tools from MCP servers that are defined in agents.md and similar. We're seeing clients start to support inline MCP's in this way and so their tools should only be applied to requests that involve those agent instructions.
  2. Sampling with tools. In these cases tools are purely internal, we get them in core in the MCP sampling request and can register them however we want.

In both cases no particular extension API is needed for the MCP scenarios.

* A map of all tools that should (`true`) and should not (`false`) be used in this request.
*/
readonly tools: Map<string, boolean>;
readonly tools: Map<LanguageModelToolInformation, boolean>;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is needed for the more general cases in #277472 where LanguageModelToolInformation given to a request might not be globally available on vscode.lm.tools, or where there are multiple definitions of a tool that vary per model and the tool name alone is not enough to identify it.

I would pair this with making invokeTool(tool: string | LanguageModelToolInformation so that extensions are able to invoke tools that are only visible in the scope of a single request

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I needed this to be able to filter the tools for a request in the chat extension to just the supported ones.
Will explore updating invokeTools per Connor's suggestion above.

@bhavyaus bhavyaus force-pushed the dev/bhavyau/tools-model branch from e3ee5c4 to d228b3c Compare November 24, 2025 23:18
Comment thread src/vs/workbench/api/common/extHostLanguageModelTools.ts Outdated
bhavyaus and others added 9 commits November 24, 2025 15:28
This is a rethinking of the initial API proposed in `languageModelToolSupportsModel.d.ts`.

1. This switches to `models?: LanguageModelChatSelector[];` to control enablement.
   definitely open to switching this out, but I think a synchronously-analyzable
   expression is important to retain the data flows in core without too many races.
2. The extension is able to define a tool at runtime via registerToolDefinition. This
   should let us have entirely service-driven tools from model providers without
   requiring a static definition for each one. We can also have model-specific
   variants of tools without a ton of package.json work for each variant of the tool
   (as initially proposed using `when` clauses)

This then propagates that down into the tools service. Currently I have this as just
compiling to a `when` expression once it reaches the main thread. Then, for the tools
service, it takes an IContextKeyService in cases where tools should be enumerated,
and the chat input sets the model keys in its scoped context key service. This allows
the tools to be filtered correctly in the tool picker.

I initially thought about allowing multiple definitions be registered for the same tool
name/id for model-specific variants of tools but I realized that gets really gnarly and
we already have a `toolReferenceName` that multiple tools can register into.

Todo for tomorrow morning:
- Tools don't make it to the ChatRequest yet, or something, still need to investigate
- Need to make sure tools in prompts/models all work. For a first pass I think we can
  let prompts/modes reference all tools by toolReferenceName.
- Validate that multiple tools actually can safely share a reference name (and do
  some priority ordering?)
- General further validation
- Some unit tests
* chat: wip on model-specific tools

This is a rethinking of the initial API proposed in `languageModelToolSupportsModel.d.ts`.

1. This switches to `models?: LanguageModelChatSelector[];` to control enablement.
   definitely open to switching this out, but I think a synchronously-analyzable
   expression is important to retain the data flows in core without too many races.
2. The extension is able to define a tool at runtime via registerToolDefinition. This
   should let us have entirely service-driven tools from model providers without
   requiring a static definition for each one. We can also have model-specific
   variants of tools without a ton of package.json work for each variant of the tool
   (as initially proposed using `when` clauses)

This then propagates that down into the tools service. Currently I have this as just
compiling to a `when` expression once it reaches the main thread. Then, for the tools
service, it takes an IContextKeyService in cases where tools should be enumerated,
and the chat input sets the model keys in its scoped context key service. This allows
the tools to be filtered correctly in the tool picker.

I initially thought about allowing multiple definitions be registered for the same tool
name/id for model-specific variants of tools but I realized that gets really gnarly and
we already have a `toolReferenceName` that multiple tools can register into.

Todo for tomorrow morning:
- Tools don't make it to the ChatRequest yet, or something, still need to investigate
- Need to make sure tools in prompts/models all work. For a first pass I think we can
  let prompts/modes reference all tools by toolReferenceName.
- Validate that multiple tools actually can safely share a reference name (and do
  some priority ordering?)
- General further validation
- Some unit tests

* key selected tools based on reference name
@connor4312
Copy link
Copy Markdown
Member

connor4312 commented Jan 15, 2026

@aeschli in this PR I have suggested moving from storing the tool.id as the key for enablement to instead storing the toolReferenceName when possible. This allows two differing tool registrations (e.g. a web search tool from Anthropic and one from OAI) to both share the same name for the user and have their enablement persist together, while having different underlying ids, schemas, and implementation.

@aeschli
Copy link
Copy Markdown
Contributor

aeschli commented Jan 15, 2026

I see that ILanguageModelToolsService.getTools now gets a IContextKeyService instance. Is the idea to use it to get hold of LM model/family of the current chat?
I think that's a misuse of the IContextKeyService and the wrong approach. IContextKeyService should not become the untyped context for everything. Note that IContextKeyService is a mainly a UI concept introduced to support keybindings in different UI parts.
Also we want to use the ILanguageModelToolsService not only in the context of a chat widget
E.g. when running an subagent, the agent defines the model to use and we want to get all tools for that model.
Or when validating a agent file that sets model and tools, we want to validate if the tools are supported by the model

---
model: GPT-3.5
tools: [ 'memory' }
---

To do that validation, I should not have to create a IContextKeyService instance.

Using the IContextKeyService for globals like confguration settings is IMO ok, but we should not go beyond that.

@connor4312
Copy link
Copy Markdown
Member

That is reasonable. I'll adjust some of the logic.

@connor4312 connor4312 marked this pull request as ready for review January 15, 2026 17:24
@vs-code-engineering
Copy link
Copy Markdown
Contributor

vs-code-engineering Bot commented Jan 15, 2026

📬 CODENOTIFY

The following users are being notified based on files changed in this PR:

@bpasero

Matched files:

  • src/vs/workbench/contrib/chat/browser/chatSetup/chatSetupProviders.ts

@jrieken

Matched files:

  • src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts

@vs-code-engineering vs-code-engineering Bot added this to the January 2026 milestone Jan 15, 2026
DonJayamanne
DonJayamanne previously approved these changes Jan 15, 2026
@aeschli
Copy link
Copy Markdown
Contributor

aeschli commented Jan 19, 2026

@connor4312 The problem of toolReferenceName is that it is not unique. Only when it is qualified with the source (MCP server, extension id) then it is. We have the concept of the full reference name (

getFullReferenceName(tool: IToolData, toolSet?: ToolSet): string;
) but its computation requires to iterate over all tool sets
This is my debt list to improve, but I don't now when...

@connor4312
Copy link
Copy Markdown
Member

@aeschli do you have any ideas how you would like to see support for this linking work, then? We could probably ship without it but it would feel buggy that a visually identical 'web search' tool is deselected between modelsl

roblourens
roblourens previously approved these changes Jan 21, 2026
DonJayamanne
DonJayamanne previously approved these changes Jan 22, 2026
@aeschli
Copy link
Copy Markdown
Contributor

aeschli commented Jan 22, 2026

I like the addition of toolset to the tool definition.
On the IToolData in core I would now add a property fullToolReferenceName with derived value ${toolset}/${toolReferenceName ?? displayName. That should eliminates a lot of complexity on my side and we can remember enabled tools by 'fullToolReferenceName'.

@aeschli
Copy link
Copy Markdown
Contributor

aeschli commented Jan 22, 2026

Is the main purpose to have specialized tool implementations for a tool? It might be cleaner to model that and have an additional contribution point or provider for tool implementations. A tool implementation has the list of models it supports and points to a tool definition id. It doesn't need to repeat all the properties.
Maybe just the extension needs to deal with the tool implementations

dbaeumer
dbaeumer previously approved these changes Jan 22, 2026
@connor4312 connor4312 enabled auto-merge January 22, 2026 17:51
@connor4312
Copy link
Copy Markdown
Member

Is the main purpose to have specialized tool implementations for a tool?

We already have a mechanism for that in the extension, but there are more scenarios. From a comment a while ago:

Some tools have definitions that would be useful to specialize/craft depending on the model. We have overrides for this in copilot chat via alternativeDefinitions() but this is a bit hacky.

Some models have tools that are vendor-provided tools, like Claude's memory tool. This previously could not be represented reasonably in the API.

In another scenario, an agent mode file defines MCP servers that apply to that mode. I think if we want to be most specific then that MCP's associated tools should only be included/available for requests that come from that chat mode. This is not in this PR, but the work here could be extended to that scenario.

In the MCP sampling scenario, a sampling (LM) request has whatever MCP-server-defined tools and those are only valid to be called by that specific request and should not be exposed otherwise. Again this is not in this PR, but the API is now there to allow this.

@connor4312 connor4312 merged commit 1980c62 into main Jan 22, 2026
22 checks passed
@connor4312 connor4312 deleted the dev/bhavyau/tools-model branch January 22, 2026 20:35
@vs-code-engineering vs-code-engineering Bot locked and limited conversation to collaborators Mar 9, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants