Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/chat/browser/chatSelectedTools.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 { CancellationToken } from '../../../../base/common/cancellation.js';
7
import { Disposable } from '../../../../base/common/lifecycle.js';
8
import { derived, IObservable, observableFromEvent, ObservableMap } from '../../../../base/common/observable.js';
9
import { isObject } from '../../../../base/common/types.js';
10
import { URI } from '../../../../base/common/uri.js';
11
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
12
import { ObservableMemento, observableMemento } from '../../../../platform/observable/common/observableMemento.js';
13
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
14
import { UserSelectedTools } from '../common/chatAgents.js';
15
import { IChatMode } from '../common/chatModes.js';
16
import { ChatModeKind } from '../common/constants.js';
17
import { ILanguageModelToolsService, IToolAndToolSetEnablementMap, IToolData, ToolSet } from '../common/languageModelToolsService.js';
18
import { PromptFileRewriter } from './promptSyntax/promptFileRewriter.js';
19
20
21
type ToolEnablementStates = {
22
readonly toolSets: ReadonlyMap<string, boolean>;
23
readonly tools: ReadonlyMap<string, boolean>;
24
};
25
26
type StoredDataV2 = {
27
readonly version: 2;
28
readonly toolSetEntries: [string, boolean][];
29
readonly toolEntries: [string, boolean][];
30
};
31
32
type StoredDataV1 = {
33
readonly version: undefined;
34
readonly disabledToolSets?: string[];
35
readonly disabledTools?: string[];
36
};
37
38
namespace ToolEnablementStates {
39
export function fromMap(map: IToolAndToolSetEnablementMap): ToolEnablementStates {
40
const toolSets: Map<string, boolean> = new Map(), tools: Map<string, boolean> = new Map();
41
for (const [entry, enabled] of map.entries()) {
42
if (entry instanceof ToolSet) {
43
toolSets.set(entry.id, enabled);
44
} else {
45
tools.set(entry.id, enabled);
46
}
47
}
48
return { toolSets, tools };
49
}
50
51
function isStoredDataV1(data: StoredDataV1 | StoredDataV2 | undefined): data is StoredDataV1 {
52
return isObject(data) && data.version === undefined
53
&& (data.disabledTools === undefined || Array.isArray(data.disabledTools))
54
&& (data.disabledToolSets === undefined || Array.isArray(data.disabledToolSets));
55
}
56
57
function isStoredDataV2(data: StoredDataV1 | StoredDataV2 | undefined): data is StoredDataV2 {
58
return isObject(data) && data.version === 2 && Array.isArray(data.toolSetEntries) && Array.isArray(data.toolEntries);
59
}
60
61
export function fromStorage(storage: string): ToolEnablementStates {
62
try {
63
const parsed = JSON.parse(storage);
64
if (isStoredDataV2(parsed)) {
65
return { toolSets: new Map(parsed.toolSetEntries), tools: new Map(parsed.toolEntries) };
66
} else if (isStoredDataV1(parsed)) {
67
const toolSetEntries = parsed.disabledToolSets?.map(id => [id, false] as [string, boolean]);
68
const toolEntries = parsed.disabledTools?.map(id => [id, false] as [string, boolean]);
69
return { toolSets: new Map(toolSetEntries), tools: new Map(toolEntries) };
70
}
71
} catch {
72
// ignore
73
}
74
// invalid data
75
return { toolSets: new Map(), tools: new Map() };
76
}
77
78
export function toStorage(state: ToolEnablementStates): string {
79
const storageData: StoredDataV2 = {
80
version: 2,
81
toolSetEntries: Array.from(state.toolSets.entries()),
82
toolEntries: Array.from(state.tools.entries())
83
};
84
return JSON.stringify(storageData);
85
}
86
}
87
88
export enum ToolsScope {
89
Global,
90
Session,
91
Mode
92
}
93
94
export class ChatSelectedTools extends Disposable {
95
96
private readonly _globalState: ObservableMemento<ToolEnablementStates>;
97
98
private readonly _sessionStates = new ObservableMap<string, ToolEnablementStates | undefined>();
99
100
private readonly _allTools: IObservable<Readonly<IToolData>[]>;
101
102
constructor(
103
private readonly _mode: IObservable<IChatMode>,
104
@ILanguageModelToolsService private readonly _toolsService: ILanguageModelToolsService,
105
@IStorageService _storageService: IStorageService,
106
@IInstantiationService private readonly _instantiationService: IInstantiationService,
107
) {
108
super();
109
110
const globalStateMemento = observableMemento<ToolEnablementStates>({
111
key: 'chat/selectedTools',
112
defaultValue: { toolSets: new Map(), tools: new Map() },
113
fromStorage: ToolEnablementStates.fromStorage,
114
toStorage: ToolEnablementStates.toStorage
115
});
116
117
this._globalState = this._store.add(globalStateMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE, _storageService));
118
this._allTools = observableFromEvent(_toolsService.onDidChangeTools, () => Array.from(_toolsService.getTools()));
119
}
120
121
/**
122
* All tools and tool sets with their enabled state.
123
*/
124
public readonly entriesMap: IObservable<IToolAndToolSetEnablementMap> = derived(r => {
125
const map = new Map<IToolData | ToolSet, boolean>();
126
127
// look up the tools in the hierarchy: session > mode > global
128
const currentMode = this._mode.read(r);
129
let currentMap = this._sessionStates.observable.read(r).get(currentMode.id);
130
if (!currentMap && currentMode.kind === ChatModeKind.Agent) {
131
const modeTools = currentMode.customTools?.read(r);
132
if (modeTools) {
133
currentMap = ToolEnablementStates.fromMap(this._toolsService.toToolAndToolSetEnablementMap(modeTools));
134
}
135
}
136
if (!currentMap) {
137
currentMap = this._globalState.read(r);
138
}
139
for (const tool of this._allTools.read(r)) {
140
if (tool.canBeReferencedInPrompt) {
141
map.set(tool, currentMap.tools.get(tool.id) !== false); // if unknown, it's enabled
142
}
143
}
144
for (const toolSet of this._toolsService.toolSets.read(r)) {
145
const toolSetEnabled = currentMap.toolSets.get(toolSet.id) !== false; // if unknown, it's enabled
146
map.set(toolSet, toolSetEnabled);
147
for (const tool of toolSet.getTools(r)) {
148
map.set(tool, toolSetEnabled || currentMap.tools.get(tool.id) === true); // if unknown, use toolSetEnabled
149
}
150
}
151
return map;
152
});
153
154
public readonly userSelectedTools: IObservable<UserSelectedTools> = derived(r => {
155
// extract a map of tool ids
156
const result: UserSelectedTools = {};
157
const map = this.entriesMap.read(r);
158
for (const [item, enabled] of map) {
159
if (!(item instanceof ToolSet)) {
160
result[item.id] = enabled;
161
}
162
}
163
return result;
164
});
165
166
get entriesScope() {
167
const mode = this._mode.get();
168
if (this._sessionStates.has(mode.id)) {
169
return ToolsScope.Session;
170
}
171
if (mode.kind === ChatModeKind.Agent && mode.customTools?.get() && mode.uri) {
172
return ToolsScope.Mode;
173
}
174
return ToolsScope.Global;
175
}
176
177
get currentMode(): IChatMode {
178
return this._mode.get();
179
}
180
181
resetSessionEnablementState() {
182
const mode = this._mode.get();
183
this._sessionStates.delete(mode.id);
184
}
185
186
set(enablementMap: IToolAndToolSetEnablementMap, sessionOnly: boolean): void {
187
const mode = this._mode.get();
188
if (sessionOnly || this._sessionStates.has(mode.id)) {
189
this._sessionStates.set(mode.id, ToolEnablementStates.fromMap(enablementMap));
190
return;
191
}
192
if (mode.kind === ChatModeKind.Agent && mode.customTools?.get() && mode.uri) {
193
// apply directly to mode file.
194
this.updateCustomModeTools(mode.uri.get(), enablementMap);
195
return;
196
}
197
this._globalState.set(ToolEnablementStates.fromMap(enablementMap), undefined);
198
}
199
200
private async updateCustomModeTools(uri: URI, enablementMap: IToolAndToolSetEnablementMap): Promise<void> {
201
await this._instantiationService.createInstance(PromptFileRewriter).openAndRewriteTools(uri, enablementMap, CancellationToken.None);
202
}
203
}
204
205