inline chat and replace-tool (#1827)

* `detectIntent` shouldn't make a request when there is no metadata to pick from

https://github.com/microsoft/vscode/issues/275818

* add unit test for https://github.com/microsoft/vscode/issues/275824
This commit is contained in:
Johannes Rieken
2025-11-06 14:50:54 +00:00
committed by GitHub
parent c6927a0975
commit 04896efbab
4 changed files with 251 additions and 0 deletions
@@ -17,6 +17,7 @@ import { ITabsAndEditorsService } from '../../../platform/tabs/common/tabsAndEdi
import { IExperimentationService } from '../../../platform/telemetry/common/nullExperimentationService';
import { ITelemetryService } from '../../../platform/telemetry/common/telemetry';
import { isNotebookCellOrNotebookChatInput } from '../../../util/common/notebooks';
import { isFalsyOrEmpty } from '../../../util/vs/base/common/arrays';
import { IInstantiationService } from '../../../util/vs/platform/instantiation/common/instantiation';
import { Position, Range } from '../../../vscodeTypes';
import { getAgentForIntent, GITHUB_PLATFORM_AGENT, Intent } from '../../common/constants';
@@ -181,6 +182,11 @@ export class IntentDetector implements ChatParticipantDetectionProvider {
this.logService.trace('Building intent detector');
if (builtinParticipants.length === 0 && (isFalsyOrEmpty(thirdPartyParticipants))) {
this.logService.trace('No participants available for intent detection');
return undefined;
}
const endpoint = await this.endpointProvider.getChatEndpoint('copilot-fast');
const preferredIntent = await this.getPreferredIntent(location, documentContext, history, messageText);
@@ -0,0 +1,77 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/// <reference path="./math.test.js" />
console.log("foobar");
// one
// two
// three
logwo
// * merge/re-use modified file entries
// * no accept/disacrd on new session
// * accept all hunks
export function fib(nth) {
if (nth <= 0) return 0;
if (nth === 1) return 0;
if (nth === 2) return 1;
return fib(nth - 1) + fib(nth - 2);
}
///-///-////---
const r = /hello/gim;
export function sum(a, b) {
return a + b;
}
export function sub(a, b) {
return a - b;
}
export function sumArray(a) {
return a.reduce((sum, num) => sum + num, 0);
}
export function div(a, b) {
// console.log fff fff
return a / b;
}
export function mul(a, b) {
return a * b;
}
export function sumThreeFloats(a, b, c) {
return a + b + c;
}
/**
* Checks if a given number is a prime number.
*
* @param {number} number - The number to check for primality.
* @returns {boolean} - Returns true if the number is prime, otherwise false.
*/
export function isPrime(number) {
if (number <= 1) return false;
for (let i = 2; i <= Math.sqrt(number); i++) {
if (number % i === 0) {
return false;
}
}
return true;
}
export function isEven(n) {
return n % 2 === 0;
}
@@ -0,0 +1,75 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/// <reference path="./math.test.js" />
console.log("foobar");
// one
// two
// three
logwo
// * merge/re-use modified file entries
// * no accept/disacrd on new session
// * accept all hunks
export function fib(nth) {
if (nth <= 0) return 0;
if (nth === 1) return 0;
if (nth === 2) return 1;
return fib(nth - 1) + fib(nth - 2);
}
///-///-////---
const r = /hello/gim;
export function sum(a, b) {
return a + b;
}
export function sub(a, b) {
return a - b;
}
export function sumArray(a) {
return a.reduce((sum, num) => sum + num, 0);
}
export function div(A, b) {
// console.log fff fff
return A / b;
}
export function mul(a, b) {
return a * b;
}
export function sumThreeFloats(a, b, c) {
return a + b + c;
}
/**
* Checks if a given number is a prime number.
*
* @param {number} number - The number to check for primality.
* @returns {boolean} - Returns true if the number is prime, otherwise false.
*/
export function isPrime(number) {
if (number <= 1) return false;
for (let i = 2; i <= Math.sqrt(number); i++) {
if (number % i === 0) {
return false;
}
}
return true;
}
export function isEven(n) {
return n % 2 === 0;
}
@@ -0,0 +1,93 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { readFileSync } from 'fs';
import { join } from 'path';
import { beforeEach, expect, it, suite } from 'vitest';
import { ITestingServicesAccessor } from '../../../../../platform/test/node/services';
import { TestWorkspaceService } from '../../../../../platform/test/node/testWorkspaceService';
import { IWorkspaceService } from '../../../../../platform/workspace/common/workspaceService';
import { ChatResponseStreamImpl } from '../../../../../util/common/chatResponseStreamImpl';
import { createTextDocumentData } from '../../../../../util/common/test/shims/textDocument';
import { CancellationToken } from '../../../../../util/vs/base/common/cancellation';
import { assertType } from '../../../../../util/vs/base/common/types';
import { URI } from '../../../../../util/vs/base/common/uri';
import { SyncDescriptor } from '../../../../../util/vs/platform/instantiation/common/descriptors';
import { IInstantiationService } from '../../../../../util/vs/platform/instantiation/common/instantiation';
import { ChatResponseTextEditPart } from '../../../../../vscodeTypes';
import { ChatVariablesCollection } from '../../../../prompt/common/chatVariablesCollection';
import { WorkingCopyOriginalDocument } from '../../../../prompts/node/inline/workingCopies';
import { createExtensionUnitTestingServices } from '../../../../test/node/services';
import { IReplaceStringToolParams, ReplaceStringTool } from '../../../node/replaceStringTool';
suite('ReplaceString Tool', () => {
let accessor: ITestingServicesAccessor;
const path = join(__dirname, 'fixtures/math.js.txt');
const fileTsUri = URI.file(path);
beforeEach(function () {
const services = createExtensionUnitTestingServices();
const content = String(readFileSync(path));
const testDoc = createTextDocumentData(fileTsUri, content, 'ts').document;
services.define(IWorkspaceService, new SyncDescriptor(
TestWorkspaceService, [[fileTsUri], [testDoc]]
));
accessor = services.createTestingAccessor();
});
it('whitespace change everywhere', async () => {
const input: IReplaceStringToolParams = JSON.parse(`{
"filePath": "${path.replaceAll('\\', '\\\\')}",
"oldString": "export function div(a, b) {\\n // console.log fff fff\\n return a / b;\\n}",
"newString": "export function div(A, b) {\\n // console.log fff fff\\n return A / b;\\n}"
}`);
const tool = accessor.get(IInstantiationService).createInstance(ReplaceStringTool);
expect(tool).toBeDefined();
const document = accessor.get(IWorkspaceService).textDocuments.find(doc => doc.uri.toString() === fileTsUri.toString());
assertType(document);
const workingCopyDocument = new WorkingCopyOriginalDocument(document.getText());
expect(document.getText().includes(input.oldString)).toBe(false); // TAB vs SPACES
let seenEdits = 0;
const stream = new ChatResponseStreamImpl((part) => {
if (part instanceof ChatResponseTextEditPart) {
const offsetEdits = workingCopyDocument.transformer.toOffsetEdit(part.edits);
if (!workingCopyDocument.isNoop(offsetEdits)) {
seenEdits++;
workingCopyDocument.applyOffsetEdits(offsetEdits);
}
}
}, () => { }, () => { });
const input2 = await tool.resolveInput(input, {
history: [],
stream,
query: 'change a to A',
chatVariables: new ChatVariablesCollection([]),
});
await tool.invoke({ input: input2, toolInvocationToken: undefined }, CancellationToken.None);
expect(seenEdits).toBe(1);
expect(workingCopyDocument.text).toMatchFileSnapshot('fixtures/math.js.txt.expected');
});
});