Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/chat/browser/contextContrib/chatContextService.ts
5251 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 { ThemeIcon } from '../../../../../base/common/themables.js';
7
import { LanguageSelector, score } from '../../../../../editor/common/languageSelector.js';
8
import { createDecorator } from '../../../../../platform/instantiation/common/instantiation.js';
9
import { IChatContextPicker, IChatContextPickerItem, IChatContextPickService } from '../attachments/chatContextPickService.js';
10
import { IChatContextItem, IChatExplicitContextProvider, IChatResourceContextProvider, IChatWorkspaceContextProvider } from '../../common/contextContrib/chatContext.js';
11
import { CancellationToken } from '../../../../../base/common/cancellation.js';
12
import { IChatRequestWorkspaceVariableEntry, IGenericChatRequestVariableEntry, StringChatContextValue } from '../../common/attachments/chatVariableEntries.js';
13
import { IExtensionService } from '../../../../services/extensions/common/extensions.js';
14
import { InstantiationType, registerSingleton } from '../../../../../platform/instantiation/common/extensions.js';
15
import { Disposable, DisposableMap, IDisposable } from '../../../../../base/common/lifecycle.js';
16
import { URI } from '../../../../../base/common/uri.js';
17
import { basename } from '../../../../../base/common/resources.js';
18
19
export const IChatContextService = createDecorator<IChatContextService>('chatContextService');
20
21
export interface IChatContextService extends ChatContextService { }
22
23
interface IChatContextProviderEntry {
24
picker?: { title: string; icon: ThemeIcon };
25
workspaceProvider?: IChatWorkspaceContextProvider;
26
explicitProvider?: IChatExplicitContextProvider;
27
resourceProvider?: {
28
selector: LanguageSelector;
29
provider: IChatResourceContextProvider;
30
};
31
}
32
33
export class ChatContextService extends Disposable {
34
_serviceBrand: undefined;
35
36
private readonly _providers = new Map<string, IChatContextProviderEntry>();
37
private readonly _workspaceContext = new Map<string, IChatContextItem[]>();
38
private readonly _registeredPickers = this._register(new DisposableMap<string, IDisposable>());
39
private _lastResourceContext: Map<StringChatContextValue, { originalItem: IChatContextItem; provider: IChatResourceContextProvider }> = new Map();
40
private _executeCommandCallback: ((itemHandle: number) => Promise<void>) | undefined;
41
42
constructor(
43
@IChatContextPickService private readonly _contextPickService: IChatContextPickService,
44
@IExtensionService private readonly _extensionService: IExtensionService
45
) {
46
super();
47
}
48
49
setExecuteCommandCallback(callback: (itemHandle: number) => Promise<void>): void {
50
this._executeCommandCallback = callback;
51
}
52
53
async executeChatContextItemCommand(handle: number): Promise<void> {
54
if (!this._executeCommandCallback) {
55
return;
56
}
57
await this._executeCommandCallback(handle);
58
}
59
60
setChatContextProvider(id: string, picker: { title: string; icon: ThemeIcon }): void {
61
const providerEntry = this._providers.get(id) ?? {};
62
providerEntry.picker = picker;
63
this._providers.set(id, providerEntry);
64
this._registerWithPickService(id);
65
}
66
67
private _registerWithPickService(id: string): void {
68
const providerEntry = this._providers.get(id);
69
if (!providerEntry || !providerEntry.picker || !providerEntry.explicitProvider) {
70
return;
71
}
72
const title = `${providerEntry.picker.title.replace(/\.+$/, '')}...`;
73
this._registeredPickers.set(id, this._contextPickService.registerChatContextItem(this._asPicker(title, providerEntry.picker.icon, id)));
74
}
75
76
registerChatWorkspaceContextProvider(id: string, provider: IChatWorkspaceContextProvider): void {
77
const providerEntry = this._providers.get(id) ?? {};
78
providerEntry.workspaceProvider = provider;
79
this._providers.set(id, providerEntry);
80
}
81
82
registerChatExplicitContextProvider(id: string, provider: IChatExplicitContextProvider): void {
83
const providerEntry = this._providers.get(id) ?? {};
84
providerEntry.explicitProvider = provider;
85
this._providers.set(id, providerEntry);
86
this._registerWithPickService(id);
87
}
88
89
registerChatResourceContextProvider(id: string, selector: LanguageSelector, provider: IChatResourceContextProvider): void {
90
const providerEntry = this._providers.get(id) ?? {};
91
providerEntry.resourceProvider = { selector, provider };
92
this._providers.set(id, providerEntry);
93
}
94
95
unregisterChatContextProvider(id: string): void {
96
this._providers.delete(id);
97
this._registeredPickers.deleteAndDispose(id);
98
}
99
100
updateWorkspaceContextItems(id: string, items: IChatContextItem[]): void {
101
this._workspaceContext.set(id, items);
102
}
103
104
getWorkspaceContextItems(): IChatRequestWorkspaceVariableEntry[] {
105
const items: IChatRequestWorkspaceVariableEntry[] = [];
106
for (const workspaceContexts of this._workspaceContext.values()) {
107
for (const item of workspaceContexts) {
108
if (!item.value) {
109
continue;
110
}
111
// Derive label from resourceUri if label is not set
112
const derivedLabel = item.label ?? (item.resourceUri ? basename(item.resourceUri) : 'Unknown');
113
items.push({
114
value: item.value,
115
name: derivedLabel,
116
modelDescription: item.modelDescription,
117
id: derivedLabel,
118
kind: 'workspace'
119
});
120
}
121
}
122
return items;
123
}
124
125
async contextForResource(uri: URI, language?: string): Promise<StringChatContextValue | undefined> {
126
return this._contextForResource(uri, false, language);
127
}
128
129
private async _contextForResource(uri: URI, withValue: boolean, language?: string): Promise<StringChatContextValue | undefined> {
130
const scoredProviders: Array<{ score: number; provider: IChatResourceContextProvider }> = [];
131
for (const providerEntry of this._providers.values()) {
132
if (!providerEntry.resourceProvider) {
133
continue;
134
}
135
const matchScore = score(providerEntry.resourceProvider.selector, uri, language ?? '', true, undefined, undefined);
136
scoredProviders.push({ score: matchScore, provider: providerEntry.resourceProvider.provider });
137
}
138
scoredProviders.sort((a, b) => b.score - a.score);
139
if (scoredProviders.length === 0 || scoredProviders[0].score <= 0) {
140
return;
141
}
142
const provider = scoredProviders[0].provider;
143
const context = (await provider.provideChatContext(uri, withValue, CancellationToken.None));
144
if (!context) {
145
return;
146
}
147
// Derive label from resourceUri if label is not set
148
const effectiveResourceUri = context.resourceUri ?? uri;
149
const derivedLabel = context.label ?? basename(effectiveResourceUri);
150
const contextValue: StringChatContextValue = {
151
value: undefined,
152
name: derivedLabel,
153
icon: context.icon,
154
uri: uri,
155
resourceUri: context.resourceUri,
156
modelDescription: context.modelDescription,
157
tooltip: context.tooltip,
158
commandId: context.command?.id,
159
handle: context.handle
160
};
161
this._lastResourceContext.clear();
162
this._lastResourceContext.set(contextValue, { originalItem: context, provider });
163
return contextValue;
164
}
165
166
async resolveChatContext(context: StringChatContextValue, language?: string): Promise<StringChatContextValue> {
167
if (context.value !== undefined) {
168
return context;
169
}
170
171
const item = this._lastResourceContext.get(context);
172
if (!item) {
173
const resolved = await this._contextForResource(context.uri, true, language);
174
context.value = resolved?.value;
175
context.modelDescription = resolved?.modelDescription;
176
context.tooltip = resolved?.tooltip;
177
return context;
178
} else {
179
const resolved = await item.provider.resolveChatContext(item.originalItem, CancellationToken.None);
180
if (resolved) {
181
context.value = resolved.value;
182
context.modelDescription = resolved.modelDescription;
183
context.tooltip = resolved.tooltip;
184
return context;
185
}
186
}
187
return context;
188
}
189
190
private _asPicker(title: string, icon: ThemeIcon, id: string): IChatContextPickerItem {
191
const asPicker = (): IChatContextPicker => {
192
let providerEntry = this._providers.get(id);
193
if (!providerEntry) {
194
throw new Error('No chat context provider registered');
195
}
196
197
const picks = async (): Promise<IChatContextItem[]> => {
198
if (providerEntry && !providerEntry.explicitProvider) {
199
// Activate the extension providing the chat context provider
200
await this._extensionService.activateByEvent(`onChatContextProvider:${id}`);
201
providerEntry = this._providers.get(id);
202
if (!providerEntry?.explicitProvider) {
203
return [];
204
}
205
}
206
const results = await providerEntry?.explicitProvider!.provideChatContext(CancellationToken.None);
207
return results || [];
208
};
209
210
return {
211
picks: picks().then(items => {
212
return items.map(item => {
213
// Derive label from resourceUri if label is not set
214
const derivedLabel = item.label ?? (item.resourceUri ? basename(item.resourceUri) : 'Unknown');
215
return {
216
label: derivedLabel,
217
iconClass: item.icon ? ThemeIcon.asClassName(item.icon) : undefined,
218
asAttachment: async (): Promise<IGenericChatRequestVariableEntry> => {
219
let contextValue = item;
220
if ((contextValue.value === undefined) && providerEntry?.explicitProvider) {
221
contextValue = await providerEntry.explicitProvider.resolveChatContext(item, CancellationToken.None);
222
}
223
// Derive label from resourceUri if label is not set
224
const resolvedLabel = contextValue.label ?? (contextValue.resourceUri ? basename(contextValue.resourceUri) : 'Unknown');
225
return {
226
kind: 'generic',
227
id: resolvedLabel,
228
name: resolvedLabel,
229
icon: contextValue.icon,
230
value: contextValue.value,
231
};
232
}
233
};
234
});
235
}),
236
placeholder: title
237
};
238
};
239
240
const picker: IChatContextPickerItem = {
241
asPicker,
242
type: 'pickerPick',
243
label: title,
244
icon
245
};
246
247
return picker;
248
}
249
}
250
251
registerSingleton(IChatContextService, ChatContextService, InstantiationType.Delayed);
252
253