Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/chat/browser/actions/chatLanguageModelActions.ts
3296 views
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 { Action2, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js';
7
import { ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js';
8
import { IQuickInputService, IQuickPickItem, QuickPickInput } from '../../../../../platform/quickinput/common/quickInput.js';
9
import { ILanguageModelsService } from '../../common/languageModels.js';
10
import { IAuthenticationAccessService } from '../../../../services/authentication/browser/authenticationAccessService.js';
11
import { localize, localize2 } from '../../../../../nls.js';
12
import { AllowedExtension, INTERNAL_AUTH_PROVIDER_PREFIX } from '../../../../services/authentication/common/authentication.js';
13
import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.js';
14
import { CHAT_CATEGORY } from './chatActions.js';
15
import { IExtensionService } from '../../../../services/extensions/common/extensions.js';
16
import { IExtensionsWorkbenchService } from '../../../extensions/common/extensions.js';
17
import { IProductService } from '../../../../../platform/product/common/productService.js';
18
import { Codicon } from '../../../../../base/common/codicons.js';
19
import { ThemeIcon } from '../../../../../base/common/themables.js';
20
import { ChatContextKeys } from '../../common/chatContextKeys.js';
21
import { ManageModelsAction } from './manageModelsActions.js';
22
23
class ManageLanguageModelAuthenticationAction extends Action2 {
24
static readonly ID = 'workbench.action.chat.manageLanguageModelAuthentication';
25
26
constructor() {
27
super({
28
id: ManageLanguageModelAuthenticationAction.ID,
29
title: localize2('manageLanguageModelAuthentication', 'Manage Language Model Access...'),
30
category: CHAT_CATEGORY,
31
precondition: ChatContextKeys.enabled,
32
menu: [{
33
id: MenuId.AccountsContext,
34
order: 100,
35
}],
36
f1: true
37
});
38
}
39
40
async run(accessor: ServicesAccessor): Promise<void> {
41
const quickInputService = accessor.get(IQuickInputService);
42
const languageModelsService = accessor.get(ILanguageModelsService);
43
const authenticationAccessService = accessor.get(IAuthenticationAccessService);
44
const dialogService = accessor.get(IDialogService);
45
const extensionService = accessor.get(IExtensionService);
46
const extensionsWorkbenchService = accessor.get(IExtensionsWorkbenchService);
47
const productService = accessor.get(IProductService);
48
49
// Get all registered language models
50
const modelIds = languageModelsService.getLanguageModelIds();
51
52
// Group models by owning extension and collect all allowed extensions
53
const extensionAuth = new Map<string, AllowedExtension[]>();
54
55
const ownerToAccountLabel = new Map<string, string>();
56
for (const modelId of modelIds) {
57
const model = languageModelsService.lookupLanguageModel(modelId);
58
if (!model?.auth) {
59
continue; // Skip if model is not found
60
}
61
const ownerId = model.extension.value;
62
if (extensionAuth.has(ownerId)) {
63
// If the owner already exists, just continue
64
continue;
65
}
66
67
// Get allowed extensions for this model's auth provider
68
try {
69
// Use providerLabel as the providerId and accountLabel (or default)
70
const providerId = INTERNAL_AUTH_PROVIDER_PREFIX + ownerId;
71
const accountLabel = model.auth.accountLabel || 'Language Models';
72
ownerToAccountLabel.set(ownerId, accountLabel);
73
const allowedExtensions = authenticationAccessService.readAllowedExtensions(
74
providerId,
75
accountLabel
76
).filter(ext => !ext.trusted); // Filter out trusted extensions because those should not be modified
77
78
if (productService.trustedExtensionAuthAccess && !Array.isArray(productService.trustedExtensionAuthAccess)) {
79
const trustedExtensions = productService.trustedExtensionAuthAccess[providerId];
80
// If the provider is trusted, add all trusted extensions to the allowed list
81
for (const ext of trustedExtensions) {
82
const index = allowedExtensions.findIndex(a => a.id === ext);
83
if (index !== -1) {
84
allowedExtensions.splice(index, 1);
85
}
86
const extension = await extensionService.getExtension(ext);
87
if (!extension) {
88
continue; // Skip if the extension is not found
89
}
90
allowedExtensions.push({
91
id: ext,
92
name: extension.displayName || extension.name,
93
allowed: true, // Assume trusted extensions are allowed by default
94
trusted: true // Mark as trusted
95
});
96
}
97
}
98
99
// Only grab extensions that are gettable from the extension service
100
const filteredExtensions = new Array<AllowedExtension>();
101
for (const ext of allowedExtensions) {
102
if (await extensionService.getExtension(ext.id)) {
103
filteredExtensions.push(ext);
104
}
105
}
106
107
extensionAuth.set(ownerId, filteredExtensions);
108
// Add all allowed extensions to the set for this owner
109
} catch (error) {
110
// Handle error by ensuring the owner is in the map
111
if (!extensionAuth.has(ownerId)) {
112
extensionAuth.set(ownerId, []);
113
}
114
}
115
}
116
117
if (extensionAuth.size === 0) {
118
dialogService.prompt({
119
type: 'info',
120
message: localize('noLanguageModels', 'No language models requiring authentication found.'),
121
detail: localize('noLanguageModelsDetail', 'There are currently no language models that require authentication.')
122
});
123
return;
124
}
125
126
const items: QuickPickInput<IQuickPickItem & { extension?: AllowedExtension; ownerId?: string }>[] = [];
127
// Create QuickPick items grouped by owner extension
128
for (const [ownerId, allowedExtensions] of extensionAuth) {
129
const extension = await extensionService.getExtension(ownerId);
130
if (!extension) {
131
// If the extension is not found, skip it
132
continue;
133
}
134
// Add separator for the owning extension
135
items.push({
136
type: 'separator',
137
id: ownerId,
138
label: localize('extensionOwner', '{0}', extension.displayName || extension.name),
139
buttons: [{
140
iconClass: ThemeIcon.asClassName(Codicon.info),
141
tooltip: localize('openExtension', 'Open Extension'),
142
}]
143
});
144
145
// Add allowed extensions as checkboxes (visual representation)
146
let addedTrustedSeparator = false;
147
if (allowedExtensions.length > 0) {
148
for (const allowedExt of allowedExtensions) {
149
if (allowedExt.trusted && !addedTrustedSeparator) {
150
items.push({
151
type: 'separator',
152
label: localize('trustedExtension', 'Trusted by Microsoft'),
153
});
154
addedTrustedSeparator = true;
155
}
156
items.push({
157
label: allowedExt.name,
158
ownerId,
159
id: allowedExt.id,
160
picked: allowedExt.allowed ?? false,
161
extension: allowedExt,
162
disabled: allowedExt.trusted, // Don't allow toggling trusted extensions
163
buttons: [{
164
iconClass: ThemeIcon.asClassName(Codicon.info),
165
tooltip: localize('openExtension', 'Open Extension'),
166
}]
167
});
168
}
169
} else {
170
items.push({
171
label: localize('noAllowedExtensions', 'No extensions have access'),
172
description: localize('noAccessDescription', 'No extensions are currently allowed to use models from {0}', ownerId),
173
pickable: false
174
});
175
}
176
}
177
178
// Show the QuickPick
179
const result = await quickInputService.pick(
180
items,
181
{
182
canPickMany: true,
183
sortByLabel: true,
184
onDidTriggerSeparatorButton(context) {
185
// Handle separator button clicks
186
const extId = context.separator.id;
187
if (extId) {
188
// Open the extension in the editor
189
void extensionsWorkbenchService.open(extId);
190
}
191
},
192
onDidTriggerItemButton(context) {
193
// Handle item button clicks
194
const extId = context.item.id;
195
if (extId) {
196
// Open the extension in the editor
197
void extensionsWorkbenchService.open(extId);
198
}
199
},
200
title: localize('languageModelAuthTitle', 'Manage Language Model Access'),
201
placeHolder: localize('languageModelAuthPlaceholder', 'Choose which extensions can access language models'),
202
}
203
);
204
if (!result) {
205
return;
206
}
207
208
for (const [ownerId, allowedExtensions] of extensionAuth) {
209
// diff with result to find out which extensions are allowed or not
210
// but we need to only look at the result items that have the ownerId
211
const allowedSet = new Set(result
212
.filter(item => item.ownerId === ownerId)
213
// only save items that are not trusted automatically
214
.filter(item => !item.extension?.trusted)
215
.map(item => item.id!));
216
217
for (const allowedExt of allowedExtensions) {
218
allowedExt.allowed = allowedSet.has(allowedExt.id);
219
}
220
221
authenticationAccessService.updateAllowedExtensions(
222
INTERNAL_AUTH_PROVIDER_PREFIX + ownerId,
223
ownerToAccountLabel.get(ownerId) || 'Language Models',
224
allowedExtensions
225
);
226
}
227
228
}
229
}
230
231
export function registerLanguageModelActions() {
232
registerAction2(ManageLanguageModelAuthenticationAction);
233
registerAction2(ManageModelsAction);
234
}
235
236