Skip to content

Commit f5bad1b

Browse files
authored
Merge branch 'main' into dev/vrbhardw/reasoningEffort
2 parents acf095a + c8f0bbd commit f5bad1b

File tree

50 files changed

+1442
-1253
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1442
-1253
lines changed

package-lock.json

Lines changed: 0 additions & 19 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -740,6 +740,58 @@
740740
]
741741
}
742742
},
743+
{
744+
"name": "copilot_multiReplaceString",
745+
"toolReferenceName": "multiReplaceString",
746+
"displayName": "%copilot.tools.multiReplaceString.name%",
747+
"modelDescription": "This tool allows you to apply multiple replace_string_in_file operations in a single call, which is more efficient than calling replace_string_in_file multiple times. It takes an array of replacement operations and applies them sequentially. Each replacement operation has the same parameters as replace_string_in_file: filePath, oldString, newString, and explanation. This tool is ideal when you need to make multiple edits across different files or multiple edits in the same file. The tool will provide a summary of successful and failed operations.",
748+
"when": "!config.github.copilot.chat.disableReplaceTool",
749+
"inputSchema": {
750+
"type": "object",
751+
"properties": {
752+
"explanation": {
753+
"type": "string",
754+
"description": "A brief explanation of what the multi-replace operation will accomplish."
755+
},
756+
"replacements": {
757+
"type": "array",
758+
"description": "An array of replacement operations to apply sequentially.",
759+
"items": {
760+
"type": "object",
761+
"properties": {
762+
"explanation": {
763+
"type": "string",
764+
"description": "A brief explanation of this specific replacement operation."
765+
},
766+
"filePath": {
767+
"type": "string",
768+
"description": "An absolute path to the file to edit."
769+
},
770+
"oldString": {
771+
"type": "string",
772+
"description": "The exact literal text to replace, preferably unescaped. Include at least 3 lines of context BEFORE and AFTER the target text, matching whitespace and indentation precisely. If this string is not the exact literal text or does not match exactly, this replacement will fail."
773+
},
774+
"newString": {
775+
"type": "string",
776+
"description": "The exact literal text to replace `oldString` with, preferably unescaped. Provide the EXACT text. Ensure the resulting code is correct and idiomatic."
777+
}
778+
},
779+
"required": [
780+
"explanation",
781+
"filePath",
782+
"oldString",
783+
"newString"
784+
]
785+
},
786+
"minItems": 1
787+
}
788+
},
789+
"required": [
790+
"explanation",
791+
"replacements"
792+
]
793+
}
794+
},
743795
{
744796
"name": "copilot_editNotebook",
745797
"toolReferenceName": "editNotebook",
@@ -3691,7 +3743,6 @@
36913743
"@anthropic-ai/sdk": "^0.56.0",
36923744
"@humanwhocodes/gitignore-to-minimatch": "1.0.2",
36933745
"@microsoft/tiktokenizer": "^1.0.10",
3694-
"@roamhq/mac-ca": "^1.0.7",
36953746
"@vscode/copilot-api": "^0.1.4",
36963747
"@vscode/extension-telemetry": "^1.0.0",
36973748
"@vscode/l10n": "^0.0.18",

package.nls.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@
271271
"copilot.tools.createFile.name": "Create File",
272272
"copilot.tools.insertEdit.name": "Edit File",
273273
"copilot.tools.replaceString.name": "Replace String in File",
274+
"copilot.tools.multiReplaceString.name": "Multi-Replace String in Files",
274275
"copilot.tools.editNotebook.name": "Edit Notebook",
275276
"copilot.tools.runNotebookCell.name": "Run Notebook Cell",
276277
"copilot.tools.getNotebookCellOutput.name": "Get Notebook Cell Output",

src/extension/inlineEdits/vscode-node/parts/vscodeWorkspace.ts

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { CancellationToken, CodeAction, CodeActionKind, commands, Diagnostic, DiagnosticSeverity, EndOfLine, languages, NotebookDocument, Range, TextDocument, TextDocumentChangeEvent, TextDocumentContentChangeEvent, TextEditor, Uri, window, workspace } from 'vscode';
6+
import { CancellationToken, CodeAction, CodeActionKind, commands, Diagnostic, DiagnosticSeverity, EndOfLine, languages, NotebookCell, NotebookCellKind, NotebookDocument, Range, TextDocument, TextDocumentChangeEvent, TextDocumentContentChangeEvent, TextEditor, Uri, window, workspace } from 'vscode';
77
import { ConfigKey, IConfigurationService } from '../../../../platform/configuration/common/configurationService';
88
import { IIgnoreService } from '../../../../platform/ignore/common/ignoreService';
99
import { CodeActionData } from '../../../../platform/inlineEdits/common/dataTypes/codeActionData';
@@ -34,6 +34,17 @@ import { OffsetRange } from '../../../../util/vs/editor/common/core/ranges/offse
3434
import { StringText } from '../../../../util/vs/editor/common/core/text/abstractText';
3535
import { IInstantiationService } from '../../../../util/vs/platform/instantiation/common/instantiation';
3636
import { toInternalTextEdit } from '../utils/translations';
37+
import { ResourceSet } from '../../../../util/vs/base/common/map';
38+
import { Lazy } from '../../../../util/vs/base/common/lazy';
39+
40+
function trackMarkdownCells(cells: NotebookCell[], resources: ResourceSet): void {
41+
cells.forEach(c => {
42+
if (c.kind === NotebookCellKind.Markup) {
43+
resources.add(c.document.uri);
44+
}
45+
});
46+
}
47+
3748

3849
export class VSCodeWorkspace extends ObservableWorkspace implements IDisposable {
3950
private readonly _openDocuments = observableValue<readonly IVSCodeObservableDocument[], { added: readonly IVSCodeObservableDocument[]; removed: readonly IVSCodeObservableDocument[] }>(this, []);
@@ -43,6 +54,13 @@ export class VSCodeWorkspace extends ObservableWorkspace implements IDisposable
4354
private get useAlternativeNotebookFormat(): boolean {
4455
return this._configurationService.getExperimentBasedConfig(ConfigKey.Internal.UseAlternativeNESNotebookFormat, this._experimentationService);
4556
}
57+
private readonly markdownNotebookCells = new Lazy<ResourceSet>(() => {
58+
const markdownCellUris = new ResourceSet();
59+
workspace.notebookDocuments.forEach(doc => trackMarkdownCells(doc.getCells(), markdownCellUris));
60+
return markdownCellUris;
61+
}
62+
63+
);
4664
constructor(
4765
@IWorkspaceService private readonly _workspaceService: IWorkspaceService,
4866
@IInstantiationService private readonly _instaService: IInstantiationService,
@@ -97,6 +115,10 @@ export class VSCodeWorkspace extends ObservableWorkspace implements IDisposable
97115
}
98116
}));
99117

118+
if (this.useAlternativeNotebookFormat) {
119+
this._store.add(workspace.onDidOpenNotebookDocument(e => trackMarkdownCells(e.getCells(), this.markdownNotebookCells.value)));
120+
}
121+
100122
this._store.add(workspace.onDidChangeNotebookDocument(e => {
101123
const doc = this._getDocumentByDocumentAndUpdateShouldTrack(e.notebook.uri);
102124
if (!doc || !e.contentChanges.length || doc instanceof VSCodeObservableTextDocument) {
@@ -112,6 +134,13 @@ export class VSCodeWorkspace extends ObservableWorkspace implements IDisposable
112134
doc.value.set(stringValueFromDoc(doc.altNotebook), tx, editWithReason);
113135
doc.version.set(doc.notebook.version, tx);
114136
});
137+
138+
if (this.useAlternativeNotebookFormat) {
139+
e.contentChanges.map(c => c.removedCells).flat().forEach(c => {
140+
this.markdownNotebookCells.value.delete(c.document.uri);
141+
});
142+
trackMarkdownCells(e.contentChanges.map(c => c.addedCells).flat(), this.markdownNotebookCells.value);
143+
}
115144
}));
116145

117146
this._store.add(window.onDidChangeTextEditorSelection(e => {
@@ -166,7 +195,7 @@ export class VSCodeWorkspace extends ObservableWorkspace implements IDisposable
166195
});
167196

168197
private getTextDocuments() {
169-
return getTextDocuments(this.useAlternativeNotebookFormat);
198+
return getTextDocuments(this.useAlternativeNotebookFormat, this.markdownNotebookCells.value);
170199
}
171200
private readonly _vscodeTextDocuments: IObservable<readonly TextDocument[]> = this.getTextDocuments();
172201
private readonly _textDocsWithShouldTrackFlag = mapObservableArrayCached(this, this._vscodeTextDocuments, (doc, store) => {
@@ -335,7 +364,8 @@ export class VSCodeWorkspace extends ObservableWorkspace implements IDisposable
335364
const notebookDocs = this._notebookDocsWithShouldTrackFlag.read(reader);
336365
notebookDocs.forEach(d => {
337366
map.set(d.doc.uri.toString(), d);
338-
d.doc.getCells().forEach(cell => map.set(cell.document.uri.toString(), d));
367+
// Markdown cells will be treated as standalone text documents (old behaviour).
368+
d.doc.getCells().filter(cell => cell.kind === NotebookCellKind.Code).forEach(cell => map.set(cell.document.uri.toString(), d));
339369
});
340370
return map;
341371
});
@@ -609,7 +639,7 @@ class VSCodeObservableNotebookDocument extends AbstractVSCodeObservableDocument
609639
}
610640
}
611641

612-
function getTextDocuments(excludeNotebookCells: boolean): IObservable<readonly TextDocument[]> {
642+
function getTextDocuments(excludeNotebookCells: boolean, markdownCellUris: ResourceSet): IObservable<readonly TextDocument[]> {
613643
return observableFromEvent(undefined, e => {
614644
const d1 = workspace.onDidOpenTextDocument(e);
615645
const d2 = workspace.onDidCloseTextDocument(e);
@@ -619,7 +649,9 @@ function getTextDocuments(excludeNotebookCells: boolean): IObservable<readonly T
619649
d2.dispose();
620650
}
621651
};
622-
}, () => excludeNotebookCells ? workspace.textDocuments.filter(doc => doc.uri.scheme !== Schemas.vscodeNotebookCell) : workspace.textDocuments);
652+
// If we're meant to exclude notebook cells, we will still include the markdown cells as separate documents.
653+
// Thats because markdown cells will be treated as standalone text documents in the editor.
654+
}, () => excludeNotebookCells ? workspace.textDocuments.filter(doc => doc.uri.scheme !== Schemas.vscodeNotebookCell || markdownCellUris.has(doc.uri)) : workspace.textDocuments);
623655
}
624656

625657
function getNotebookDocuments(): IObservable<readonly NotebookDocument[]> {

src/extension/intents/node/agentIntent.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,20 @@ const getTools = (instaService: IInstantiationService, request: vscode.ChatReque
6161

6262
const allowTools: Record<string, boolean> = {};
6363
allowTools[ToolName.EditFile] = true;
64-
allowTools[ToolName.ReplaceString] = modelSupportsReplaceString(model) || !!(model.family.includes('gemini') && configurationService.getExperimentBasedConfig(ConfigKey.Internal.GeminiReplaceString, experimentationService));
64+
allowTools[ToolName.ReplaceString] = modelSupportsReplaceString(model);
6565
allowTools[ToolName.ApplyPatch] = await modelSupportsApplyPatch(model) && !!toolsService.getTool(ToolName.ApplyPatch);
6666

6767
if (modelCanUseReplaceStringExclusively(model)) {
6868
allowTools[ToolName.ReplaceString] = true;
6969
allowTools[ToolName.EditFile] = false;
7070
}
7171

72+
if (allowTools[ToolName.ReplaceString]) {
73+
if (configurationService.getExperimentBasedConfig(ConfigKey.Internal.MultiReplaceString, experimentationService)) {
74+
allowTools[ToolName.MultiReplaceString] = true;
75+
}
76+
}
77+
7278
allowTools[ToolName.RunTests] = await testService.hasAnyTests();
7379
allowTools[ToolName.CoreRunTask] = !!(configurationService.getConfig(ConfigKey.AgentCanRunTasks) && tasksService.getTasks().length);
7480

src/extension/intents/node/editCodeIntent2.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import type * as vscode from 'vscode';
77
import { ChatLocation } from '../../../platform/chat/common/commonTypes';
88
import { ConfigKey, IConfigurationService } from '../../../platform/configuration/common/configurationService';
9+
import { modelSupportsReplaceString } from '../../../platform/endpoint/common/chatModelCapabilities';
910
import { IEndpointProvider } from '../../../platform/endpoint/common/endpointProvider';
1011
import { IEnvService } from '../../../platform/env/common/envService';
1112
import { ILogService } from '../../../platform/log/common/logService';
@@ -40,14 +41,18 @@ const getTools = (instaService: IInstantiationService, request: vscode.ChatReque
4041
const experimentalService = accessor.get<IExperimentationService>(IExperimentationService);
4142
const model = await endpointProvider.getChatEndpoint(request);
4243
const lookForTools = new Set<string>([ToolName.EditFile]);
44+
const experimentationService = accessor.get<IExperimentationService>(IExperimentationService);
4345

4446

4547
if (configurationService.getExperimentBasedConfig(ConfigKey.EditsCodeNewNotebookAgentEnabled, experimentalService) !== false && requestHasNotebookRefs(request, notebookService, { checkPromptAsWell: true })) {
4648
lookForTools.add(ToolName.CreateNewJupyterNotebook);
4749
}
4850

49-
if (model.family.startsWith('claude')) {
51+
if (modelSupportsReplaceString(model)) {
5052
lookForTools.add(ToolName.ReplaceString);
53+
if (configurationService.getExperimentBasedConfig(ConfigKey.Internal.MultiReplaceString, experimentationService)) {
54+
lookForTools.add(ToolName.MultiReplaceString);
55+
}
5156
}
5257
lookForTools.add(ToolName.EditNotebook);
5358
if (requestHasNotebookRefs(request, notebookService, { checkPromptAsWell: true })) {

src/extension/intents/node/notebookEditorIntent.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import { IToolsService } from '../../tools/common/toolsService';
3333
import { EditCodeIntent, EditCodeIntentOptions } from './editCodeIntent';
3434
import { EditCode2IntentInvocation } from './editCodeIntent2';
3535
import { getRequestedToolCallIterationLimit } from './toolCallingLoop';
36+
import { modelSupportsReplaceString } from '../../../platform/endpoint/common/chatModelCapabilities';
3637

3738
const getTools = (instaService: IInstantiationService, request: vscode.ChatRequest): Promise<vscode.LanguageModelToolInformation[]> =>
3839
instaService.invokeFunction(async accessor => {
@@ -43,13 +44,17 @@ const getTools = (instaService: IInstantiationService, request: vscode.ChatReque
4344
const experimentalService = accessor.get<IExperimentationService>(IExperimentationService);
4445
const model = await endpointProvider.getChatEndpoint(request);
4546
const lookForTools = new Set<string>([ToolName.EditFile]);
47+
const experimentationService = accessor.get<IExperimentationService>(IExperimentationService);
4648

4749
if (configurationService.getExperimentBasedConfig(ConfigKey.EditsCodeNewNotebookAgentEnabled, experimentalService) !== false && requestHasNotebookRefs(request, notebookService, { checkPromptAsWell: true })) {
4850
lookForTools.add(ToolName.CreateNewJupyterNotebook);
4951
}
5052

51-
if (model.family.startsWith('claude')) {
53+
if (modelSupportsReplaceString(model)) {
5254
lookForTools.add(ToolName.ReplaceString);
55+
if (configurationService.getExperimentBasedConfig(ConfigKey.Internal.MultiReplaceString, experimentationService)) {
56+
lookForTools.add(ToolName.MultiReplaceString);
57+
}
5358
}
5459

5560
lookForTools.add(ToolName.EditNotebook);

src/extension/log/vscode-node/loggingActions.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import { SyncDescriptor } from '../../../util/vs/platform/instantiation/common/d
3434
import { FetcherService } from '../../../platform/networking/vscode-node/fetcherServiceImpl';
3535
import { CAPIClientImpl } from '../../../platform/endpoint/node/capiClientImpl';
3636
import { shuffle } from '../../../util/vs/base/common/arrays';
37+
import { EXTENSION_ID } from '../../common/constants';
3738

3839
export interface ProxyAgentLog {
3940
trace(message: string, ...args: any[]): void;
@@ -342,7 +343,7 @@ function getProxyEnvVariables() {
342343
return res.length ? `\n\nEnvironment Variables:${res.join('')}` : '';
343344
}
344345

345-
export function collectFetcherTelemetry(accessor: ServicesAccessor): void {
346+
export function collectFetcherTelemetry(accessor: ServicesAccessor, error: any): void {
346347
const extensionContext = accessor.get(IVSCodeExtensionContext);
347348
const fetcherService = accessor.get(IFetcherService);
348349
const envService = accessor.get(IEnvService);
@@ -357,7 +358,6 @@ export function collectFetcherTelemetry(accessor: ServicesAccessor): void {
357358
return;
358359
}
359360

360-
const currentUserAgentLibrary = fetcherService.getUserAgentLibrary();
361361
if (!configurationService.getExperimentBasedConfig(ConfigKey.Internal.DebugCollectFetcherTelemetry, expService)) {
362362
return;
363363
}
@@ -383,7 +383,13 @@ export function collectFetcherTelemetry(accessor: ServicesAccessor): void {
383383
}
384384
logService.debug(`Refetch model metadata: This window won.`);
385385

386-
const userAgentLibraryUpdate = (original: string) => `${vscode.env.remoteName || 'local'}-on-${process.platform}-after-${currentUserAgentLibrary}-using-${original}`;
386+
const ext = vscode.extensions.getExtension(EXTENSION_ID);
387+
const extKind = (ext ? ext.extensionKind === vscode.ExtensionKind.UI : !vscode.env.remoteName) ? 'local' : 'remote';
388+
const remoteName = vscode.env.remoteName || 'none';
389+
const platform = process.platform;
390+
const originalLibrary = fetcherService.getUserAgentLibrary();
391+
const originalError = error ? (error.message || 'unknown') : 'none';
392+
const userAgentLibraryUpdate = (library: string) => JSON.stringify({ extKind, remoteName, platform, library, originalLibrary, originalError });
387393
const fetchers = [
388394
ElectronFetcher.create(envService, userAgentLibraryUpdate),
389395
new NodeFetchFetcher(envService, userAgentLibraryUpdate),

src/extension/mcp/test/vscode-node/nuget.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { ITestingServicesAccessor, TestingServiceCollection } from '../../../../
1010
import { createExtensionUnitTestingServices } from '../../../test/node/services';
1111
import { NuGetMcpSetup } from '../../vscode-node/nuget';
1212

13-
describe('get nuget MCP server info', { timeout: 30_000 }, () => {
13+
describe.skip('get nuget MCP server info', { timeout: 30_000 }, () => {
1414
let testingServiceCollection: TestingServiceCollection = createExtensionUnitTestingServices();
1515
let accessor: ITestingServicesAccessor = testingServiceCollection.createTestingAccessor();
1616
let logService: ILogService = accessor.get(ILogService);
@@ -94,4 +94,4 @@ describe('get nuget MCP server info', { timeout: 30_000 }, () => {
9494
expect.fail();
9595
}
9696
});
97-
});
97+
});

0 commit comments

Comments
 (0)