Skip to content

Commit 79c7047

Browse files
authored
Enhance session management for selected tools in chat (microsoft#249878)
* have `ChatSelectedTools#disable|enable` and also consider tool sets for prompts * * chat selected tools should have session enablement state * use session enablement state for prompts
1 parent ed52e54 commit 79c7047

File tree

5 files changed

+83
-45
lines changed

5 files changed

+83
-45
lines changed

src/vs/workbench/contrib/chat/browser/actions/chatToolActions.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ class ConfigureToolsAction extends Action2 {
122122
}
123123
}
124124
}
125-
widget.input.selectedToolsModel.update(disableToolSets, disableTools);
125+
widget.input.selectedToolsModel.disable(disableToolSets, disableTools, false);
126126
});
127127

128128
telemetryService.publicLog2<SelectedToolData, SelectedToolClassification>('chat/selectedTools', {
@@ -132,8 +132,6 @@ class ConfigureToolsAction extends Action2 {
132132
}
133133
}
134134

135-
136-
137135
export function registerChatToolActions() {
138136
registerAction2(AcceptToolConfirmation);
139137
registerAction2(ConfigureToolsAction);

src/vs/workbench/contrib/chat/browser/actions/chatToolPicker.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,19 @@ export async function showToolsPicker(
290290
}
291291
}
292292

293-
onUpdate?.(result);
293+
if (onUpdate) {
294+
let didChange = toolsEntries.size !== result.size;
295+
for (const [key, value] of toolsEntries) {
296+
if (didChange) {
297+
break;
298+
}
299+
didChange = result.get(key) !== value;
300+
}
301+
302+
if (didChange) {
303+
onUpdate(result);
304+
}
305+
}
294306

295307
} finally {
296308
ignoreEvent = false;

src/vs/workbench/contrib/chat/browser/chatInputPart.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
659659
const attachments = state.inputState?.chatContextAttachments ?? [];
660660
this._attachmentModel.clearAndSetContext(...attachments);
661661

662+
this.selectedToolsModel.resetSessionEnablementState();
663+
662664
if (state.inputValue) {
663665
this.setValue(state.inputValue, false);
664666
}

src/vs/workbench/contrib/chat/browser/chatSelectedTools.ts

Lines changed: 50 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { Disposable } from '../../../../base/common/lifecycle.js';
7-
import { autorun, IObservable, observableFromEvent, ObservableMap, transaction } from '../../../../base/common/observable.js';
7+
import { autorun, derived, IObservable, observableFromEvent, ObservableMap, observableValue, transaction } from '../../../../base/common/observable.js';
88
import { ObservableMemento, observableMemento } from '../../../../platform/observable/common/observableMemento.js';
99
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
1010
import { ChatMode } from '../common/constants.js';
1111
import { ILanguageModelToolsService, IToolData, ToolSet, ToolDataSource } from '../common/languageModelToolsService.js';
1212

13+
1314
/**
1415
* New tools and new tool sources that come in should generally be enabled until
1516
* the user disables them. To store things, we store only the tool sets and
@@ -18,8 +19,8 @@ import { ILanguageModelToolsService, IToolData, ToolSet, ToolDataSource } from '
1819
* also enabled.
1920
*/
2021
type StoredData = {
21-
disabledToolSets?: readonly string[];
22-
disabledTools?: readonly string[];
22+
readonly disabledToolSets?: readonly string[];
23+
readonly disabledTools?: readonly string[];
2324
};
2425

2526
const storedTools = observableMemento<StoredData>({
@@ -31,6 +32,8 @@ export class ChatSelectedTools extends Disposable {
3132

3233
private readonly _selectedTools: ObservableMemento<StoredData>;
3334

35+
private readonly _sessionSelectedTools = observableValue<StoredData>(this, {});
36+
3437
private readonly _allTools: IObservable<Readonly<IToolData>[]>;
3538

3639
/**
@@ -53,22 +56,39 @@ export class ChatSelectedTools extends Disposable {
5356

5457
constructor(
5558
mode: IObservable<ChatMode>,
56-
@ILanguageModelToolsService toolsService: ILanguageModelToolsService,
59+
@ILanguageModelToolsService private readonly _toolsService: ILanguageModelToolsService,
5760
@IStorageService storageService: IStorageService,
5861
) {
5962
super();
6063

6164
this._selectedTools = this._store.add(storedTools(StorageScope.WORKSPACE, StorageTarget.MACHINE, storageService));
6265

63-
this._allTools = observableFromEvent(toolsService.onDidChangeTools, () => Array.from(toolsService.getTools()));
66+
this._allTools = observableFromEvent(_toolsService.onDidChangeTools, () => Array.from(_toolsService.getTools()));
67+
68+
const disabledDataObs = derived(r => {
69+
const globalData = this._selectedTools.read(r);
70+
const sessionData = this._sessionSelectedTools.read(r);
71+
72+
const toolSetIds = new Set<string>();
73+
const toolIds = new Set<string>();
6474

65-
const disabledDataObs = this._selectedTools.map(data => {
66-
return (data.disabledToolSets?.length || data.disabledTools?.length)
67-
? {
68-
toolSetIds: new Set(data.disabledToolSets),
69-
toolIds: new Set(data.disabledTools),
75+
for (const data of [globalData, sessionData]) {
76+
if (data.disabledToolSets) {
77+
for (const id of data.disabledToolSets) {
78+
toolSetIds.add(id);
79+
}
7080
}
71-
: undefined;
81+
if (data.disabledTools) {
82+
for (const id of data.disabledTools) {
83+
toolIds.add(id);
84+
}
85+
}
86+
}
87+
88+
if (toolSetIds.size === 0 && toolIds.size === 0) {
89+
return undefined;
90+
}
91+
return { toolSetIds, toolIds };
7292
});
7393

7494
this._store.add(autorun(r => {
@@ -82,12 +102,12 @@ export class ChatSelectedTools extends Disposable {
82102
sourceByTool.set(tool, tool.source);
83103
}
84104

85-
const toolSets = toolsService.toolSets.read(r);
105+
const toolSets = _toolsService.toolSets.read(r);
86106

87107
for (const toolSet of toolSets) {
88108

89109
if (!toolSet.isHomogenous.read(r)) {
90-
// only homogenous tool sets can shallow tools
110+
// only homogenous tool sets can swallow tools
91111
continue;
92112
}
93113

@@ -126,16 +146,27 @@ export class ChatSelectedTools extends Disposable {
126146
}));
127147
}
128148

129-
selectOnly(toolIds: readonly string[]): void {
130-
const uniqueTools = new Set(toolIds);
149+
resetSessionEnablementState() {
150+
this._sessionSelectedTools.set({}, undefined);
151+
}
131152

132-
const disabledTools = this._allTools.get().filter(tool => !uniqueTools.has(tool.id));
153+
enable(toolSets: readonly ToolSet[], tools: readonly IToolData[], sessionOnly: boolean): void {
154+
const toolIds = new Set(tools.map(t => t.id));
155+
const toolsetIds = new Set(toolSets.map(t => t.id));
133156

134-
this.update([], disabledTools);
157+
const disabledTools = this._allTools.get().filter(tool => !toolIds.has(tool.id));
158+
const disabledToolSets = Array.from(this._toolsService.toolSets.get()).filter(toolset => !toolsetIds.has(toolset.id));
159+
160+
this.disable(disabledToolSets, disabledTools, sessionOnly);
135161
}
136162

137-
update(disabledToolSets: readonly ToolSet[], disableTools: readonly IToolData[]): void {
138-
this._selectedTools.set({
163+
disable(disabledToolSets: readonly ToolSet[], disableTools: readonly IToolData[], sessionOnly: boolean): void {
164+
165+
const target = sessionOnly
166+
? this._sessionSelectedTools
167+
: this._selectedTools;
168+
169+
target.set({
139170
disabledToolSets: disabledToolSets.map(t => t.id),
140171
disabledTools: disableTools.map(t => t.id)
141172
}, undefined);

src/vs/workbench/contrib/chat/browser/chatWidget.ts

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ import { ChatViewModel, IChatResponseViewModel, isRequestVM, isResponseVM } from
5151
import { IChatInputState } from '../common/chatWidgetHistoryService.js';
5252
import { CodeBlockModelCollection } from '../common/codeBlockModelCollection.js';
5353
import { ChatAgentLocation, ChatMode } from '../common/constants.js';
54-
import { ILanguageModelToolsService, ToolSet } from '../common/languageModelToolsService.js';
54+
import { ILanguageModelToolsService, IToolData, ToolSet } from '../common/languageModelToolsService.js';
5555
import { IPromptMetadata } from '../common/promptSyntax/parsers/types.js';
5656
import { IMetadata, IPromptsService } from '../common/promptSyntax/service/types.js';
5757
import { handleModeSwitch } from './actions/chatActions.js';
@@ -1568,27 +1568,22 @@ export class ChatWidget extends Disposable implements IChatWidget {
15681568
// if we have some tools present, the mode must have been equal to `agent`
15691569
assert(this.inputPart.currentMode === ChatMode.Agent, `Chat mode must be 'agent' when there are 'tools' defined, got ${this.inputPart.currentMode}.`);
15701570

1571-
1572-
// convert tools names to tool IDs
1573-
const toolIds = tools
1574-
.map((toolName) => {
1575-
const tool = this.toolsService.getToolByName(toolName);
1576-
1577-
if (tool === undefined) {
1578-
this.logService.warn(
1579-
`[setup tools]: cannot to find tool '${toolName}'`,
1580-
);
1581-
}
1582-
1583-
return tool;
1584-
})
1585-
.filter(isDefined)
1586-
.map(tool => tool.id);
1587-
1588-
// if there are some tools defined in the prompt files, select only the specified tools
1589-
this.inputPart
1590-
.selectedToolsModel
1591-
.selectOnly(toolIds);
1571+
// convert names to tools or tool sets
1572+
const enabledTools: IToolData[] = [];
1573+
const enabledToolSets: ToolSet[] = [];
1574+
for (const name of tools) {
1575+
const tool = this.toolsService.getToolByName(name);
1576+
if (tool) {
1577+
enabledTools.push(tool);
1578+
continue;
1579+
}
1580+
const toolset = this.toolsService.getToolSetByName(name);
1581+
if (toolset) {
1582+
enabledToolSets.push(toolset);
1583+
continue;
1584+
}
1585+
}
1586+
this.inputPart.selectedToolsModel.enable(enabledToolSets, enabledTools, true);
15921587
}
15931588

15941589
/**

0 commit comments

Comments
 (0)