Skip to content

Commit fa23e5f

Browse files
authored
1 parent a141eda commit fa23e5f

File tree

3 files changed

+128
-22
lines changed

3 files changed

+128
-22
lines changed

src/vs/workbench/contrib/chat/common/languageModels.ts

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { CancellationToken } from 'vs/base/common/cancellation';
77
import { Emitter, Event } from 'vs/base/common/event';
88
import { Iterable } from 'vs/base/common/iterator';
99
import { IJSONSchema } from 'vs/base/common/jsonSchema';
10-
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
10+
import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
1111
import { isFalsyOrWhitespace } from 'vs/base/common/strings';
1212
import { localize } from 'vs/nls';
1313
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
@@ -133,18 +133,20 @@ export class LanguageModelsService implements ILanguageModelsService {
133133

134134
readonly _serviceBrand: undefined;
135135

136+
private readonly _store = new DisposableStore();
137+
136138
private readonly _providers = new Map<string, ILanguageModelChat>();
137139
private readonly _vendors = new Set<string>();
138140

139-
private readonly _onDidChangeProviders = new Emitter<ILanguageModelsChangeEvent>();
141+
private readonly _onDidChangeProviders = this._store.add(new Emitter<ILanguageModelsChangeEvent>());
140142
readonly onDidChangeLanguageModels: Event<ILanguageModelsChangeEvent> = this._onDidChangeProviders.event;
141143

142144
constructor(
143145
@IExtensionService private readonly _extensionService: IExtensionService,
144146
@ILogService private readonly _logService: ILogService,
145147
) {
146148

147-
languageModelExtensionPoint.setHandler((extensions) => {
149+
this._store.add(languageModelExtensionPoint.setHandler((extensions) => {
148150

149151
this._vendors.clear();
150152

@@ -182,11 +184,11 @@ export class LanguageModelsService implements ILanguageModelsService {
182184
if (removed.length > 0) {
183185
this._onDidChangeProviders.fire({ removed });
184186
}
185-
});
187+
}));
186188
}
187189

188190
dispose() {
189-
this._onDidChangeProviders.dispose();
191+
this._store.dispose();
190192
this._providers.clear();
191193
}
192194

@@ -213,22 +215,12 @@ export class LanguageModelsService implements ILanguageModelsService {
213215

214216
for (const [identifier, model] of this._providers) {
215217

216-
if (selector.vendor !== undefined && model.metadata.vendor === selector.vendor
217-
|| selector.family !== undefined && model.metadata.family === selector.family
218-
|| selector.version !== undefined && model.metadata.version === selector.version
219-
|| selector.identifier !== undefined && model.metadata.id === selector.identifier
220-
|| selector.extension !== undefined && model.metadata.targetExtensions?.some(candidate => ExtensionIdentifier.equals(candidate, selector.extension))
221-
) {
222-
// true selection
223-
result.push(identifier);
224-
225-
} else if (!selector || (
226-
selector.vendor === undefined
227-
&& selector.family === undefined
228-
&& selector.version === undefined
229-
&& selector.identifier === undefined)
218+
if ((selector.vendor === undefined || model.metadata.vendor === selector.vendor)
219+
&& (selector.family === undefined || model.metadata.family === selector.family)
220+
&& (selector.version === undefined || model.metadata.version === selector.version)
221+
&& (selector.identifier === undefined || model.metadata.id === selector.identifier)
222+
&& (!model.metadata.targetExtensions || model.metadata.targetExtensions.some(candidate => ExtensionIdentifier.equals(candidate, selector.extension)))
230223
) {
231-
// no selection
232224
result.push(identifier);
233225
}
234226
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as assert from 'assert';
7+
import { DisposableStore } from 'vs/base/common/lifecycle';
8+
import { mock } from 'vs/base/test/common/mock';
9+
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
10+
import { NullLogService } from 'vs/platform/log/common/log';
11+
import { languageModelExtensionPoint, LanguageModelsService } from 'vs/workbench/contrib/chat/common/languageModels';
12+
import { IExtensionService, nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
13+
import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry';
14+
15+
suite('LanguageModels', function () {
16+
17+
let languageModels: LanguageModelsService;
18+
19+
const store = new DisposableStore();
20+
const activationEvents = new Set<string>();
21+
22+
setup(function () {
23+
24+
languageModels = new LanguageModelsService(
25+
new class extends mock<IExtensionService>() {
26+
override activateByEvent(name: string) {
27+
activationEvents.add(name);
28+
return Promise.resolve();
29+
}
30+
},
31+
new NullLogService()
32+
);
33+
34+
const ext = ExtensionsRegistry.getExtensionPoints().find(e => e.name === languageModelExtensionPoint.name)!;
35+
36+
ext.acceptUsers([{
37+
description: { ...nullExtensionDescription, enabledApiProposals: ['chatProvider'] },
38+
value: { vendor: 'test-vendor' },
39+
collector: null!
40+
}]);
41+
42+
43+
store.add(languageModels.registerLanguageModelChat('1', {
44+
metadata: {
45+
extension: nullExtensionDescription.identifier,
46+
name: 'Pretty Name',
47+
vendor: 'test-vendor',
48+
family: 'test-family',
49+
version: 'test-version',
50+
id: 'test-id',
51+
maxInputTokens: 100,
52+
maxOutputTokens: 100,
53+
},
54+
provideChatResponse: async () => {
55+
throw new Error();
56+
},
57+
provideTokenCount: async () => {
58+
throw new Error();
59+
}
60+
}));
61+
62+
store.add(languageModels.registerLanguageModelChat('12', {
63+
metadata: {
64+
extension: nullExtensionDescription.identifier,
65+
name: 'Pretty Name',
66+
vendor: 'test-vendor',
67+
family: 'test2-family',
68+
version: 'test2-version',
69+
id: 'test-id',
70+
maxInputTokens: 100,
71+
maxOutputTokens: 100,
72+
},
73+
provideChatResponse: async () => {
74+
throw new Error();
75+
},
76+
provideTokenCount: async () => {
77+
throw new Error();
78+
}
79+
}));
80+
});
81+
82+
teardown(function () {
83+
languageModels.dispose();
84+
activationEvents.clear();
85+
store.clear();
86+
});
87+
88+
ensureNoDisposablesAreLeakedInTestSuite();
89+
90+
test('empty selector returns all', async function () {
91+
92+
const result1 = await languageModels.selectLanguageModels({});
93+
assert.deepStrictEqual(result1.length, 2);
94+
assert.deepStrictEqual(result1[0], '1');
95+
assert.deepStrictEqual(result1[1], '12');
96+
});
97+
98+
test('no warning that a matching model was not found #213716', async function () {
99+
const result1 = await languageModels.selectLanguageModels({ vendor: 'test-vendor' });
100+
assert.deepStrictEqual(result1.length, 2);
101+
102+
const result2 = await languageModels.selectLanguageModels({ vendor: 'test-vendor', family: 'FAKE' });
103+
assert.deepStrictEqual(result2.length, 0);
104+
});
105+
106+
107+
});

src/vs/workbench/services/extensions/common/extensionsRegistry.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { ExtensionKind } from 'vs/platform/environment/common/environment';
1616
import { allApiProposals } from 'vs/workbench/services/extensions/common/extensionsApiProposals';
1717
import { productSchemaId } from 'vs/platform/product/common/productService';
1818
import { ImplicitActivationEvents, IActivationEventsGenerator } from 'vs/platform/extensionManagement/common/implicitActivationEvents';
19+
import { IDisposable } from 'vs/base/common/lifecycle';
1920

2021
const schemaRegistry = Registry.as<IJSONContributionRegistry>(Extensions.JSONContribution);
2122

@@ -67,7 +68,7 @@ export type IExtensionPointHandler<T> = (extensions: readonly IExtensionPointUse
6768

6869
export interface IExtensionPoint<T> {
6970
readonly name: string;
70-
setHandler(handler: IExtensionPointHandler<T>): void;
71+
setHandler(handler: IExtensionPointHandler<T>): IDisposable;
7172
readonly defaultExtensionKind: ExtensionKind[] | undefined;
7273
}
7374

@@ -121,12 +122,18 @@ export class ExtensionPoint<T> implements IExtensionPoint<T> {
121122
this._delta = null;
122123
}
123124

124-
setHandler(handler: IExtensionPointHandler<T>): void {
125+
setHandler(handler: IExtensionPointHandler<T>): IDisposable {
125126
if (this._handler !== null) {
126127
throw new Error('Handler already set!');
127128
}
128129
this._handler = handler;
129130
this._handle();
131+
132+
return {
133+
dispose: () => {
134+
this._handler = null;
135+
}
136+
};
130137
}
131138

132139
acceptUsers(users: IExtensionPointUser<T>[]): void {

0 commit comments

Comments
 (0)