@theia/ai-core 1.74.0-next.5 → 1.74.0-next.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/browser/ai-configuration-service-impl.d.ts +32 -0
- package/lib/browser/ai-configuration-service-impl.d.ts.map +1 -0
- package/lib/browser/ai-configuration-service-impl.js +120 -0
- package/lib/browser/ai-configuration-service-impl.js.map +1 -0
- package/lib/browser/ai-configuration-service-impl.spec.d.ts +2 -0
- package/lib/browser/ai-configuration-service-impl.spec.d.ts.map +1 -0
- package/lib/browser/ai-configuration-service-impl.spec.js +214 -0
- package/lib/browser/ai-configuration-service-impl.spec.js.map +1 -0
- package/lib/browser/ai-core-frontend-module.d.ts.map +1 -1
- package/lib/browser/ai-core-frontend-module.js +4 -0
- package/lib/browser/ai-core-frontend-module.js.map +1 -1
- package/lib/browser/ai-settings-service.d.ts +2 -5
- package/lib/browser/ai-settings-service.d.ts.map +1 -1
- package/lib/browser/ai-settings-service.js +8 -15
- package/lib/browser/ai-settings-service.js.map +1 -1
- package/lib/browser/frontend-language-model-alias-registry.d.ts +2 -4
- package/lib/browser/frontend-language-model-alias-registry.d.ts.map +1 -1
- package/lib/browser/frontend-language-model-alias-registry.js +11 -16
- package/lib/browser/frontend-language-model-alias-registry.js.map +1 -1
- package/lib/browser/frontend-language-model-service.d.ts +2 -3
- package/lib/browser/frontend-language-model-service.d.ts.map +1 -1
- package/lib/browser/frontend-language-model-service.js +6 -6
- package/lib/browser/frontend-language-model-service.js.map +1 -1
- package/lib/browser/frontend-prompt-customization-service.d.ts +38 -0
- package/lib/browser/frontend-prompt-customization-service.d.ts.map +1 -1
- package/lib/browser/frontend-prompt-customization-service.js +189 -76
- package/lib/browser/frontend-prompt-customization-service.js.map +1 -1
- package/lib/browser/frontend-prompt-customization-service.spec.js +103 -0
- package/lib/browser/frontend-prompt-customization-service.spec.js.map +1 -1
- package/lib/browser/index.d.ts +1 -0
- package/lib/browser/index.d.ts.map +1 -1
- package/lib/browser/index.js +1 -0
- package/lib/browser/index.js.map +1 -1
- package/lib/browser/trust-aware-preference-reader.d.ts +13 -1
- package/lib/browser/trust-aware-preference-reader.d.ts.map +1 -1
- package/lib/browser/trust-aware-preference-reader.js +21 -2
- package/lib/browser/trust-aware-preference-reader.js.map +1 -1
- package/lib/browser/trust-aware-preference-reader.spec.js +26 -0
- package/lib/browser/trust-aware-preference-reader.spec.js.map +1 -1
- package/lib/common/ai-configuration-service.d.ts +104 -0
- package/lib/common/ai-configuration-service.d.ts.map +1 -0
- package/lib/common/ai-configuration-service.js +20 -0
- package/lib/common/ai-configuration-service.js.map +1 -0
- package/lib/common/ai-core-preferences.d.ts +4 -0
- package/lib/common/ai-core-preferences.d.ts.map +1 -1
- package/lib/common/ai-core-preferences.js +20 -1
- package/lib/common/ai-core-preferences.js.map +1 -1
- package/lib/common/index.d.ts +1 -0
- package/lib/common/index.d.ts.map +1 -1
- package/lib/common/index.js +1 -0
- package/lib/common/index.js.map +1 -1
- package/lib/common/language-model.d.ts +56 -2
- package/lib/common/language-model.d.ts.map +1 -1
- package/lib/common/language-model.js +53 -3
- package/lib/common/language-model.js.map +1 -1
- package/lib/common/language-model.spec.js +75 -0
- package/lib/common/language-model.spec.js.map +1 -1
- package/lib/common/prompt-service.d.ts +13 -3
- package/lib/common/prompt-service.d.ts.map +1 -1
- package/lib/common/prompt-service.js.map +1 -1
- package/lib/node/backend-language-model-registry.d.ts.map +1 -1
- package/lib/node/backend-language-model-registry.js +1 -0
- package/lib/node/backend-language-model-registry.js.map +1 -1
- package/package.json +11 -11
- package/src/browser/ai-configuration-service-impl.spec.ts +254 -0
- package/src/browser/ai-configuration-service-impl.ts +120 -0
- package/src/browser/ai-core-frontend-module.ts +7 -1
- package/src/browser/ai-settings-service.ts +8 -16
- package/src/browser/frontend-language-model-alias-registry.ts +12 -16
- package/src/browser/frontend-language-model-service.ts +5 -6
- package/src/browser/frontend-prompt-customization-service.spec.ts +130 -1
- package/src/browser/frontend-prompt-customization-service.ts +204 -84
- package/src/browser/index.ts +1 -0
- package/src/browser/trust-aware-preference-reader.spec.ts +29 -0
- package/src/browser/trust-aware-preference-reader.ts +23 -3
- package/src/common/ai-configuration-service.ts +128 -0
- package/src/common/ai-core-preferences.ts +23 -1
- package/src/common/index.ts +1 -0
- package/src/common/language-model.spec.ts +98 -1
- package/src/common/language-model.ts +107 -4
- package/src/common/prompt-service.ts +14 -3
- package/src/node/backend-language-model-registry.ts +1 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2026 EclipseSource GmbH.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import { enableJSDOM } from '@theia/core/lib/browser/test/jsdom';
|
|
18
|
+
const disableJSDOM = enableJSDOM();
|
|
19
|
+
import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/frontend-application-config-provider';
|
|
20
|
+
FrontendApplicationConfigProvider.set({});
|
|
21
|
+
|
|
22
|
+
import { expect } from 'chai';
|
|
23
|
+
import { Container } from '@theia/core/shared/inversify';
|
|
24
|
+
import { Emitter, Event } from '@theia/core';
|
|
25
|
+
import { Deferred } from '@theia/core/lib/common/promise-util';
|
|
26
|
+
import { PreferenceChange, PreferenceInspection, PreferenceScope, PreferenceService } from '@theia/core/lib/common/preferences';
|
|
27
|
+
import { WorkspaceTrustService } from '@theia/workspace/lib/browser/workspace-trust-service';
|
|
28
|
+
import { TrustAwarePreferenceReader } from './trust-aware-preference-reader';
|
|
29
|
+
import { AiConfigurationChange } from '../common/ai-configuration-service';
|
|
30
|
+
import { AiConfigurationServiceImpl } from './ai-configuration-service-impl';
|
|
31
|
+
|
|
32
|
+
disableJSDOM();
|
|
33
|
+
|
|
34
|
+
const KEY = 'ai-features.someKey';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Minimal in-memory `PreferenceService` stub that stores values per scope and derives the
|
|
38
|
+
* effective value with the same precedence as the real service.
|
|
39
|
+
*/
|
|
40
|
+
class StubPreferenceService {
|
|
41
|
+
readonly ready = Promise.resolve();
|
|
42
|
+
readonly isReady = true;
|
|
43
|
+
|
|
44
|
+
protected readonly values = new Map<string, Map<PreferenceScope, unknown>>();
|
|
45
|
+
|
|
46
|
+
protected readonly onPreferenceChangedEmitter = new Emitter<PreferenceChange>();
|
|
47
|
+
readonly onPreferenceChanged: Event<PreferenceChange> = this.onPreferenceChangedEmitter.event;
|
|
48
|
+
|
|
49
|
+
protected scopeMap(key: string): Map<PreferenceScope, unknown> {
|
|
50
|
+
let map = this.values.get(key);
|
|
51
|
+
if (!map) {
|
|
52
|
+
map = new Map();
|
|
53
|
+
this.values.set(key, map);
|
|
54
|
+
}
|
|
55
|
+
return map;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
setScopeValue(key: string, scope: PreferenceScope, value: unknown): void {
|
|
59
|
+
this.scopeMap(key).set(scope, value);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
get<T>(preferenceName: string, defaultValue?: T): T | undefined {
|
|
63
|
+
const inspection = this.inspect<T>(preferenceName);
|
|
64
|
+
return (inspection?.value as T | undefined) ?? defaultValue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
set(preferenceName: string, value: unknown, scope: PreferenceScope = PreferenceScope.User): Promise<void> {
|
|
68
|
+
this.scopeMap(preferenceName).set(scope, value);
|
|
69
|
+
this.fire(preferenceName, scope);
|
|
70
|
+
return Promise.resolve();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
updateValue(preferenceName: string, value: unknown): Promise<void> {
|
|
74
|
+
return this.set(preferenceName, value, PreferenceScope.User);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
inspect<T>(preferenceName: string): PreferenceInspection<T> | undefined {
|
|
78
|
+
const map = this.values.get(preferenceName);
|
|
79
|
+
const defaultValue = map?.get(PreferenceScope.Default) as T | undefined;
|
|
80
|
+
const globalValue = map?.get(PreferenceScope.User) as T | undefined;
|
|
81
|
+
const workspaceValue = map?.get(PreferenceScope.Workspace) as T | undefined;
|
|
82
|
+
const workspaceFolderValue = map?.get(PreferenceScope.Folder) as T | undefined;
|
|
83
|
+
const value = workspaceFolderValue ?? workspaceValue ?? globalValue ?? defaultValue;
|
|
84
|
+
return { preferenceName, defaultValue, globalValue, workspaceValue, workspaceFolderValue, value };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
protected fire(preferenceName: string, scope: PreferenceScope): void {
|
|
88
|
+
this.onPreferenceChangedEmitter.fire({
|
|
89
|
+
preferenceName,
|
|
90
|
+
scope,
|
|
91
|
+
domain: undefined,
|
|
92
|
+
affects: () => true
|
|
93
|
+
} as unknown as PreferenceChange);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
class StubWorkspaceTrustService {
|
|
98
|
+
readonly trustDeferred = new Deferred<boolean>();
|
|
99
|
+
protected readonly emitter = new Emitter<boolean>();
|
|
100
|
+
readonly onDidChangeWorkspaceTrust: Event<boolean> = this.emitter.event;
|
|
101
|
+
|
|
102
|
+
getWorkspaceTrust(): Promise<boolean> {
|
|
103
|
+
return this.trustDeferred.promise;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
fireTrustChange(trusted: boolean): void {
|
|
107
|
+
this.emitter.fire(trusted);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
describe('AiConfigurationServiceImpl', () => {
|
|
112
|
+
let preferences: StubPreferenceService;
|
|
113
|
+
let trust: StubWorkspaceTrustService;
|
|
114
|
+
let service: AiConfigurationServiceImpl;
|
|
115
|
+
|
|
116
|
+
const createService = async (trusted: boolean): Promise<void> => {
|
|
117
|
+
preferences = new StubPreferenceService();
|
|
118
|
+
trust = new StubWorkspaceTrustService();
|
|
119
|
+
|
|
120
|
+
const container = new Container();
|
|
121
|
+
container.bind(PreferenceService).toConstantValue(preferences as unknown as PreferenceService);
|
|
122
|
+
container.bind(WorkspaceTrustService).toConstantValue(trust as unknown as WorkspaceTrustService);
|
|
123
|
+
container.bind(TrustAwarePreferenceReader).toSelf().inSingletonScope();
|
|
124
|
+
container.bind(AiConfigurationServiceImpl).toSelf().inSingletonScope();
|
|
125
|
+
|
|
126
|
+
service = container.get(AiConfigurationServiceImpl);
|
|
127
|
+
trust.trustDeferred.resolve(trusted);
|
|
128
|
+
await service.ready;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
describe('acceptance scenario', () => {
|
|
132
|
+
beforeEach(() => createService(true));
|
|
133
|
+
|
|
134
|
+
it('writes at User scope, reads back, reports User as source scope, and fires onDidChange', async () => {
|
|
135
|
+
const changes: AiConfigurationChange[] = [];
|
|
136
|
+
service.onDidChange(change => changes.push(change));
|
|
137
|
+
|
|
138
|
+
await service.set(KEY, true, PreferenceScope.User);
|
|
139
|
+
|
|
140
|
+
expect(service.get<boolean>(KEY)).to.equal(true);
|
|
141
|
+
expect(service.inspect(KEY)?.sourceScope).to.equal(PreferenceScope.User);
|
|
142
|
+
expect(changes.some(change => change.preferenceName === KEY)).to.equal(true);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe('inspect source-scope derivation (trusted)', () => {
|
|
147
|
+
beforeEach(() => createService(true));
|
|
148
|
+
|
|
149
|
+
it('reports undefined source scope when only the default applies', () => {
|
|
150
|
+
preferences.setScopeValue(KEY, PreferenceScope.Default, 'default');
|
|
151
|
+
const inspection = service.inspect<string>(KEY);
|
|
152
|
+
expect(inspection?.sourceScope).to.equal(undefined);
|
|
153
|
+
expect(inspection?.value).to.equal('default');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('reports User when a user value is set', () => {
|
|
157
|
+
preferences.setScopeValue(KEY, PreferenceScope.Default, 'default');
|
|
158
|
+
preferences.setScopeValue(KEY, PreferenceScope.User, 'user');
|
|
159
|
+
const inspection = service.inspect<string>(KEY);
|
|
160
|
+
expect(inspection?.sourceScope).to.equal(PreferenceScope.User);
|
|
161
|
+
expect(inspection?.value).to.equal('user');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('reports Workspace when a workspace value wins', () => {
|
|
165
|
+
preferences.setScopeValue(KEY, PreferenceScope.User, 'user');
|
|
166
|
+
preferences.setScopeValue(KEY, PreferenceScope.Workspace, 'workspace');
|
|
167
|
+
const inspection = service.inspect<string>(KEY);
|
|
168
|
+
expect(inspection?.sourceScope).to.equal(PreferenceScope.Workspace);
|
|
169
|
+
expect(inspection?.value).to.equal('workspace');
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
describe('trust-aware reads', () => {
|
|
174
|
+
it('ignores the workspace value when untrusted', async () => {
|
|
175
|
+
await createService(false);
|
|
176
|
+
preferences.setScopeValue(KEY, PreferenceScope.Default, 'default');
|
|
177
|
+
preferences.setScopeValue(KEY, PreferenceScope.User, 'user');
|
|
178
|
+
preferences.setScopeValue(KEY, PreferenceScope.Workspace, 'workspace');
|
|
179
|
+
|
|
180
|
+
expect(service.get<string>(KEY)).to.equal('user');
|
|
181
|
+
const inspection = service.inspect<string>(KEY);
|
|
182
|
+
expect(inspection?.value).to.equal('user');
|
|
183
|
+
expect(inspection?.sourceScope).to.equal(PreferenceScope.User);
|
|
184
|
+
// The suppressed workspace/folder values are cleared, not just excluded from `value`.
|
|
185
|
+
expect(inspection?.workspaceValue).to.equal(undefined);
|
|
186
|
+
expect(inspection?.workspaceFolderValue).to.equal(undefined);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('honors the workspace value when trusted', async () => {
|
|
190
|
+
await createService(true);
|
|
191
|
+
preferences.setScopeValue(KEY, PreferenceScope.User, 'user');
|
|
192
|
+
preferences.setScopeValue(KEY, PreferenceScope.Workspace, 'workspace');
|
|
193
|
+
|
|
194
|
+
expect(service.get<string>(KEY)).to.equal('workspace');
|
|
195
|
+
expect(service.inspect<string>(KEY)?.value).to.equal('workspace');
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('fires onDidChange (without a preference name) on a trust transition', async () => {
|
|
199
|
+
await createService(false);
|
|
200
|
+
const changes: AiConfigurationChange[] = [];
|
|
201
|
+
service.onDidChange(change => changes.push(change));
|
|
202
|
+
|
|
203
|
+
trust.fireTrustChange(true);
|
|
204
|
+
|
|
205
|
+
expect(changes).to.have.lengthOf(1);
|
|
206
|
+
expect(changes[0].preferenceName).to.equal(undefined);
|
|
207
|
+
expect(changes[0].affects()).to.equal(true);
|
|
208
|
+
// A trust transition affects every trust-gated (ai-features.*) key, but not unrelated ones.
|
|
209
|
+
expect(changes[0].affectsPreference(KEY)).to.equal(true);
|
|
210
|
+
expect(changes[0].affectsPreference('ai-features.anythingElse')).to.equal(true);
|
|
211
|
+
expect(changes[0].affectsPreference('editor.fontSize')).to.equal(false);
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
describe('set vs update', () => {
|
|
216
|
+
beforeEach(() => createService(true));
|
|
217
|
+
|
|
218
|
+
it('set writes the exact scope requested', async () => {
|
|
219
|
+
await service.set(KEY, 'workspace', PreferenceScope.Workspace);
|
|
220
|
+
expect(preferences.inspect<string>(KEY)?.workspaceValue).to.equal('workspace');
|
|
221
|
+
expect(preferences.inspect<string>(KEY)?.globalValue).to.equal(undefined);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('update maps to updateValue (User scope)', async () => {
|
|
225
|
+
await service.update(KEY, 'user');
|
|
226
|
+
expect(preferences.inspect<string>(KEY)?.globalValue).to.equal('user');
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
describe('onDidChange filtering', () => {
|
|
231
|
+
beforeEach(() => createService(true));
|
|
232
|
+
|
|
233
|
+
it('only fires for ai-features.* keys', async () => {
|
|
234
|
+
const changes: AiConfigurationChange[] = [];
|
|
235
|
+
service.onDidChange(change => changes.push(change));
|
|
236
|
+
|
|
237
|
+
await service.set('editor.fontSize', 12, PreferenceScope.User);
|
|
238
|
+
await service.set(KEY, true, PreferenceScope.User);
|
|
239
|
+
|
|
240
|
+
expect(changes.map(change => change.preferenceName)).to.deep.equal([KEY]);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('affectsPreference matches the changed key on a keyed change, not unrelated keys', async () => {
|
|
244
|
+
const changes: AiConfigurationChange[] = [];
|
|
245
|
+
service.onDidChange(change => changes.push(change));
|
|
246
|
+
|
|
247
|
+
await service.set(KEY, true, PreferenceScope.User);
|
|
248
|
+
|
|
249
|
+
expect(changes).to.have.lengthOf(1);
|
|
250
|
+
expect(changes[0].affectsPreference(KEY)).to.equal(true);
|
|
251
|
+
expect(changes[0].affectsPreference('ai-features.other')).to.equal(false);
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
});
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2026 EclipseSource GmbH.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import { DisposableCollection, Emitter, Event } from '@theia/core';
|
|
18
|
+
import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
|
|
19
|
+
import { JSONValue } from '@theia/core/shared/@lumino/coreutils';
|
|
20
|
+
import { Deferred } from '@theia/core/lib/common/promise-util';
|
|
21
|
+
import { PreferenceInspection, PreferenceScope, PreferenceService } from '@theia/core/lib/common/preferences';
|
|
22
|
+
import { AiConfigurationChange, AiConfigurationInspection, AiConfigurationService } from '../common/ai-configuration-service';
|
|
23
|
+
import { TrustAwarePreferenceReader } from './trust-aware-preference-reader';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Prefix shared by all AI-related preference keys. Only changes to keys under this namespace are
|
|
27
|
+
* surfaced through {@link AiConfigurationService.onDidChange}.
|
|
28
|
+
*/
|
|
29
|
+
const AI_PREFERENCE_PREFIX = 'ai-features.';
|
|
30
|
+
|
|
31
|
+
@injectable()
|
|
32
|
+
export class AiConfigurationServiceImpl implements AiConfigurationService {
|
|
33
|
+
|
|
34
|
+
@inject(PreferenceService)
|
|
35
|
+
protected readonly preferenceService: PreferenceService;
|
|
36
|
+
|
|
37
|
+
@inject(TrustAwarePreferenceReader)
|
|
38
|
+
protected readonly trustAwareReader: TrustAwarePreferenceReader;
|
|
39
|
+
|
|
40
|
+
protected readonly toDispose = new DisposableCollection();
|
|
41
|
+
|
|
42
|
+
protected readonly onDidChangeEmitter = new Emitter<AiConfigurationChange>();
|
|
43
|
+
readonly onDidChange: Event<AiConfigurationChange> = this.onDidChangeEmitter.event;
|
|
44
|
+
|
|
45
|
+
protected readonly _ready = new Deferred<void>();
|
|
46
|
+
get ready(): Promise<void> {
|
|
47
|
+
return this._ready.promise;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@postConstruct()
|
|
51
|
+
protected init(): void {
|
|
52
|
+
this._ready.resolve(Promise.all([this.preferenceService.ready, this.trustAwareReader.ready]).then(() => undefined));
|
|
53
|
+
this.toDispose.push(this.onDidChangeEmitter);
|
|
54
|
+
this.toDispose.push(
|
|
55
|
+
this.preferenceService.onPreferenceChanged(change => {
|
|
56
|
+
if (change.preferenceName.startsWith(AI_PREFERENCE_PREFIX)) {
|
|
57
|
+
this.onDidChangeEmitter.fire({
|
|
58
|
+
preferenceName: change.preferenceName,
|
|
59
|
+
affects: resourceUri => change.affects(resourceUri),
|
|
60
|
+
affectsPreference: preferenceName => preferenceName === change.preferenceName
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
);
|
|
65
|
+
this.toDispose.push(
|
|
66
|
+
// A trust transition can change the effective value of any trust-gated key. Emit a
|
|
67
|
+
// single change with no `preferenceName` and a sentinel `affects()` so listeners
|
|
68
|
+
// re-query, mirroring how trust-aware services already react to trust changes.
|
|
69
|
+
this.trustAwareReader.onDidChangeTrust(() => this.onDidChangeEmitter.fire({
|
|
70
|
+
preferenceName: undefined,
|
|
71
|
+
affects: () => true,
|
|
72
|
+
affectsPreference: preferenceName => preferenceName.startsWith(AI_PREFERENCE_PREFIX)
|
|
73
|
+
}))
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
get<T>(key: string, defaultValue?: T, resourceUri?: string): T | undefined {
|
|
78
|
+
return this.trustAwareReader.get<T>(key, defaultValue, resourceUri);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
set(key: string, value: unknown, scope: PreferenceScope, resourceUri?: string): Promise<void> {
|
|
82
|
+
return this.preferenceService.set(key, value, scope, resourceUri);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
update(key: string, value: unknown, resourceUri?: string): Promise<void> {
|
|
86
|
+
return this.preferenceService.updateValue(key, value, resourceUri);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
inspect<T extends JSONValue>(key: string, resourceUri?: string): AiConfigurationInspection<T> | undefined {
|
|
90
|
+
const inspection = this.preferenceService.inspect<T>(key, resourceUri);
|
|
91
|
+
if (!inspection) {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
return this.enrichInspection(inspection);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Derives the trust-aware {@link AiConfigurationInspection.sourceScope} and effective `value`
|
|
99
|
+
* from a raw {@link PreferenceInspection}. The workspace-trust rule is applied once, via
|
|
100
|
+
* {@link TrustAwarePreferenceReader.suppressUntrusted} (the single owner of that rule): when
|
|
101
|
+
* untrusted, folder and workspace scope values are cleared, so the scope walk below naturally
|
|
102
|
+
* resolves to the user/default scope and the whole inspection stays consistent with {@link get}.
|
|
103
|
+
*
|
|
104
|
+
* Note: `value` reflects the narrowest defined scope, matching `PreferenceService.inspect`. For
|
|
105
|
+
* object-valued preferences this can differ from {@link get}, which deep-merges across scopes.
|
|
106
|
+
*/
|
|
107
|
+
protected enrichInspection<T extends JSONValue>(inspection: PreferenceInspection<T>): AiConfigurationInspection<T> {
|
|
108
|
+
const suppressed = this.trustAwareReader.suppressUntrusted(inspection);
|
|
109
|
+
if (suppressed.workspaceFolderValue !== undefined) {
|
|
110
|
+
return { ...suppressed, sourceScope: PreferenceScope.Folder, value: suppressed.workspaceFolderValue };
|
|
111
|
+
}
|
|
112
|
+
if (suppressed.workspaceValue !== undefined) {
|
|
113
|
+
return { ...suppressed, sourceScope: PreferenceScope.Workspace, value: suppressed.workspaceValue };
|
|
114
|
+
}
|
|
115
|
+
if (suppressed.globalValue !== undefined) {
|
|
116
|
+
return { ...suppressed, sourceScope: PreferenceScope.User, value: suppressed.globalValue };
|
|
117
|
+
}
|
|
118
|
+
return { ...suppressed, sourceScope: undefined, value: suppressed.defaultValue };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
import { ContainerModule } from '@theia/core/shared/inversify';
|
|
23
23
|
import { DefaultLanguageModelAliasRegistry } from './frontend-language-model-alias-registry';
|
|
24
24
|
import { TrustAwarePreferenceReader } from './trust-aware-preference-reader';
|
|
25
|
+
import { AiConfigurationServiceImpl } from './ai-configuration-service-impl';
|
|
25
26
|
import { LanguageModelAliasRegistry } from '../common/language-model-alias';
|
|
26
27
|
import {
|
|
27
28
|
AIVariableContribution,
|
|
@@ -46,7 +47,8 @@ import {
|
|
|
46
47
|
AIVariableResourceResolver,
|
|
47
48
|
ConfigurableInMemoryResources,
|
|
48
49
|
Agent,
|
|
49
|
-
FrontendLanguageModelRegistry
|
|
50
|
+
FrontendLanguageModelRegistry,
|
|
51
|
+
AiConfigurationService
|
|
50
52
|
} from '../common';
|
|
51
53
|
import {
|
|
52
54
|
FrontendLanguageModelRegistryImpl,
|
|
@@ -196,8 +198,12 @@ export default new ContainerModule(bind => {
|
|
|
196
198
|
bind(DefaultLanguageModelAliasRegistry).toSelf().inSingletonScope();
|
|
197
199
|
bind(LanguageModelAliasRegistry).toService(DefaultLanguageModelAliasRegistry);
|
|
198
200
|
|
|
201
|
+
// Internal implementation detail of AiConfigurationService; consumers inject AiConfigurationService.
|
|
199
202
|
bind(TrustAwarePreferenceReader).toSelf().inSingletonScope();
|
|
200
203
|
|
|
204
|
+
bind(AiConfigurationServiceImpl).toSelf().inSingletonScope();
|
|
205
|
+
bind(AiConfigurationService).toService(AiConfigurationServiceImpl);
|
|
206
|
+
|
|
201
207
|
bind(TokenUsageService).toDynamicValue(ctx => {
|
|
202
208
|
const connection = ctx.container.get<ServiceConnectionProvider>(RemoteConnectionProvider);
|
|
203
209
|
const client = ctx.container.get<TokenUsageServiceClient>(TokenUsageServiceClient);
|
|
@@ -15,9 +15,7 @@
|
|
|
15
15
|
// *****************************************************************************
|
|
16
16
|
import { DisposableCollection, Emitter, Event, ILogger, RecursiveReadonly } from '@theia/core';
|
|
17
17
|
import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
|
|
18
|
-
import {
|
|
19
|
-
import { AISettings, AISettingsService, AgentSettings } from '../common';
|
|
20
|
-
import { TrustAwarePreferenceReader } from './trust-aware-preference-reader';
|
|
18
|
+
import { AiConfigurationService, AISettings, AISettingsService, AgentSettings } from '../common';
|
|
21
19
|
|
|
22
20
|
@injectable()
|
|
23
21
|
export class AISettingsServiceImpl implements AISettingsService {
|
|
@@ -25,10 +23,8 @@ export class AISettingsServiceImpl implements AISettingsService {
|
|
|
25
23
|
@inject(ILogger)
|
|
26
24
|
protected readonly logger: ILogger;
|
|
27
25
|
|
|
28
|
-
@inject(
|
|
29
|
-
|
|
30
|
-
@inject(TrustAwarePreferenceReader)
|
|
31
|
-
protected readonly trustAwareReader: TrustAwarePreferenceReader;
|
|
26
|
+
@inject(AiConfigurationService)
|
|
27
|
+
protected readonly aiConfigurationService: AiConfigurationService;
|
|
32
28
|
|
|
33
29
|
static readonly PREFERENCE_NAME = 'ai-features.agentSettings';
|
|
34
30
|
|
|
@@ -40,22 +36,19 @@ export class AISettingsServiceImpl implements AISettingsService {
|
|
|
40
36
|
@postConstruct()
|
|
41
37
|
protected init(): void {
|
|
42
38
|
this.toDispose.push(
|
|
43
|
-
this.
|
|
44
|
-
if (
|
|
39
|
+
this.aiConfigurationService.onDidChange(change => {
|
|
40
|
+
if (change.affectsPreference(AISettingsServiceImpl.PREFERENCE_NAME)) {
|
|
45
41
|
this.onDidChangeEmitter.fire();
|
|
46
42
|
}
|
|
47
43
|
})
|
|
48
44
|
);
|
|
49
|
-
this.toDispose.push(
|
|
50
|
-
this.trustAwareReader.onDidChangeTrust(() => this.onDidChangeEmitter.fire())
|
|
51
|
-
);
|
|
52
45
|
}
|
|
53
46
|
|
|
54
47
|
async updateAgentSettings(agent: string, agentSettings: Partial<AgentSettings>): Promise<void> {
|
|
55
48
|
const settings = await this.getSettings();
|
|
56
49
|
const toSet = { ...settings, [agent]: { ...settings[agent], ...agentSettings } };
|
|
57
50
|
try {
|
|
58
|
-
await this.
|
|
51
|
+
await this.aiConfigurationService.update(AISettingsServiceImpl.PREFERENCE_NAME, toSet);
|
|
59
52
|
} catch (e) {
|
|
60
53
|
this.onDidChangeEmitter.fire();
|
|
61
54
|
this.logger.warn('Updating the preferences was unsuccessful: ' + e);
|
|
@@ -68,8 +61,7 @@ export class AISettingsServiceImpl implements AISettingsService {
|
|
|
68
61
|
}
|
|
69
62
|
|
|
70
63
|
async getSettings(): Promise<RecursiveReadonly<AISettings>> {
|
|
71
|
-
await this.
|
|
72
|
-
|
|
73
|
-
return this.trustAwareReader.get<AISettings>(AISettingsServiceImpl.PREFERENCE_NAME, {}) ?? {};
|
|
64
|
+
await this.aiConfigurationService.ready;
|
|
65
|
+
return this.aiConfigurationService.get<AISettings>(AISettingsServiceImpl.PREFERENCE_NAME, {}) ?? {};
|
|
74
66
|
}
|
|
75
67
|
}
|
|
@@ -17,10 +17,10 @@
|
|
|
17
17
|
import { injectable, inject, postConstruct } from '@theia/core/shared/inversify';
|
|
18
18
|
import { Emitter, Event, nls } from '@theia/core';
|
|
19
19
|
import { LanguageModelAlias, LanguageModelAliasRegistry } from '../common/language-model-alias';
|
|
20
|
-
import { PreferenceScope
|
|
20
|
+
import { PreferenceScope } from '@theia/core/lib/common';
|
|
21
21
|
import { LANGUAGE_MODEL_ALIASES_PREFERENCE } from '../common/ai-core-preferences';
|
|
22
22
|
import { Deferred } from '@theia/core/lib/common/promise-util';
|
|
23
|
-
import {
|
|
23
|
+
import { AiConfigurationService } from '../common/ai-configuration-service';
|
|
24
24
|
|
|
25
25
|
@injectable()
|
|
26
26
|
export class DefaultLanguageModelAliasRegistry implements LanguageModelAliasRegistry {
|
|
@@ -76,11 +76,8 @@ export class DefaultLanguageModelAliasRegistry implements LanguageModelAliasRegi
|
|
|
76
76
|
protected readonly onDidChangeEmitter = new Emitter<void>();
|
|
77
77
|
readonly onDidChange: Event<void> = this.onDidChangeEmitter.event;
|
|
78
78
|
|
|
79
|
-
@inject(
|
|
80
|
-
protected readonly
|
|
81
|
-
|
|
82
|
-
@inject(TrustAwarePreferenceReader)
|
|
83
|
-
protected readonly trustAwareReader: TrustAwarePreferenceReader;
|
|
79
|
+
@inject(AiConfigurationService)
|
|
80
|
+
protected readonly aiConfigurationService: AiConfigurationService;
|
|
84
81
|
|
|
85
82
|
protected readonly _ready = new Deferred<void>();
|
|
86
83
|
get ready(): Promise<void> {
|
|
@@ -89,17 +86,16 @@ export class DefaultLanguageModelAliasRegistry implements LanguageModelAliasRegi
|
|
|
89
86
|
|
|
90
87
|
@postConstruct()
|
|
91
88
|
protected init(): void {
|
|
92
|
-
|
|
89
|
+
this.aiConfigurationService.ready.then(() => {
|
|
93
90
|
this.loadFromPreference();
|
|
94
|
-
this.
|
|
95
|
-
if (
|
|
91
|
+
this.aiConfigurationService.onDidChange(change => {
|
|
92
|
+
if (change.affectsPreference(LANGUAGE_MODEL_ALIASES_PREFERENCE)) {
|
|
96
93
|
this.loadFromPreference();
|
|
94
|
+
if (!change.preferenceName) {
|
|
95
|
+
this.onDidChangeEmitter.fire();
|
|
96
|
+
}
|
|
97
97
|
}
|
|
98
98
|
});
|
|
99
|
-
this.trustAwareReader.onDidChangeTrust(() => {
|
|
100
|
-
this.loadFromPreference();
|
|
101
|
-
this.onDidChangeEmitter.fire();
|
|
102
|
-
});
|
|
103
99
|
this._ready.resolve();
|
|
104
100
|
}, err => {
|
|
105
101
|
this._ready.reject(err);
|
|
@@ -158,7 +154,7 @@ export class DefaultLanguageModelAliasRegistry implements LanguageModelAliasRegi
|
|
|
158
154
|
* Load aliases from the persisted setting
|
|
159
155
|
*/
|
|
160
156
|
protected loadFromPreference(): void {
|
|
161
|
-
const stored = this.
|
|
157
|
+
const stored = this.aiConfigurationService.get<{ [name: string]: { selectedModel: string } }>(LANGUAGE_MODEL_ALIASES_PREFERENCE) || {};
|
|
162
158
|
this.aliases.forEach(alias => {
|
|
163
159
|
if (stored[alias.id] && stored[alias.id].selectedModel) {
|
|
164
160
|
alias.selectedModelId = stored[alias.id].selectedModel;
|
|
@@ -178,6 +174,6 @@ export class DefaultLanguageModelAliasRegistry implements LanguageModelAliasRegi
|
|
|
178
174
|
map[alias.id] = { selectedModel: alias.selectedModelId };
|
|
179
175
|
}
|
|
180
176
|
}
|
|
181
|
-
this.
|
|
177
|
+
this.aiConfigurationService.set(LANGUAGE_MODEL_ALIASES_PREFERENCE, map, PreferenceScope.User);
|
|
182
178
|
}
|
|
183
179
|
}
|
|
@@ -18,7 +18,7 @@ import { nls } from '@theia/core/lib/common/nls';
|
|
|
18
18
|
import { inject, injectable } from '@theia/core/shared/inversify';
|
|
19
19
|
import { Prioritizeable } from '@theia/core/lib/common/prioritizeable';
|
|
20
20
|
import { WorkspaceTrustService } from '@theia/workspace/lib/browser/workspace-trust-service';
|
|
21
|
-
import { LanguageModel, LanguageModelResponse, ReasoningSettings, UserRequest } from '../common';
|
|
21
|
+
import { AiConfigurationService, LanguageModel, LanguageModelResponse, ReasoningSettings, UserRequest } from '../common';
|
|
22
22
|
import { LanguageModelServiceImpl } from '../common/language-model-service';
|
|
23
23
|
import {
|
|
24
24
|
PREFERENCE_NAME_REQUEST_SETTINGS,
|
|
@@ -27,7 +27,6 @@ import {
|
|
|
27
27
|
ReasoningPreferenceEntry,
|
|
28
28
|
getRequestSettingSpecificity
|
|
29
29
|
} from '../common/ai-core-preferences';
|
|
30
|
-
import { TrustAwarePreferenceReader } from './trust-aware-preference-reader';
|
|
31
30
|
|
|
32
31
|
@injectable()
|
|
33
32
|
export class FrontendLanguageModelServiceImpl extends LanguageModelServiceImpl {
|
|
@@ -35,15 +34,15 @@ export class FrontendLanguageModelServiceImpl extends LanguageModelServiceImpl {
|
|
|
35
34
|
@inject(WorkspaceTrustService)
|
|
36
35
|
protected readonly workspaceTrustService: WorkspaceTrustService;
|
|
37
36
|
|
|
38
|
-
@inject(
|
|
39
|
-
protected readonly
|
|
37
|
+
@inject(AiConfigurationService)
|
|
38
|
+
protected readonly aiConfiguration: AiConfigurationService;
|
|
40
39
|
|
|
41
40
|
override async sendRequest(
|
|
42
41
|
languageModel: LanguageModel,
|
|
43
42
|
languageModelRequest: UserRequest
|
|
44
43
|
): Promise<LanguageModelResponse> {
|
|
45
|
-
const requestSettings = this.
|
|
46
|
-
const reasoningEntries = this.
|
|
44
|
+
const requestSettings = this.aiConfiguration.get<RequestSetting[]>(PREFERENCE_NAME_REQUEST_SETTINGS, []) ?? [];
|
|
45
|
+
const reasoningEntries = this.aiConfiguration.get<ReasoningPreferenceEntry[]>(PREFERENCE_NAME_REASONING, []) ?? [];
|
|
47
46
|
const trusted = await this.workspaceTrustService.getWorkspaceTrust();
|
|
48
47
|
if (!trusted) {
|
|
49
48
|
throw new Error(nls.localize('theia/ai-core/aiDisabledInRestrictedMode', 'AI features are not available in untrusted workspaces.'));
|