Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionApprovalModel.ts
13406 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 { renderAsPlaintext } from '../../../../../base/browser/markdownRenderer.js';
7
import { Disposable, DisposableResourceMap, IDisposable } from '../../../../../base/common/lifecycle.js';
8
import { autorun, autorunIterableDelta, IObservable, ISettableObservable, observableValue } from '../../../../../base/common/observable.js';
9
import { URI } from '../../../../../base/common/uri.js';
10
import { migrateLegacyTerminalToolSpecificData } from '../../common/chat.js';
11
import { IChatModel } from '../../common/model/chatModel.js';
12
import { IChatService, IChatToolInvocation, ToolConfirmKind } from '../../common/chatService/chatService.js';
13
import { ILanguageService } from '../../../../../editor/common/languages/language.js';
14
15
export interface IAgentSessionApprovalInfo {
16
readonly label: string;
17
readonly languageId: string | undefined;
18
readonly since: Date;
19
confirm(): void;
20
}
21
22
/**
23
* Tracks approval state for all live chat sessions. For each session,
24
* exposes an observable that emits {@link IAgentSessionApprovalInfo}
25
* when a tool invocation is waiting for user confirmation, or `undefined`
26
* when no approval is needed.
27
*/
28
export class AgentSessionApprovalModel extends Disposable {
29
30
private readonly _approvals = new Map<string, ISettableObservable<IAgentSessionApprovalInfo | undefined>>();
31
private readonly _modelTrackers = this._register(new DisposableResourceMap());
32
33
constructor(
34
@IChatService private readonly _chatService: IChatService,
35
@ILanguageService private readonly _languageService: ILanguageService,
36
) {
37
super();
38
39
this._register(autorunIterableDelta(
40
reader => this._chatService.chatModels.read(reader),
41
({ addedValues, removedValues }) => {
42
for (const model of addedValues) {
43
this._modelTrackers.set(model.sessionResource, this._trackModel(model));
44
}
45
for (const model of removedValues) {
46
this._modelTrackers.deleteAndDispose(model.sessionResource);
47
this._approvals.get(model.sessionResource.toString())?.set(undefined, undefined);
48
}
49
}
50
));
51
}
52
53
getApproval(sessionResource: URI): IObservable<IAgentSessionApprovalInfo | undefined> {
54
return this._getOrCreateApproval(sessionResource.toString());
55
}
56
57
private _getOrCreateApproval(key: string): ISettableObservable<IAgentSessionApprovalInfo | undefined> {
58
let obs = this._approvals.get(key);
59
if (!obs) {
60
obs = observableValue<IAgentSessionApprovalInfo | undefined>(`sessionApproval.${key}`, undefined);
61
this._approvals.set(key, obs);
62
}
63
return obs;
64
}
65
66
private _trackModel(model: IChatModel): IDisposable {
67
const settable = this._getOrCreateApproval(model.sessionResource.toString());
68
69
const setIfChanged = (value: IAgentSessionApprovalInfo | undefined) => {
70
const current = settable.get();
71
if (current === value) {
72
return;
73
}
74
if (current !== undefined && value !== undefined && current.label === value.label && current.languageId === value.languageId) {
75
return;
76
}
77
settable.set(value, undefined);
78
};
79
80
return autorun(reader => {
81
const needsInput = model.requestNeedsInput.read(reader);
82
if (!needsInput) {
83
setIfChanged(undefined);
84
return;
85
}
86
87
const lastResponse = model.lastRequest?.response;
88
if (!lastResponse?.response?.value) {
89
setIfChanged(undefined);
90
return;
91
}
92
93
for (const part of lastResponse.response.value) {
94
if (part.kind !== 'toolInvocation' || part.toolSpecificData?.kind === 'modifiedFilesConfirmation') {
95
continue; // unsupported
96
}
97
const state = part.state.read(reader);
98
if (state.type === IChatToolInvocation.StateKind.WaitingForConfirmation || state.type === IChatToolInvocation.StateKind.WaitingForPostApproval) {
99
let label: string;
100
let languageId: string | undefined;
101
if (part.toolSpecificData?.kind === 'terminal') {
102
const terminalData = migrateLegacyTerminalToolSpecificData(part.toolSpecificData);
103
label = terminalData.presentationOverrides?.commandLine ?? terminalData.commandLine.forDisplay ?? terminalData.commandLine.userEdited ?? terminalData.commandLine.toolEdited ?? terminalData.commandLine.original;
104
languageId = this._languageService.getLanguageIdByLanguageName(terminalData.presentationOverrides?.language ?? terminalData.language) ?? undefined;
105
} else if (needsInput.detail) {
106
label = needsInput.detail;
107
} else {
108
const msg = part.invocationMessage;
109
label = typeof msg === 'string' ? msg : renderAsPlaintext(msg);
110
}
111
112
const confirmState = state;
113
setIfChanged({
114
label,
115
languageId,
116
since: new Date(),
117
confirm: () => confirmState.confirm({ type: ToolConfirmKind.UserAction }),
118
});
119
return;
120
}
121
}
122
123
setIfChanged(undefined);
124
});
125
}
126
}
127
128