Skip to content
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,23 @@ export class SettingsEditorSearchServiceImpl implements ISettingsEditorSearchSer
}

await this.embeddingIndex.loadIndexes();

if (embeddingResult.values.length === 0 || !embeddingResult.values[0]) {
progress.report({
query,
kind: SettingsSearchResultKind.EMBEDDED,
settings: []
});
if (!options.embeddingsOnly) {
progress.report({
query,
kind: SettingsSearchResultKind.LLM_RANKED,
settings: []
});
}
return;
}

const embeddingSettings: SettingListItem[] = this.embeddingIndex.settingsIndex.nClosestValues(embeddingResult.values[0], 25);
if (token.isCancellationRequested) {
progress.report(canceledBundle);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { beforeEach, describe, expect, test, vi } from 'vitest';
import type { Progress, SettingsSearchProviderOptions, SettingsSearchResult } from 'vscode';
import { SettingsSearchResultKind } from 'vscode';

Check failure on line 8 in src/extension/prompt/vscode-node/test/settingsEditorSearchServiceImpl.spec.ts

View workflow job for this annotation

GitHub Actions / Test (Linux)

'vscode' import is duplicated

Check failure on line 8 in src/extension/prompt/vscode-node/test/settingsEditorSearchServiceImpl.spec.ts

View workflow job for this annotation

GitHub Actions / Test (Windows)

'vscode' import is duplicated

Check failure on line 8 in src/extension/prompt/vscode-node/test/settingsEditorSearchServiceImpl.spec.ts

View workflow job for this annotation

GitHub Actions / Test (Windows)

'vscode' import is duplicated

Check failure on line 8 in src/extension/prompt/vscode-node/test/settingsEditorSearchServiceImpl.spec.ts

View workflow job for this annotation

GitHub Actions / Test (Linux)

'vscode' import is duplicated
Copy link
Contributor

Choose a reason for hiding this comment

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

@copilot the type and non-type vscode imports being separate causes a build error

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in commit 19e002e. Combined the type and value imports from 'vscode' into a single import statement using inline type annotations.

import { IAuthenticationService } from '../../../../platform/authentication/common/authentication';
import { Embeddings, EmbeddingType, IEmbeddingsComputer } from '../../../../platform/embeddings/common/embeddingsComputer';
import { ICombinedEmbeddingIndex } from '../../../../platform/embeddings/common/vscodeIndex';
import { IEndpointProvider } from '../../../../platform/endpoint/common/endpointProvider';
import { ITestingServicesAccessor } from '../../../../platform/test/node/services';
import { CancellationToken } from '../../../../util/vs/base/common/cancellation';
import { IInstantiationService } from '../../../../util/vs/platform/instantiation/common/instantiation';
import { createExtensionUnitTestingServices } from '../../../test/node/services';
import { SettingsEditorSearchServiceImpl } from '../settingsEditorSearchServiceImpl';

describe('SettingsEditorSearchServiceImpl', () => {
let accessor: ITestingServicesAccessor;
let service: SettingsEditorSearchServiceImpl;
let mockEmbeddingsComputer: IEmbeddingsComputer;
let mockEmbeddingIndex: ICombinedEmbeddingIndex;
let mockAuthService: IAuthenticationService;
let mockEndpointProvider: IEndpointProvider;

beforeEach(() => {
const testingServiceCollection = createExtensionUnitTestingServices();
accessor = testingServiceCollection.createTestingAccessor();

// Create mock implementations
mockEmbeddingsComputer = {
_serviceBrand: undefined,
computeEmbeddings: vi.fn()
};

mockEmbeddingIndex = {
_serviceBrand: undefined,
loadIndexes: vi.fn().mockResolvedValue(undefined),
settingsIndex: {
nClosestValues: vi.fn().mockReturnValue([])
}
} as any;

mockAuthService = {
_serviceBrand: undefined,
getCopilotToken: vi.fn().mockResolvedValue({ isFreeUser: true, isNoAuthUser: false })
} as any;

mockEndpointProvider = {
_serviceBrand: undefined
} as any;

// Create the service manually with mocks
service = new SettingsEditorSearchServiceImpl(
mockAuthService,
mockEndpointProvider,
mockEmbeddingIndex,
mockEmbeddingsComputer,
accessor.get(IInstantiationService)
);
});

test('handles empty embeddings result gracefully', async () => {
// Simulate computeEmbeddings returning an empty values array
const emptyEmbeddings: Embeddings = {
type: EmbeddingType.text3small_512,
values: []
};
vi.mocked(mockEmbeddingsComputer.computeEmbeddings).mockResolvedValue(emptyEmbeddings);

const results: SettingsSearchResult[] = [];
const progress: Progress<SettingsSearchResult> = {
report: (result: SettingsSearchResult) => results.push(result)
};

const options: SettingsSearchProviderOptions = {
limit: 10,
embeddingsOnly: false
};

await service.provideSettingsSearchResults('test query', options, progress, CancellationToken.None);

// Verify that nClosestValues was NOT called (since values[0] is undefined)
expect(mockEmbeddingIndex.settingsIndex.nClosestValues).not.toHaveBeenCalled();

// Verify that we reported empty results for both EMBEDDED and LLM_RANKED
expect(results).toHaveLength(2);
expect(results[0]).toEqual({
query: 'test query',
kind: SettingsSearchResultKind.EMBEDDED,
settings: []
});
expect(results[1]).toEqual({
query: 'test query',
kind: SettingsSearchResultKind.LLM_RANKED,
settings: []
});
});

test('handles empty embeddings result with embeddingsOnly option', async () => {
// Simulate computeEmbeddings returning an empty values array
const emptyEmbeddings: Embeddings = {
type: EmbeddingType.text3small_512,
values: []
};
vi.mocked(mockEmbeddingsComputer.computeEmbeddings).mockResolvedValue(emptyEmbeddings);

const results: SettingsSearchResult[] = [];
const progress: Progress<SettingsSearchResult> = {
report: (result: SettingsSearchResult) => results.push(result)
};

const options: SettingsSearchProviderOptions = {
limit: 10,
embeddingsOnly: true
};

await service.provideSettingsSearchResults('test query', options, progress, CancellationToken.None);

// Verify that nClosestValues was NOT called (since values[0] is undefined)
expect(mockEmbeddingIndex.settingsIndex.nClosestValues).not.toHaveBeenCalled();

// Verify that we only reported empty results for EMBEDDED (not LLM_RANKED)
expect(results).toHaveLength(1);
expect(results[0]).toEqual({
query: 'test query',
kind: SettingsSearchResultKind.EMBEDDED,
settings: []
});
});

test('calls nClosestValues when embeddings are available', async () => {
// Simulate computeEmbeddings returning a valid embedding
const validEmbeddings: Embeddings = {
type: EmbeddingType.text3small_512,
values: [{
type: EmbeddingType.text3small_512,
value: [0.1, 0.2, 0.3]
}]
};
vi.mocked(mockEmbeddingsComputer.computeEmbeddings).mockResolvedValue(validEmbeddings);

const results: SettingsSearchResult[] = [];
const progress: Progress<SettingsSearchResult> = {
report: (result: SettingsSearchResult) => results.push(result)
};

const options: SettingsSearchProviderOptions = {
limit: 10,
embeddingsOnly: true
};

await service.provideSettingsSearchResults('test query', options, progress, CancellationToken.None);

// Verify that nClosestValues WAS called with the first embedding
expect(mockEmbeddingIndex.settingsIndex.nClosestValues).toHaveBeenCalledWith(
validEmbeddings.values[0],
25
);

// Verify that we reported the result
expect(results).toHaveLength(1);
expect(results[0].kind).toBe(SettingsSearchResultKind.EMBEDDED);
});
});
6 changes: 6 additions & 0 deletions src/util/common/test/shims/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,10 @@ export enum FileType {
File = 1,
Directory = 2,
SymbolicLink = 64
}

export enum SettingsSearchResultKind {
EMBEDDED = 1,
LLM_RANKED = 2,
CANCELED = 3
}
5 changes: 3 additions & 2 deletions src/util/common/test/shims/vscodeTypesShim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { SymbolInformation, SymbolKind } from '../../../vs/workbench/api/common/
import { EndOfLine, TextEdit } from '../../../vs/workbench/api/common/extHostTypes/textEdit';
import { AISearchKeyword, ChatErrorLevel, ChatImageMimeType, ChatPrepareToolInvocationPart, ChatReferenceBinaryData, ChatReferenceDiagnostic, ChatRequestEditedFileEventKind, ChatRequestEditorData, ChatRequestNotebookData, ChatRequestTurn, ChatResponseAnchorPart, ChatResponseClearToPreviousToolInvocationReason, ChatResponseCodeblockUriPart, ChatResponseCodeCitationPart, ChatResponseCommandButtonPart, ChatResponseConfirmationPart, ChatResponseExtensionsPart, ChatResponseFileTreePart, ChatResponseMarkdownPart, ChatResponseMarkdownWithVulnerabilitiesPart, ChatResponseMovePart, ChatResponseNotebookEditPart, ChatResponseProgressPart, ChatResponseProgressPart2, ChatResponsePullRequestPart, ChatResponseReferencePart, ChatResponseReferencePart2, ChatResponseTextEditPart, ChatResponseThinkingProgressPart, ChatResponseTurn, ChatResponseTurn2, ChatResponseWarningPart, ChatToolInvocationPart, ExcludeSettingOptions, LanguageModelChatMessageRole, LanguageModelDataPart, LanguageModelDataPart2, LanguageModelError, LanguageModelPartAudience, LanguageModelPromptTsxPart, LanguageModelTextPart, LanguageModelTextPart2, LanguageModelToolCallPart, LanguageModelToolExtensionSource, LanguageModelToolMCPSource, LanguageModelToolResult, LanguageModelToolResult2, LanguageModelToolResultPart, LanguageModelToolResultPart2, TextSearchMatch2 } from './chatTypes';
import { TextDocumentChangeReason, TextEditorSelectionChangeKind, WorkspaceEdit } from './editing';
import { ChatLocation, ChatVariableLevel, DiagnosticSeverity, ExtensionMode, FileType, TextEditorCursorStyle, TextEditorLineNumbersStyle, TextEditorRevealType } from './enums';
import { ChatLocation, ChatVariableLevel, DiagnosticSeverity, ExtensionMode, FileType, SettingsSearchResultKind, TextEditorCursorStyle, TextEditorLineNumbersStyle, TextEditorRevealType } from './enums';
import { t } from './l10n';
import { NewSymbolName, NewSymbolNameTag, NewSymbolNameTriggerKind } from './newSymbolName';
import { TerminalShellExecutionCommandLineConfidence } from './terminal';
Expand Down Expand Up @@ -116,7 +116,8 @@ const shim: typeof vscodeTypes = {
SymbolKind,
SnippetString,
SnippetTextEdit,
FileType
FileType,
SettingsSearchResultKind
};

export = shim;
1 change: 1 addition & 0 deletions src/vscodeTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export import SymbolKind = vscode.SymbolKind;
export import SnippetString = vscode.SnippetString;
export import SnippetTextEdit = vscode.SnippetTextEdit;
export import FileType = vscode.FileType;
export import SettingsSearchResultKind = vscode.SettingsSearchResultKind;

export const l10n = {
/**
Expand Down
Loading