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
4780 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, IChatContextProvider } 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
18
export const IChatContextService = createDecorator<IChatContextService>('chatContextService');
19
20
export interface IChatContextService extends ChatContextService { }
21
22
interface IChatContextProviderEntry {
23
picker?: { title: string; icon: ThemeIcon };
24
chatContextProvider?: {
25
selector: LanguageSelector | undefined;
26
provider: IChatContextProvider;
27
};
28
}
29
30
export class ChatContextService extends Disposable {
31
_serviceBrand: undefined;
32
33
private readonly _providers = new Map<string, IChatContextProviderEntry>();
34
private readonly _workspaceContext = new Map<string, IChatContextItem[]>();
35
private readonly _registeredPickers = this._register(new DisposableMap<string, IDisposable>());
36
private _lastResourceContext: Map<StringChatContextValue, { originalItem: IChatContextItem; provider: IChatContextProvider }> = new Map();
37
38
constructor(
39
@IChatContextPickService private readonly _contextPickService: IChatContextPickService,
40
@IExtensionService private readonly _extensionService: IExtensionService
41
) {
42
super();
43
}
44
45
setChatContextProvider(id: string, picker: { title: string; icon: ThemeIcon }): void {
46
const providerEntry = this._providers.get(id) ?? { picker: undefined };
47
providerEntry.picker = picker;
48
this._providers.set(id, providerEntry);
49
this._registerWithPickService(id);
50
}
51
52
private _registerWithPickService(id: string): void {
53
const providerEntry = this._providers.get(id);
54
if (!providerEntry || !providerEntry.picker || !providerEntry.chatContextProvider) {
55
return;
56
}
57
const title = `${providerEntry.picker.title.replace(/\.+$/, '')}...`;
58
this._registeredPickers.set(id, this._contextPickService.registerChatContextItem(this._asPicker(title, providerEntry.picker.icon, id)));
59
}
60
61
registerChatContextProvider(id: string, selector: LanguageSelector | undefined, provider: IChatContextProvider): void {
62
const providerEntry = this._providers.get(id) ?? { picker: undefined };
63
providerEntry.chatContextProvider = { selector, provider };
64
this._providers.set(id, providerEntry);
65
this._registerWithPickService(id);
66
}
67
68
unregisterChatContextProvider(id: string): void {
69
this._providers.delete(id);
70
this._registeredPickers.deleteAndDispose(id);
71
}
72
73
updateWorkspaceContextItems(id: string, items: IChatContextItem[]): void {
74
this._workspaceContext.set(id, items);
75
}
76
77
getWorkspaceContextItems(): IChatRequestWorkspaceVariableEntry[] {
78
const items: IChatRequestWorkspaceVariableEntry[] = [];
79
for (const workspaceContexts of this._workspaceContext.values()) {
80
for (const item of workspaceContexts) {
81
if (!item.value) {
82
continue;
83
}
84
items.push({
85
value: item.value,
86
name: item.label,
87
modelDescription: item.modelDescription,
88
id: item.label,
89
kind: 'workspace'
90
});
91
}
92
}
93
return items;
94
}
95
96
async contextForResource(uri: URI): Promise<StringChatContextValue | undefined> {
97
return this._contextForResource(uri, false);
98
}
99
100
private async _contextForResource(uri: URI, withValue: boolean): Promise<StringChatContextValue | undefined> {
101
const scoredProviders: Array<{ score: number; provider: IChatContextProvider }> = [];
102
for (const providerEntry of this._providers.values()) {
103
if (!providerEntry.chatContextProvider?.provider.provideChatContextForResource || (providerEntry.chatContextProvider.selector === undefined)) {
104
continue;
105
}
106
const matchScore = score(providerEntry.chatContextProvider.selector, uri, '', true, undefined, undefined);
107
scoredProviders.push({ score: matchScore, provider: providerEntry.chatContextProvider.provider });
108
}
109
scoredProviders.sort((a, b) => b.score - a.score);
110
if (scoredProviders.length === 0 || scoredProviders[0].score <= 0) {
111
return;
112
}
113
const context = (await scoredProviders[0].provider.provideChatContextForResource!(uri, withValue, CancellationToken.None));
114
if (!context) {
115
return;
116
}
117
const contextValue: StringChatContextValue = {
118
value: undefined,
119
name: context.label,
120
icon: context.icon,
121
uri: uri,
122
modelDescription: context.modelDescription
123
};
124
this._lastResourceContext.clear();
125
this._lastResourceContext.set(contextValue, { originalItem: context, provider: scoredProviders[0].provider });
126
return contextValue;
127
}
128
129
async resolveChatContext(context: StringChatContextValue): Promise<StringChatContextValue> {
130
if (context.value !== undefined) {
131
return context;
132
}
133
134
const item = this._lastResourceContext.get(context);
135
if (!item) {
136
const resolved = await this._contextForResource(context.uri, true);
137
context.value = resolved?.value;
138
context.modelDescription = resolved?.modelDescription;
139
return context;
140
} else if (item.provider.resolveChatContext) {
141
const resolved = await item.provider.resolveChatContext(item.originalItem, CancellationToken.None);
142
if (resolved) {
143
context.value = resolved.value;
144
context.modelDescription = resolved.modelDescription;
145
return context;
146
}
147
}
148
return context;
149
}
150
151
private _asPicker(title: string, icon: ThemeIcon, id: string): IChatContextPickerItem {
152
const asPicker = (): IChatContextPicker => {
153
let providerEntry = this._providers.get(id);
154
if (!providerEntry) {
155
throw new Error('No chat context provider registered');
156
}
157
158
const picks = async (): Promise<IChatContextItem[]> => {
159
if (providerEntry && !providerEntry.chatContextProvider) {
160
// Activate the extension providing the chat context provider
161
await this._extensionService.activateByEvent(`onChatContextProvider:${id}`);
162
providerEntry = this._providers.get(id);
163
if (!providerEntry?.chatContextProvider) {
164
return [];
165
}
166
}
167
const results = await providerEntry?.chatContextProvider!.provider.provideChatContext({}, CancellationToken.None);
168
return results || [];
169
};
170
171
return {
172
picks: picks().then(items => {
173
return items.map(item => ({
174
label: item.label,
175
iconClass: ThemeIcon.asClassName(item.icon),
176
asAttachment: async (): Promise<IGenericChatRequestVariableEntry> => {
177
let contextValue = item;
178
if ((contextValue.value === undefined) && providerEntry?.chatContextProvider?.provider!.resolveChatContext) {
179
contextValue = await providerEntry.chatContextProvider.provider.resolveChatContext(item, CancellationToken.None);
180
}
181
return {
182
kind: 'generic',
183
id: contextValue.label,
184
name: contextValue.label,
185
icon: contextValue.icon,
186
value: contextValue.value
187
};
188
}
189
}));
190
}),
191
placeholder: title
192
};
193
};
194
195
const picker: IChatContextPickerItem = {
196
asPicker,
197
type: 'pickerPick',
198
label: title,
199
icon
200
};
201
202
return picker;
203
}
204
}
205
206
registerSingleton(IChatContextService, ChatContextService, InstantiationType.Delayed);
207
208