@theia/ai-chat 1.62.1 → 1.63.0-next.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/browser/ai-chat-frontend-module.d.ts.map +1 -1
- package/lib/browser/ai-chat-frontend-module.js +7 -1
- package/lib/browser/ai-chat-frontend-module.js.map +1 -1
- package/lib/browser/change-set-file-element.d.ts +9 -6
- package/lib/browser/change-set-file-element.d.ts.map +1 -1
- package/lib/browser/change-set-file-element.js +27 -14
- package/lib/browser/change-set-file-element.js.map +1 -1
- package/lib/browser/change-set-variable.js +1 -2
- package/lib/browser/change-set-variable.js.map +1 -1
- package/lib/browser/chat-tool-preferences.d.ts +54 -0
- package/lib/browser/chat-tool-preferences.d.ts.map +1 -0
- package/lib/browser/chat-tool-preferences.js +170 -0
- package/lib/browser/chat-tool-preferences.js.map +1 -0
- package/lib/browser/chat-tool-request-service.d.ts +20 -0
- package/lib/browser/chat-tool-request-service.d.ts.map +1 -0
- package/lib/browser/chat-tool-request-service.js +89 -0
- package/lib/browser/chat-tool-request-service.js.map +1 -0
- package/lib/browser/frontend-chat-service.d.ts.map +1 -1
- package/lib/browser/frontend-chat-service.js +2 -6
- package/lib/browser/frontend-chat-service.js.map +1 -1
- package/lib/common/change-set.d.ts +78 -0
- package/lib/common/change-set.d.ts.map +1 -0
- package/lib/common/change-set.js +133 -0
- package/lib/common/change-set.js.map +1 -0
- package/lib/common/chat-agents.d.ts.map +1 -1
- package/lib/common/chat-agents.js +4 -1
- package/lib/common/chat-agents.js.map +1 -1
- package/lib/common/chat-model.d.ts +68 -80
- package/lib/common/chat-model.d.ts.map +1 -1
- package/lib/common/chat-model.js +224 -136
- package/lib/common/chat-model.js.map +1 -1
- package/lib/common/chat-service.d.ts +3 -3
- package/lib/common/chat-service.d.ts.map +1 -1
- package/lib/common/chat-service.js +5 -8
- package/lib/common/chat-service.js.map +1 -1
- package/package.json +10 -10
- package/src/browser/ai-chat-frontend-module.ts +8 -1
- package/src/browser/change-set-file-element.ts +33 -21
- package/src/browser/change-set-variable.ts +1 -1
- package/src/browser/chat-tool-preferences.ts +178 -0
- package/src/browser/chat-tool-request-service.ts +93 -0
- package/src/browser/frontend-chat-service.ts +3 -6
- package/src/common/change-set.ts +197 -0
- package/src/common/chat-agents.ts +15 -11
- package/src/common/chat-model.ts +249 -207
- package/src/common/chat-service.ts +6 -9
package/src/common/chat-model.ts
CHANGED
|
@@ -20,11 +20,14 @@
|
|
|
20
20
|
// Partially copied from https://github.com/microsoft/vscode/blob/a2cab7255c0df424027be05d58e1b7b941f4ea60/src/vs/workbench/contrib/chat/common/chatModel.ts
|
|
21
21
|
|
|
22
22
|
import { AIVariableResolutionRequest, LanguageModelMessage, ResolvedAIContextVariable, TextMessage, ThinkingMessage, ToolResultMessage, ToolUseMessage } from '@theia/ai-core';
|
|
23
|
-
import { CancellationToken, CancellationTokenSource, Command, Disposable, DisposableCollection, Emitter, Event, generateUuid, URI } from '@theia/core';
|
|
23
|
+
import { ArrayUtils, CancellationToken, CancellationTokenSource, Command, Disposable, DisposableCollection, Emitter, Event, generateUuid, URI } from '@theia/core';
|
|
24
24
|
import { MarkdownString, MarkdownStringImpl } from '@theia/core/lib/common/markdown-rendering';
|
|
25
25
|
import { Position } from '@theia/core/shared/vscode-languageserver-protocol';
|
|
26
26
|
import { ChatAgentLocation } from './chat-agents';
|
|
27
27
|
import { ParsedChatRequest } from './parsed-chat-request';
|
|
28
|
+
import { ChangeSet, ChangeSetImpl, ChangeSetElement, ChatUpdateChangeSetEvent } from './change-set';
|
|
29
|
+
import debounce = require('@theia/core/shared/lodash.debounce');
|
|
30
|
+
export { ChangeSet, ChangeSetImpl, ChangeSetElement };
|
|
28
31
|
|
|
29
32
|
/**********************
|
|
30
33
|
* INTERFACES AND TYPE GUARDS
|
|
@@ -37,10 +40,8 @@ export type ChatChangeEvent =
|
|
|
37
40
|
| ChatRemoveVariableEvent
|
|
38
41
|
| ChatSetVariablesEvent
|
|
39
42
|
| ChatRemoveRequestEvent
|
|
40
|
-
| ChatSetChangeSetEvent
|
|
41
43
|
| ChatSuggestionsChangedEvent
|
|
42
44
|
| ChatUpdateChangeSetEvent
|
|
43
|
-
| ChatRemoveChangeSetEvent
|
|
44
45
|
| ChatEditRequestEvent
|
|
45
46
|
| ChatEditCancelEvent
|
|
46
47
|
| ChatEditSubmitEvent
|
|
@@ -80,22 +81,6 @@ export interface ChatAddResponseEvent {
|
|
|
80
81
|
response: ChatResponseModel;
|
|
81
82
|
}
|
|
82
83
|
|
|
83
|
-
export interface ChatSetChangeSetEvent {
|
|
84
|
-
kind: 'setChangeSet';
|
|
85
|
-
changeSet: ChangeSet;
|
|
86
|
-
oldChangeSet?: ChangeSet;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export interface ChatUpdateChangeSetEvent {
|
|
90
|
-
kind: 'updateChangeSet';
|
|
91
|
-
changeSet: ChangeSet;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export interface ChatRemoveChangeSetEvent {
|
|
95
|
-
kind: 'removeChangeSet';
|
|
96
|
-
changeSet: ChangeSet;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
84
|
export interface ChatAddVariableEvent {
|
|
100
85
|
kind: 'addVariable';
|
|
101
86
|
}
|
|
@@ -114,8 +99,8 @@ export interface ChatSuggestionsChangedEvent {
|
|
|
114
99
|
}
|
|
115
100
|
|
|
116
101
|
export namespace ChatChangeEvent {
|
|
117
|
-
export function isChangeSetEvent(event: ChatChangeEvent): event is
|
|
118
|
-
return event.kind === '
|
|
102
|
+
export function isChangeSetEvent(event: ChatChangeEvent): event is ChatUpdateChangeSetEvent {
|
|
103
|
+
return event.kind === 'updateChangeSet';
|
|
119
104
|
}
|
|
120
105
|
}
|
|
121
106
|
|
|
@@ -136,7 +121,7 @@ export interface ChatRemoveRequestEvent {
|
|
|
136
121
|
* - Within each branch, the requests are stored in a list. Those requests are the alternatives to the original request.
|
|
137
122
|
* Each of those items can have a next branch, which is the next request in the hierarchy.
|
|
138
123
|
*/
|
|
139
|
-
export interface ChatRequestHierarchy<TRequest extends ChatRequestModel = ChatRequestModel> {
|
|
124
|
+
export interface ChatRequestHierarchy<TRequest extends ChatRequestModel = ChatRequestModel> extends Disposable {
|
|
140
125
|
readonly branch: ChatHierarchyBranch<TRequest>
|
|
141
126
|
|
|
142
127
|
onDidChange: Event<ChangeActiveBranchEvent<TRequest>>;
|
|
@@ -160,7 +145,7 @@ export interface ChangeActiveBranchEvent<TRequest extends ChatRequestModel = Cha
|
|
|
160
145
|
* It contains a list of items, each representing a request.
|
|
161
146
|
* Those items can have a next branch, which is the next request in the hierarchy.
|
|
162
147
|
*/
|
|
163
|
-
export interface ChatHierarchyBranch<TRequest extends ChatRequestModel = ChatRequestModel> {
|
|
148
|
+
export interface ChatHierarchyBranch<TRequest extends ChatRequestModel = ChatRequestModel> extends Disposable {
|
|
164
149
|
readonly id: string;
|
|
165
150
|
readonly hierarchy: ChatRequestHierarchy<TRequest>;
|
|
166
151
|
readonly previous?: ChatHierarchyBranch<TRequest>;
|
|
@@ -192,28 +177,15 @@ export interface ChatModel {
|
|
|
192
177
|
readonly onDidChange: Event<ChatChangeEvent>;
|
|
193
178
|
readonly id: string;
|
|
194
179
|
readonly location: ChatAgentLocation;
|
|
195
|
-
readonly changeSet?: ChangeSet;
|
|
196
180
|
readonly context: ChatContextManager;
|
|
197
181
|
readonly suggestions: readonly ChatSuggestion[];
|
|
198
182
|
readonly settings?: { [key: string]: unknown };
|
|
183
|
+
readonly changeSet: ChangeSet;
|
|
199
184
|
getRequests(): ChatRequestModel[];
|
|
200
185
|
getBranches(): ChatHierarchyBranch<ChatRequestModel>[];
|
|
201
186
|
isEmpty(): boolean;
|
|
202
187
|
}
|
|
203
188
|
|
|
204
|
-
export interface ChangeSet extends Disposable {
|
|
205
|
-
onDidChange: Event<ChangeSetChangeEvent>;
|
|
206
|
-
readonly title: string;
|
|
207
|
-
getElements(): ChangeSetElement[];
|
|
208
|
-
/**
|
|
209
|
-
* Find an element by URI.
|
|
210
|
-
* @param uri The URI to look for.
|
|
211
|
-
* @returns The element with the given URI, or undefined if not found.
|
|
212
|
-
*/
|
|
213
|
-
getElementByURI(uri: URI): ChangeSetElement | undefined;
|
|
214
|
-
dispose(): void;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
189
|
export interface ChatSuggestionCallback {
|
|
218
190
|
kind: 'callback',
|
|
219
191
|
callback: () => unknown;
|
|
@@ -240,25 +212,6 @@ export interface ChatContextManager {
|
|
|
240
212
|
clear(): void;
|
|
241
213
|
}
|
|
242
214
|
|
|
243
|
-
export interface ChangeSetElement {
|
|
244
|
-
readonly uri: URI;
|
|
245
|
-
|
|
246
|
-
onDidChange?: Event<void>
|
|
247
|
-
readonly name?: string;
|
|
248
|
-
readonly icon?: string;
|
|
249
|
-
readonly additionalInfo?: string;
|
|
250
|
-
|
|
251
|
-
readonly state?: 'pending' | 'applied' | 'stale';
|
|
252
|
-
readonly type?: 'add' | 'modify' | 'delete';
|
|
253
|
-
readonly data?: { [key: string]: unknown };
|
|
254
|
-
|
|
255
|
-
open?(): Promise<void>;
|
|
256
|
-
openChange?(): Promise<void>;
|
|
257
|
-
apply?(): Promise<void>;
|
|
258
|
-
revert?(): Promise<void>;
|
|
259
|
-
dispose?(): void;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
215
|
export interface ChangeSetDecoration {
|
|
263
216
|
readonly priority?: number;
|
|
264
217
|
readonly additionalInfoSuffixIcon?: string[];
|
|
@@ -429,6 +382,9 @@ export interface ToolCallChatResponseContent extends Required<ChatResponseConten
|
|
|
429
382
|
arguments?: string;
|
|
430
383
|
finished: boolean;
|
|
431
384
|
result?: string;
|
|
385
|
+
confirmed: Promise<boolean>;
|
|
386
|
+
confirm(): void;
|
|
387
|
+
deny(): void;
|
|
432
388
|
}
|
|
433
389
|
|
|
434
390
|
export interface ThinkingChatResponseContent
|
|
@@ -700,26 +656,39 @@ export class MutableChatModel implements ChatModel, Disposable {
|
|
|
700
656
|
|
|
701
657
|
protected _hierarchy: ChatRequestHierarchy<MutableChatRequestModel>;
|
|
702
658
|
protected _id: string;
|
|
703
|
-
protected _changeSet?: ChangeSetImpl;
|
|
704
659
|
protected _suggestions: readonly ChatSuggestion[] = [];
|
|
705
660
|
protected readonly _contextManager = new ChatContextManagerImpl();
|
|
661
|
+
protected readonly _changeSet: ChatTreeChangeSet;
|
|
706
662
|
protected _settings: { [key: string]: unknown };
|
|
707
663
|
|
|
708
664
|
constructor(public readonly location = ChatAgentLocation.Panel) {
|
|
709
665
|
// TODO accept serialized data as a parameter to restore a previously saved ChatModel
|
|
710
666
|
this._hierarchy = new ChatRequestHierarchyImpl<MutableChatRequestModel>();
|
|
667
|
+
this._changeSet = new ChatTreeChangeSet(this._hierarchy);
|
|
668
|
+
this.toDispose.push(this._changeSet);
|
|
669
|
+
this._changeSet.onDidChange(this._onDidChangeEmitter.fire, this._onDidChangeEmitter, this.toDispose);
|
|
711
670
|
this._id = generateUuid();
|
|
712
671
|
|
|
713
672
|
this.toDispose.pushAll([
|
|
714
673
|
this._onDidChangeEmitter,
|
|
715
|
-
this._contextManager.onDidChange(
|
|
716
|
-
this._hierarchy.onDidChange(event =>
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
674
|
+
this._contextManager.onDidChange(this._onDidChangeEmitter.fire, this._onDidChangeEmitter),
|
|
675
|
+
this._hierarchy.onDidChange(event => {
|
|
676
|
+
this._onDidChangeEmitter.fire({
|
|
677
|
+
kind: 'changeHierarchyBranch',
|
|
678
|
+
branch: event.branch,
|
|
679
|
+
});
|
|
680
|
+
}),
|
|
720
681
|
]);
|
|
721
682
|
}
|
|
722
683
|
|
|
684
|
+
get id(): string {
|
|
685
|
+
return this._id;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
get changeSet(): ChangeSet {
|
|
689
|
+
return this._changeSet;
|
|
690
|
+
}
|
|
691
|
+
|
|
723
692
|
getBranches(): ChatHierarchyBranch<ChatRequestModel>[] {
|
|
724
693
|
return this._hierarchy.activeBranches();
|
|
725
694
|
}
|
|
@@ -736,14 +705,6 @@ export class MutableChatModel implements ChatModel, Disposable {
|
|
|
736
705
|
return this.getRequests().find(request => request.id === id);
|
|
737
706
|
}
|
|
738
707
|
|
|
739
|
-
get id(): string {
|
|
740
|
-
return this._id;
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
get changeSet(): ChangeSetImpl | undefined {
|
|
744
|
-
return this._changeSet;
|
|
745
|
-
}
|
|
746
|
-
|
|
747
708
|
get suggestions(): readonly ChatSuggestion[] {
|
|
748
709
|
return this._suggestions;
|
|
749
710
|
}
|
|
@@ -760,46 +721,18 @@ export class MutableChatModel implements ChatModel, Disposable {
|
|
|
760
721
|
this._settings = settings;
|
|
761
722
|
}
|
|
762
723
|
|
|
763
|
-
setChangeSet(changeSet: ChangeSetImpl | undefined): void {
|
|
764
|
-
if (!changeSet) {
|
|
765
|
-
return this.removeChangeSet();
|
|
766
|
-
}
|
|
767
|
-
const oldChangeSet = this._changeSet;
|
|
768
|
-
oldChangeSet?.dispose();
|
|
769
|
-
this._changeSet = changeSet;
|
|
770
|
-
this._onDidChangeEmitter.fire({
|
|
771
|
-
kind: 'setChangeSet',
|
|
772
|
-
changeSet,
|
|
773
|
-
oldChangeSet,
|
|
774
|
-
});
|
|
775
|
-
changeSet.onDidChange(() => {
|
|
776
|
-
this._onDidChangeEmitter.fire({
|
|
777
|
-
kind: 'updateChangeSet',
|
|
778
|
-
changeSet,
|
|
779
|
-
});
|
|
780
|
-
});
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
removeChangeSet(): void {
|
|
784
|
-
if (this._changeSet) {
|
|
785
|
-
const oldChangeSet = this._changeSet;
|
|
786
|
-
this._changeSet = undefined;
|
|
787
|
-
oldChangeSet.dispose();
|
|
788
|
-
this._onDidChangeEmitter.fire({
|
|
789
|
-
kind: 'removeChangeSet',
|
|
790
|
-
changeSet: oldChangeSet,
|
|
791
|
-
});
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
|
|
795
724
|
addRequest(parsedChatRequest: ParsedChatRequest, agentId?: string, context: ChatContext = { variables: [] }): MutableChatRequestModel {
|
|
796
|
-
|
|
797
|
-
return this.applyEdit(parsedChatRequest, agentId, context);
|
|
798
|
-
}
|
|
799
|
-
|
|
725
|
+
const add = this.getTargetForRequestAddition(parsedChatRequest);
|
|
800
726
|
const requestModel = new MutableChatRequestModel(this, parsedChatRequest, agentId, context);
|
|
801
|
-
|
|
802
|
-
|
|
727
|
+
requestModel.onDidChange(event => {
|
|
728
|
+
if (!ChatChangeEvent.isChangeSetEvent(event)) {
|
|
729
|
+
this._onDidChangeEmitter.fire(event);
|
|
730
|
+
}
|
|
731
|
+
}, this, this.toDispose);
|
|
732
|
+
|
|
733
|
+
add(requestModel);
|
|
734
|
+
this._changeSet.registerRequest(requestModel);
|
|
735
|
+
|
|
803
736
|
this._onDidChangeEmitter.fire({
|
|
804
737
|
kind: 'addRequest',
|
|
805
738
|
request: requestModel,
|
|
@@ -807,6 +740,13 @@ export class MutableChatModel implements ChatModel, Disposable {
|
|
|
807
740
|
return requestModel;
|
|
808
741
|
}
|
|
809
742
|
|
|
743
|
+
protected getTargetForRequestAddition(request: ParsedChatRequest): (addendum: MutableChatRequestModel) => void {
|
|
744
|
+
const requestId = request.request.referencedRequestId;
|
|
745
|
+
const branch = requestId !== undefined && this._hierarchy.findBranch(requestId);
|
|
746
|
+
if (requestId !== undefined && !branch) { throw new Error(`Cannot find branch for requestId: ${requestId}`); }
|
|
747
|
+
return branch ? branch.add.bind(branch) : this._hierarchy.append.bind(this._hierarchy);
|
|
748
|
+
}
|
|
749
|
+
|
|
810
750
|
setSuggestions(suggestions: ChatSuggestion[]): void {
|
|
811
751
|
this._suggestions = Object.freeze(suggestions);
|
|
812
752
|
this._onDidChangeEmitter.fire({
|
|
@@ -819,33 +759,132 @@ export class MutableChatModel implements ChatModel, Disposable {
|
|
|
819
759
|
return this.getRequests().length === 0;
|
|
820
760
|
}
|
|
821
761
|
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
762
|
+
dispose(): void {
|
|
763
|
+
this.toDispose.dispose();
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
export class ChatTreeChangeSet implements Omit<ChangeSet, 'onDidChange'> {
|
|
768
|
+
protected readonly onDidChangeEmitter = new Emitter<ChatUpdateChangeSetEvent>();
|
|
769
|
+
get onDidChange(): Event<ChatUpdateChangeSetEvent> {
|
|
770
|
+
return this.onDidChangeEmitter.event;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
protected readonly toDispose = new DisposableCollection();
|
|
774
|
+
|
|
775
|
+
constructor(protected readonly hierarchy: ChatRequestHierarchy<MutableChatRequestModel>) {
|
|
776
|
+
hierarchy.onDidChange(this.handleChangeSetChange, this, this.toDispose);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
get title(): string {
|
|
780
|
+
return this.getCurrentChangeSet()?.title ?? '';
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
removeElements(...uris: URI[]): boolean {
|
|
784
|
+
return this.getMutableChangeSet().removeElements(...uris);
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
addElements(...elements: ChangeSetElement[]): boolean {
|
|
788
|
+
return this.getMutableChangeSet().addElements(...elements);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
setElements(...elements: ChangeSetElement[]): void {
|
|
792
|
+
this.getMutableChangeSet().setElements(...elements);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
setTitle(title: string): void {
|
|
796
|
+
this.getMutableChangeSet().setTitle(title);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
getElementByURI(uri: URI): ChangeSetElement | undefined {
|
|
800
|
+
return this.currentElements.find(candidate => candidate.uri.isEqual(uri));
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
protected currentElements: ChangeSetElement[] = [];
|
|
804
|
+
protected handleChangeSetChange = debounce(this.doHandleChangeSetChange.bind(this), 100, { leading: false, trailing: true });
|
|
805
|
+
protected doHandleChangeSetChange(): void {
|
|
806
|
+
const newElements = this.computeChangeSetElements();
|
|
807
|
+
this.handleElementChange(newElements);
|
|
808
|
+
this.currentElements = newElements;
|
|
809
|
+
this.onDidChangeEmitter.fire({ kind: 'updateChangeSet', elements: this.currentElements, title: this.getCurrentChangeSet()?.title });
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
getElements(): ChangeSetElement[] {
|
|
813
|
+
return this.currentElements;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
protected computeChangeSetElements(): ChangeSetElement[] {
|
|
817
|
+
const allElements = ChangeSetImpl.combine((function* (requests: MutableChatRequestModel[]): IterableIterator<ChangeSetImpl> {
|
|
818
|
+
for (let i = requests.length - 1; i >= 0; i--) {
|
|
819
|
+
const changeSet = requests[i].changeSet;
|
|
820
|
+
if (changeSet) { yield changeSet; }
|
|
821
|
+
}
|
|
822
|
+
})(this.hierarchy.activeRequests()));
|
|
823
|
+
return ArrayUtils.coalesce(Array.from(allElements.values()));
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
protected handleElementChange(newElements: ChangeSetElement[]): void {
|
|
827
|
+
const old = new Set(this.currentElements);
|
|
828
|
+
for (const element of newElements) {
|
|
829
|
+
if (!old.delete(element)) {
|
|
830
|
+
element.onShow?.();
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
for (const element of old) {
|
|
834
|
+
element.onHide?.();
|
|
827
835
|
}
|
|
836
|
+
}
|
|
828
837
|
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
this.
|
|
838
|
+
protected toDisposeOnRequestAdded = new DisposableCollection();
|
|
839
|
+
registerRequest(request: MutableChatRequestModel): void {
|
|
840
|
+
request.onDidChange(event => event.kind === 'updateChangeSet' && this.handleChangeSetChange(), this, this.toDispose);
|
|
841
|
+
if (this.localChangeSet) {
|
|
842
|
+
request.changeSet = this.localChangeSet;
|
|
843
|
+
this.localChangeSet = undefined;
|
|
844
|
+
}
|
|
845
|
+
this.toDisposeOnRequestAdded.dispose();
|
|
846
|
+
}
|
|
833
847
|
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
848
|
+
protected localChangeSet?: ChangeSetImpl;
|
|
849
|
+
protected getMutableChangeSet(): ChangeSetImpl {
|
|
850
|
+
const tipRequest = this.hierarchy.activeRequests().at(-1);
|
|
851
|
+
const existingChangeSet = tipRequest?.changeSet;
|
|
852
|
+
if (existingChangeSet) {
|
|
853
|
+
return existingChangeSet;
|
|
854
|
+
}
|
|
855
|
+
if (this.localChangeSet && tipRequest) {
|
|
856
|
+
throw new Error('Non-empty chat model retained reference to own change set. This is unexpected!');
|
|
857
|
+
}
|
|
858
|
+
if (this.localChangeSet) {
|
|
859
|
+
return this.localChangeSet;
|
|
860
|
+
}
|
|
861
|
+
const newChangeSet = new ChangeSetImpl();
|
|
862
|
+
if (tipRequest) {
|
|
863
|
+
tipRequest.changeSet = newChangeSet;
|
|
864
|
+
} else {
|
|
865
|
+
this.localChangeSet = newChangeSet;
|
|
866
|
+
newChangeSet.onDidChange(this.handleChangeSetChange, this, this.toDisposeOnRequestAdded);
|
|
867
|
+
}
|
|
868
|
+
return newChangeSet;
|
|
869
|
+
}
|
|
838
870
|
|
|
839
|
-
|
|
871
|
+
protected getCurrentChangeSet(): ChangeSet | undefined {
|
|
872
|
+
const holder = this.getBranchParent(candidate => !!candidate.get().changeSet);
|
|
873
|
+
return holder?.get().changeSet ?? this.localChangeSet;
|
|
840
874
|
}
|
|
841
875
|
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
this.
|
|
876
|
+
/** Returns the lowest node among active nodes that satisfies {@link criterion} */
|
|
877
|
+
getBranchParent(criterion: (branch: ChatHierarchyBranch<MutableChatRequestModel>) => boolean): ChatHierarchyBranch<MutableChatRequestModel> | undefined {
|
|
878
|
+
const branches = this.hierarchy.activeBranches();
|
|
879
|
+
for (let i = branches.length - 1; i >= 0; i--) {
|
|
880
|
+
const branch = branches[i];
|
|
881
|
+
if (criterion?.(branch)) { return branch; }
|
|
882
|
+
}
|
|
883
|
+
return branches.at(0);
|
|
845
884
|
}
|
|
846
885
|
|
|
847
|
-
|
|
848
|
-
this.
|
|
886
|
+
dispose(): void {
|
|
887
|
+
this.toDispose.dispose();
|
|
849
888
|
}
|
|
850
889
|
}
|
|
851
890
|
|
|
@@ -915,6 +954,11 @@ export class ChatRequestHierarchyImpl<TRequest extends ChatRequestModel = ChatRe
|
|
|
915
954
|
notifyChange(event: ChangeActiveBranchEvent<TRequest>): void {
|
|
916
955
|
this.onDidChangeActiveBranchEmitter.fire(event);
|
|
917
956
|
}
|
|
957
|
+
|
|
958
|
+
dispose(): void {
|
|
959
|
+
this.onDidChangeActiveBranchEmitter.dispose();
|
|
960
|
+
this.branch.dispose();
|
|
961
|
+
}
|
|
918
962
|
}
|
|
919
963
|
|
|
920
964
|
export class ChatRequestHierarchyBranchImpl<TRequest extends ChatRequestModel> implements ChatHierarchyBranch<TRequest> {
|
|
@@ -1019,82 +1063,12 @@ export class ChatRequestHierarchyBranchImpl<TRequest extends ChatRequestModel> i
|
|
|
1019
1063
|
|
|
1020
1064
|
return branches;
|
|
1021
1065
|
}
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
interface ChangeSetChangeEvent {
|
|
1025
|
-
added?: URI[],
|
|
1026
|
-
removed?: URI[],
|
|
1027
|
-
modified?: URI[],
|
|
1028
|
-
/** Fired when only the state of a given element changes, not its contents */
|
|
1029
|
-
state?: URI[],
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
export class ChangeSetImpl implements ChangeSet {
|
|
1033
|
-
protected readonly _onDidChangeEmitter = new Emitter<ChangeSetChangeEvent>();
|
|
1034
|
-
onDidChange: Event<ChangeSetChangeEvent> = this._onDidChangeEmitter.event;
|
|
1035
|
-
|
|
1036
|
-
protected _elements: ChangeSetElement[] = [];
|
|
1037
|
-
|
|
1038
|
-
constructor(public readonly title: string, elements: ChangeSetElement[] = []) {
|
|
1039
|
-
this.addElements(...elements);
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
getElements(): ChangeSetElement[] {
|
|
1043
|
-
return this._elements;
|
|
1044
|
-
}
|
|
1045
|
-
|
|
1046
|
-
/**
|
|
1047
|
-
* Find an element by URI.
|
|
1048
|
-
* @param uri The URI to look for.
|
|
1049
|
-
* @returns The element with the given URI, or undefined if not found.
|
|
1050
|
-
*/
|
|
1051
|
-
getElementByURI(uri: URI): ChangeSetElement | undefined {
|
|
1052
|
-
const uriString = uri.toString();
|
|
1053
|
-
for (const element of this._elements) {
|
|
1054
|
-
if (element.uri.toString() === uriString) {
|
|
1055
|
-
return element;
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
return undefined;
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
/** Will replace any element that is already present, using URI as identity criterion. */
|
|
1062
|
-
addElements(...elements: ChangeSetElement[]): void {
|
|
1063
|
-
const added: URI[] = [];
|
|
1064
|
-
const modified: URI[] = [];
|
|
1065
|
-
const toDispose: ChangeSetElement[] = [];
|
|
1066
|
-
const current = new Map(this.getElements().map((element, index) => [element.uri.toString(), index]));
|
|
1067
|
-
elements.forEach(element => {
|
|
1068
|
-
const existingIndex = current.get(element.uri.toString());
|
|
1069
|
-
if (existingIndex !== undefined) {
|
|
1070
|
-
modified.push(element.uri);
|
|
1071
|
-
toDispose.push(this._elements[existingIndex]);
|
|
1072
|
-
this._elements[existingIndex] = element;
|
|
1073
|
-
} else {
|
|
1074
|
-
added.push(element.uri);
|
|
1075
|
-
this._elements.push(element);
|
|
1076
|
-
}
|
|
1077
|
-
element.onDidChange?.(() => this.notifyChange({ state: [element.uri] }));
|
|
1078
|
-
});
|
|
1079
|
-
toDispose.forEach(element => element.dispose?.());
|
|
1080
|
-
this.notifyChange({ added, modified });
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
removeElements(...indices: number[]): void {
|
|
1084
|
-
// From highest to lowest so that we don't affect lower indices with our splicing.
|
|
1085
|
-
const sorted = indices.slice().sort((left, right) => left - right);
|
|
1086
|
-
const deletions = sorted.flatMap(index => this._elements.splice(index, 1));
|
|
1087
|
-
deletions.forEach(deleted => deleted.dispose?.());
|
|
1088
|
-
this.notifyChange({ removed: deletions.map(element => element.uri) });
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
|
-
protected notifyChange(change: ChangeSetChangeEvent): void {
|
|
1092
|
-
this._onDidChangeEmitter.fire(change);
|
|
1093
|
-
}
|
|
1094
1066
|
|
|
1095
1067
|
dispose(): void {
|
|
1096
|
-
this.
|
|
1097
|
-
|
|
1068
|
+
if (Disposable.is(this.get())) {
|
|
1069
|
+
this.items.forEach(({ element }) => Disposable.is(element) && element.dispose());
|
|
1070
|
+
}
|
|
1071
|
+
this.items.length = 0;
|
|
1098
1072
|
}
|
|
1099
1073
|
}
|
|
1100
1074
|
|
|
@@ -1161,10 +1135,13 @@ export class ChatContextManagerImpl implements ChatContextManager {
|
|
|
1161
1135
|
}
|
|
1162
1136
|
|
|
1163
1137
|
export class MutableChatRequestModel implements ChatRequestModel, EditableChatRequestModel, Disposable {
|
|
1138
|
+
protected readonly _onDidChangeEmitter = new Emitter<ChatChangeEvent>();
|
|
1139
|
+
onDidChange: Event<ChatChangeEvent> = this._onDidChangeEmitter.event;
|
|
1164
1140
|
protected readonly _id: string;
|
|
1165
1141
|
protected _session: MutableChatModel;
|
|
1166
1142
|
protected _request: ChatRequest;
|
|
1167
1143
|
protected _response: MutableChatResponseModel;
|
|
1144
|
+
protected _changeSet?: ChangeSetImpl;
|
|
1168
1145
|
protected _context: ChatContext;
|
|
1169
1146
|
protected _agentId?: string;
|
|
1170
1147
|
protected _data: { [key: string]: unknown };
|
|
@@ -1185,9 +1162,20 @@ export class MutableChatRequestModel implements ChatRequestModel, EditableChatRe
|
|
|
1185
1162
|
this._data = data;
|
|
1186
1163
|
|
|
1187
1164
|
this.editContextManager = new ChatContextManagerImpl(context);
|
|
1188
|
-
this.
|
|
1189
|
-
|
|
1190
|
-
|
|
1165
|
+
this.editContextManager.onDidChange(this._onDidChangeEmitter.fire, this._onDidChangeEmitter, this.toDispose);
|
|
1166
|
+
this.toDispose.push(this._onDidChangeEmitter);
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
get changeSet(): ChangeSetImpl | undefined {
|
|
1170
|
+
return this._changeSet;
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
set changeSet(changeSet: ChangeSetImpl) {
|
|
1174
|
+
this._changeSet?.dispose();
|
|
1175
|
+
this._changeSet = changeSet;
|
|
1176
|
+
this.toDispose.push(changeSet);
|
|
1177
|
+
changeSet.onDidChange(() => this._onDidChangeEmitter.fire({ kind: 'updateChangeSet', elements: changeSet.getElements(), title: changeSet.title }), this, this.toDispose);
|
|
1178
|
+
this._onDidChangeEmitter.fire({ kind: 'updateChangeSet', elements: changeSet.getElements(), title: changeSet.title });
|
|
1191
1179
|
}
|
|
1192
1180
|
|
|
1193
1181
|
get isEditing(): boolean {
|
|
@@ -1280,7 +1268,7 @@ export class MutableChatRequestModel implements ChatRequestModel, EditableChatRe
|
|
|
1280
1268
|
if (!branch) {
|
|
1281
1269
|
throw new Error(`Cannot find hierarchy for requestId: ${request.id}`);
|
|
1282
1270
|
}
|
|
1283
|
-
this.
|
|
1271
|
+
this._onDidChangeEmitter.fire({
|
|
1284
1272
|
kind: 'enableEdit',
|
|
1285
1273
|
request,
|
|
1286
1274
|
branch,
|
|
@@ -1292,7 +1280,7 @@ export class MutableChatRequestModel implements ChatRequestModel, EditableChatRe
|
|
|
1292
1280
|
if (!branch) {
|
|
1293
1281
|
throw new Error(`Cannot find branch for requestId: ${request.id}`);
|
|
1294
1282
|
}
|
|
1295
|
-
this.
|
|
1283
|
+
this._onDidChangeEmitter.fire({
|
|
1296
1284
|
kind: 'cancelEdit',
|
|
1297
1285
|
request,
|
|
1298
1286
|
branch,
|
|
@@ -1304,7 +1292,7 @@ export class MutableChatRequestModel implements ChatRequestModel, EditableChatRe
|
|
|
1304
1292
|
if (!branch) {
|
|
1305
1293
|
throw new Error(`Cannot find branch for requestId: ${request.id}`);
|
|
1306
1294
|
}
|
|
1307
|
-
this.
|
|
1295
|
+
this._onDidChangeEmitter.fire({
|
|
1308
1296
|
kind: 'submitEdit',
|
|
1309
1297
|
request,
|
|
1310
1298
|
branch,
|
|
@@ -1359,6 +1347,7 @@ export class TextChatResponseContentImpl implements TextChatResponseContent {
|
|
|
1359
1347
|
};
|
|
1360
1348
|
}
|
|
1361
1349
|
}
|
|
1350
|
+
|
|
1362
1351
|
export class ThinkingChatResponseContentImpl implements ThinkingChatResponseContent {
|
|
1363
1352
|
readonly kind = 'thinking';
|
|
1364
1353
|
protected _content: string;
|
|
@@ -1501,6 +1490,9 @@ export class ToolCallChatResponseContentImpl implements ToolCallChatResponseCont
|
|
|
1501
1490
|
protected _arguments?: string;
|
|
1502
1491
|
protected _finished?: boolean;
|
|
1503
1492
|
protected _result?: string;
|
|
1493
|
+
protected _confirmed: Promise<boolean>;
|
|
1494
|
+
protected _confirmationResolver?: (value: boolean) => void;
|
|
1495
|
+
protected _confirmationRejecter?: (reason?: unknown) => void;
|
|
1504
1496
|
|
|
1505
1497
|
constructor(id?: string, name?: string, arg_string?: string, finished?: boolean, result?: string) {
|
|
1506
1498
|
this._id = id;
|
|
@@ -1508,6 +1500,8 @@ export class ToolCallChatResponseContentImpl implements ToolCallChatResponseCont
|
|
|
1508
1500
|
this._arguments = arg_string;
|
|
1509
1501
|
this._finished = finished;
|
|
1510
1502
|
this._result = result;
|
|
1503
|
+
// Initialize the confirmation promise immediately
|
|
1504
|
+
this._confirmed = this.createConfirmationPromise();
|
|
1511
1505
|
}
|
|
1512
1506
|
|
|
1513
1507
|
get id(): string | undefined {
|
|
@@ -1529,6 +1523,53 @@ export class ToolCallChatResponseContentImpl implements ToolCallChatResponseCont
|
|
|
1529
1523
|
return this._result;
|
|
1530
1524
|
}
|
|
1531
1525
|
|
|
1526
|
+
get confirmed(): Promise<boolean> {
|
|
1527
|
+
return this._confirmed;
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
/**
|
|
1531
|
+
* Create a confirmation promise that can be resolved/rejected later
|
|
1532
|
+
*/
|
|
1533
|
+
createConfirmationPromise(): Promise<boolean> {
|
|
1534
|
+
// The promise is always created, just ensure we have resolution handlers
|
|
1535
|
+
if (!this._confirmationResolver) {
|
|
1536
|
+
this._confirmed = new Promise<boolean>((resolve, reject) => {
|
|
1537
|
+
this._confirmationResolver = resolve;
|
|
1538
|
+
this._confirmationRejecter = reject;
|
|
1539
|
+
});
|
|
1540
|
+
}
|
|
1541
|
+
return this._confirmed;
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
/**
|
|
1545
|
+
* Confirm the tool execution
|
|
1546
|
+
*/
|
|
1547
|
+
confirm(): void {
|
|
1548
|
+
if (this._confirmationResolver) {
|
|
1549
|
+
this._confirmationResolver(true);
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
/**
|
|
1554
|
+
* Deny the tool execution
|
|
1555
|
+
*/
|
|
1556
|
+
deny(): void {
|
|
1557
|
+
if (this._confirmationResolver) {
|
|
1558
|
+
this._confirmationResolver(false);
|
|
1559
|
+
this._finished = true;
|
|
1560
|
+
this._result = 'Tool execution denied by user';
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
/**
|
|
1565
|
+
* Cancel the confirmation (reject the promise)
|
|
1566
|
+
*/
|
|
1567
|
+
cancelConfirmation(reason?: unknown): void {
|
|
1568
|
+
if (this._confirmationRejecter) {
|
|
1569
|
+
this._confirmationRejecter(reason);
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1532
1573
|
asString(): string {
|
|
1533
1574
|
return '';
|
|
1534
1575
|
}
|
|
@@ -1543,6 +1584,7 @@ export class ToolCallChatResponseContentImpl implements ToolCallChatResponseCont
|
|
|
1543
1584
|
this._result = nextChatResponseContent.result;
|
|
1544
1585
|
const args = nextChatResponseContent.arguments;
|
|
1545
1586
|
this._arguments = (args && args.length > 0) ? args : this._arguments;
|
|
1587
|
+
// Don't merge confirmation promises - they should be managed separately
|
|
1546
1588
|
return true;
|
|
1547
1589
|
}
|
|
1548
1590
|
if (nextChatResponseContent.name !== undefined) {
|