Add cache-stable mode for vscode_renameSymbol and vscode_listCodeUsages (experimental)#312568
Merged
bhavyaus merged 3 commits intomicrosoft:mainfrom Apr 26, 2026
Merged
Conversation
These two tools today emit a description that embeds the live list of registered language IDs and return undefined when no providers are registered. As language extensions activate during an agent turn (and sometimes deactivate), the tools' description bytes change and the tools themselves can disappear from / reappear in the tool array. Both effects shift bytes inside the tools array prefix and break the provider-side prompt cache mid-turn. Add an experimental setting, `chat.experimental.symbolTools.cacheStable`, that when enabled: - Always registers both tools, regardless of whether any provider is currently registered. - Uses a static modelDescription that does not include the language list. - Skips re-firing onDidUpdateToolData on provider changes (no re-registration churn). Tool behavior at invoke time is unchanged: the tools already return an error when the file's language has no provider, and the static description tells the model to expect that. Default is false so no behavior change without explicit opt-in or experiment assignment. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Adds an experimental “cache-stable” mode to keep vscode_renameSymbol and vscode_listCodeUsages tool registration/description byte-stable across a turn (to avoid provider-side prompt-cache invalidations when language extensions activate).
Changes:
- Add
chat.experimental.symbolTools.cacheStableconfiguration key (experimental, startup-mode experiment). - Gate
RenameTool/UsagesToolgetToolData()to optionally return static tool data and avoid provider-change-driven tool data updates. - Inject
IConfigurationServiceinto both tools to read the setting.
Show a summary per file
| File | Description |
|---|---|
| src/vs/workbench/contrib/chat/common/constants.ts | Adds ChatConfiguration.SymbolToolsCacheStable enum entry and documentation. |
| src/vs/workbench/contrib/chat/browser/chat.contribution.ts | Registers the new experimental startup-mode configuration setting. |
| src/vs/workbench/contrib/chat/browser/tools/renameTool.ts | Adds cache-stable tool-data branch + config injection; skips provider-change debounced updates when enabled. |
| src/vs/workbench/contrib/chat/browser/tools/usagesTool.ts | Same pattern as RenameTool for usages/reference providers. |
Copilot's findings
Comments suppressed due to low confidence (2)
src/vs/workbench/contrib/chat/browser/tools/renameTool.ts:100
- The new cache-stable branch in
getToolData()(static description + always-registered behavior) is currently untested. There are already unit tests coveringgetToolData()for the non-cache-stable path; please add coverage forchat.experimental.symbolTools.cacheStable = true(e.g. tool data is returned even with no providers, and the model description does not include a per-language list).
getToolData(): IToolData | undefined {
if (this._isCacheStable()) {
return this._getStaticToolData();
}
src/vs/workbench/contrib/chat/browser/tools/usagesTool.ts:97
- The new cache-stable branch in
getToolData()(static description + always-registered behavior) is currently untested. There are already unit tests coveringgetToolData()for the non-cache-stable path; please add coverage forchat.experimental.symbolTools.cacheStable = true(e.g. tool data is returned even with no providers, and the model description does not include a per-language list).
getToolData(): IToolData | undefined {
if (this._isCacheStable()) {
return this._getStaticToolData();
}
- Files reviewed: 4/4 changed files
- Comments generated: 2
…ndency Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Addresses Copilot PR review: covers RenameTool/UsagesTool getToolData() behavior when chat.experimental.symbolTools.cacheStable is enabled: - tool data is returned even with no providers registered - modelDescription does not include 'Currently supported for' list - modelDescription is byte-stable across provider registrations Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
bhavyaus
approved these changes
Apr 26, 2026
roblourens
approved these changes
Apr 26, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
vscode_renameSymbolandvscode_listCodeUsagescurrently emit a tool description that embeds the live set of registered language IDs (e.g.Currently supported for: python, r, rmd.) and returnundefinedfromgetToolData()when the corresponding provider has no registered languages.Both behaviors poison the provider-side prompt cache during an agent turn:
tools[]array invalidates everything from that tool onward.registeredLanguageIdsgoes empty, the whole tool disappears from the array, shifting every subsequent tool's index by one — a much larger byte delta than a description change. The two tools are wired to independent events (renameProvider.onDidChangevsreferenceProvider.onDidChange), so they can flap in and out of the array out of step.Both effects fire mid-turn, on perfectly normal use (e.g. the model reading one file in a new language), and produce 0% cache-hit requests in the middle of otherwise high-hit turns.
What this PR does
Adds an experimental setting:
chat.experimental.symbolTools.cacheStablefalseexperimental,experiment: { mode: 'startup' }(eligible for ExP A/B treatment)When enabled, both
RenameToolandUsagesTool:modelDescriptionthat omits the per-language list. A single sentence is appended explaining that the tool returns an error if the file's language has no provider — which is already the runtime behavior ininvoke().*Provider.onDidChange, soonDidUpdateToolDatais never re-fired and the tool registration doesn't churn.prepareToolInvocationandinvokeare untouched; the tools still error out cleanly when a file's language has no provider registered.When the setting is
false(default) the existing behavior is preserved exactly.Why this is safe
false— opt-in only.invoke(),prepareToolInvocation(), or the input schemas.Expected impact
Independent of this PR, request bodies captured locally show 0% cache-hit rounds occurring at the exact moments when the language list inside these two tools changes (or the tools flap in/out of the array). With the flag enabled, those events should no longer cause cache-prefix invalidation, leading to materially higher cache-hit percentages in long agent turns that touch multiple languages.
We will validate the impact via an A/B experiment by comparing prompt cache hit rates and uncached input tokens between treatment and control populations.
Trying it out
Set in user settings:
{ "chat.experimental.symbolTools.cacheStable": true }Restart the window so the constructor's startup-time branch picks up the new value. (The setting is
experiment: { mode: 'startup' }— runtime toggling is not supported.)Files changed
src/vs/workbench/contrib/chat/common/constants.ts— newChatConfiguration.SymbolToolsCacheStableenum entry.src/vs/workbench/contrib/chat/browser/chat.contribution.ts— registers the configuration with experimental tag andexperiment: { mode: 'startup' }.src/vs/workbench/contrib/chat/browser/tools/renameTool.ts— gated branch ingetToolData()returning a staticIToolData;IConfigurationServiceinjected.src/vs/workbench/contrib/chat/browser/tools/usagesTool.ts— same pattern.