@theia/ai-chat 1.61.0-next.8 → 1.62.0-next.3

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.
Files changed (98) hide show
  1. package/lib/browser/ai-chat-frontend-contribution.d.ts +11 -0
  2. package/lib/browser/ai-chat-frontend-contribution.d.ts.map +1 -0
  3. package/lib/browser/ai-chat-frontend-contribution.js +56 -0
  4. package/lib/browser/ai-chat-frontend-contribution.js.map +1 -0
  5. package/lib/browser/ai-chat-frontend-module.d.ts.map +1 -1
  6. package/lib/browser/ai-chat-frontend-module.js +21 -3
  7. package/lib/browser/ai-chat-frontend-module.js.map +1 -1
  8. package/lib/browser/change-set-decorator-service.d.ts +24 -0
  9. package/lib/browser/change-set-decorator-service.d.ts.map +1 -0
  10. package/lib/browser/change-set-decorator-service.js +66 -0
  11. package/lib/browser/change-set-decorator-service.js.map +1 -0
  12. package/lib/browser/change-set-file-element.d.ts +7 -4
  13. package/lib/browser/change-set-file-element.d.ts.map +1 -1
  14. package/lib/browser/change-set-file-element.js +20 -12
  15. package/lib/browser/change-set-file-element.js.map +1 -1
  16. package/lib/browser/change-set-file-resource.d.ts +1 -42
  17. package/lib/browser/change-set-file-resource.d.ts.map +1 -1
  18. package/lib/browser/change-set-file-resource.js +1 -136
  19. package/lib/browser/change-set-file-resource.js.map +1 -1
  20. package/lib/browser/change-set-variable.d.ts.map +1 -1
  21. package/lib/browser/change-set-variable.js +13 -4
  22. package/lib/browser/change-set-variable.js.map +1 -1
  23. package/lib/browser/context-file-variable-label-provider.js +1 -1
  24. package/lib/browser/context-file-variable-label-provider.js.map +1 -1
  25. package/lib/browser/file-chat-variable-contribution.d.ts.map +1 -1
  26. package/lib/browser/file-chat-variable-contribution.js +29 -27
  27. package/lib/browser/file-chat-variable-contribution.js.map +1 -1
  28. package/lib/browser/task-context-service.d.ts +40 -0
  29. package/lib/browser/task-context-service.d.ts.map +1 -0
  30. package/lib/browser/task-context-service.js +148 -0
  31. package/lib/browser/task-context-service.js.map +1 -0
  32. package/lib/browser/task-context-storage-service.d.ts +18 -0
  33. package/lib/browser/task-context-storage-service.d.ts.map +1 -0
  34. package/lib/browser/task-context-storage-service.js +77 -0
  35. package/lib/browser/task-context-storage-service.js.map +1 -0
  36. package/lib/browser/task-context-variable-contribution.d.ts +20 -0
  37. package/lib/browser/task-context-variable-contribution.d.ts.map +1 -0
  38. package/lib/browser/task-context-variable-contribution.js +101 -0
  39. package/lib/browser/task-context-variable-contribution.js.map +1 -0
  40. package/lib/browser/task-context-variable-label-provider.d.ts +21 -0
  41. package/lib/browser/task-context-variable-label-provider.d.ts.map +1 -0
  42. package/lib/browser/task-context-variable-label-provider.js +83 -0
  43. package/lib/browser/task-context-variable-label-provider.js.map +1 -0
  44. package/lib/browser/task-context-variable.d.ts +3 -0
  45. package/lib/browser/task-context-variable.d.ts.map +1 -0
  46. package/lib/browser/task-context-variable.js +29 -0
  47. package/lib/browser/task-context-variable.js.map +1 -0
  48. package/lib/common/chat-agents.d.ts +2 -1
  49. package/lib/common/chat-agents.d.ts.map +1 -1
  50. package/lib/common/chat-agents.js +5 -0
  51. package/lib/common/chat-agents.js.map +1 -1
  52. package/lib/common/chat-model.d.ts +173 -8
  53. package/lib/common/chat-model.d.ts.map +1 -1
  54. package/lib/common/chat-model.js +329 -17
  55. package/lib/common/chat-model.js.map +1 -1
  56. package/lib/common/chat-request-parser.d.ts +1 -1
  57. package/lib/common/chat-request-parser.d.ts.map +1 -1
  58. package/lib/common/chat-request-parser.js +1 -1
  59. package/lib/common/chat-request-parser.js.map +1 -1
  60. package/lib/common/chat-service.d.ts +2 -0
  61. package/lib/common/chat-service.d.ts.map +1 -1
  62. package/lib/common/chat-service.js +18 -25
  63. package/lib/common/chat-service.js.map +1 -1
  64. package/lib/common/chat-session-naming-service.d.ts.map +1 -1
  65. package/lib/common/chat-session-naming-service.js +3 -3
  66. package/lib/common/chat-session-naming-service.js.map +1 -1
  67. package/lib/common/chat-session-summary-agent-prompt.d.ts +5 -0
  68. package/lib/common/chat-session-summary-agent-prompt.d.ts.map +1 -0
  69. package/lib/common/chat-session-summary-agent-prompt.js +30 -0
  70. package/lib/common/chat-session-summary-agent-prompt.js.map +1 -0
  71. package/lib/common/chat-session-summary-agent.d.ts +17 -0
  72. package/lib/common/chat-session-summary-agent.d.ts.map +1 -0
  73. package/lib/common/chat-session-summary-agent.js +48 -0
  74. package/lib/common/chat-session-summary-agent.js.map +1 -0
  75. package/lib/common/context-summary-variable.js +1 -1
  76. package/lib/common/context-summary-variable.js.map +1 -1
  77. package/package.json +11 -11
  78. package/src/browser/ai-chat-frontend-contribution.ts +49 -0
  79. package/src/browser/ai-chat-frontend-module.ts +25 -4
  80. package/src/browser/change-set-decorator-service.ts +72 -0
  81. package/src/browser/change-set-file-element.ts +18 -13
  82. package/src/browser/change-set-file-resource.ts +1 -138
  83. package/src/browser/change-set-variable.ts +14 -6
  84. package/src/browser/context-file-variable-label-provider.ts +1 -1
  85. package/src/browser/file-chat-variable-contribution.ts +26 -29
  86. package/src/browser/task-context-service.ts +144 -0
  87. package/src/browser/task-context-storage-service.ts +75 -0
  88. package/src/browser/task-context-variable-contribution.ts +93 -0
  89. package/src/browser/task-context-variable-label-provider.ts +67 -0
  90. package/src/browser/task-context-variable.ts +28 -0
  91. package/src/common/chat-agents.ts +6 -1
  92. package/src/common/chat-model.ts +507 -18
  93. package/src/common/chat-request-parser.ts +2 -2
  94. package/src/common/chat-service.ts +17 -26
  95. package/src/common/chat-session-naming-service.ts +4 -3
  96. package/src/common/chat-session-summary-agent-prompt.ts +28 -0
  97. package/src/common/chat-session-summary-agent.ts +42 -0
  98. package/src/common/context-summary-variable.ts +1 -1
@@ -19,12 +19,12 @@
19
19
  *--------------------------------------------------------------------------------------------*/
20
20
  // Partially copied from https://github.com/microsoft/vscode/blob/a2cab7255c0df424027be05d58e1b7b941f4ea60/src/vs/workbench/contrib/chat/common/chatModel.ts
21
21
 
22
- import { CancellationToken, CancellationTokenSource, Command, Disposable, Emitter, Event, generateUuid, URI } from '@theia/core';
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
24
  import { MarkdownString, MarkdownStringImpl } from '@theia/core/lib/common/markdown-rendering';
24
25
  import { Position } from '@theia/core/shared/vscode-languageserver-protocol';
25
26
  import { ChatAgentLocation } from './chat-agents';
26
27
  import { ParsedChatRequest } from './parsed-chat-request';
27
- import { AIVariableResolutionRequest, LanguageModelMessage, ResolvedAIContextVariable, TextMessage, ThinkingMessage, ToolResultMessage, ToolUseMessage } from '@theia/ai-core';
28
28
 
29
29
  /**********************
30
30
  * INTERFACES AND TYPE GUARDS
@@ -35,16 +35,46 @@ export type ChatChangeEvent =
35
35
  | ChatAddResponseEvent
36
36
  | ChatAddVariableEvent
37
37
  | ChatRemoveVariableEvent
38
+ | ChatSetVariablesEvent
38
39
  | ChatRemoveRequestEvent
39
40
  | ChatSetChangeSetEvent
41
+ | ChatSuggestionsChangedEvent
40
42
  | ChatUpdateChangeSetEvent
41
- | ChatRemoveChangeSetEvent;
43
+ | ChatRemoveChangeSetEvent
44
+ | ChatEditRequestEvent
45
+ | ChatEditCancelEvent
46
+ | ChatEditSubmitEvent
47
+ | ChatChangeHierarchyBranchEvent;
42
48
 
43
49
  export interface ChatAddRequestEvent {
44
50
  kind: 'addRequest';
45
51
  request: ChatRequestModel;
46
52
  }
47
53
 
54
+ export interface ChatEditRequestEvent {
55
+ kind: 'enableEdit';
56
+ request: EditableChatRequestModel;
57
+ branch: ChatHierarchyBranch<ChatRequestModel>;
58
+ }
59
+
60
+ export interface ChatEditCancelEvent {
61
+ kind: 'cancelEdit';
62
+ request: EditableChatRequestModel;
63
+ branch: ChatHierarchyBranch<ChatRequestModel>;
64
+ }
65
+
66
+ export interface ChatEditSubmitEvent {
67
+ kind: 'submitEdit';
68
+ request: EditableChatRequestModel;
69
+ branch: ChatHierarchyBranch<ChatRequestModel>;
70
+ newRequest: ChatRequest;
71
+ }
72
+
73
+ export interface ChatChangeHierarchyBranchEvent {
74
+ kind: 'changeHierarchyBranch';
75
+ branch: ChatHierarchyBranch<ChatRequestModel>;
76
+ }
77
+
48
78
  export interface ChatAddResponseEvent {
49
79
  kind: 'addResponse';
50
80
  response: ChatResponseModel;
@@ -74,6 +104,15 @@ export interface ChatRemoveVariableEvent {
74
104
  kind: 'removeVariable';
75
105
  }
76
106
 
107
+ export interface ChatSetVariablesEvent {
108
+ kind: 'setVariables';
109
+ }
110
+
111
+ export interface ChatSuggestionsChangedEvent {
112
+ kind: 'suggestionsChanged';
113
+ suggestions: ChatSuggestion[];
114
+ }
115
+
77
116
  export namespace ChatChangeEvent {
78
117
  export function isChangeSetEvent(event: ChatChangeEvent): event is ChatSetChangeSetEvent | ChatUpdateChangeSetEvent | ChatRemoveChangeSetEvent {
79
118
  return event.kind === 'setChangeSet' || event.kind === 'removeChangeSet' || event.kind === 'updateChangeSet';
@@ -89,14 +128,76 @@ export interface ChatRemoveRequestEvent {
89
128
  reason: ChatRequestRemovalReason;
90
129
  }
91
130
 
131
+ /**
132
+ * A model that contains information about a chat request that may branch off.
133
+ *
134
+ * The hierarchy of requests is represented by a tree structure.
135
+ * - The root of the tree is the initial request
136
+ * - Within each branch, the requests are stored in a list. Those requests are the alternatives to the original request.
137
+ * Each of those items can have a next branch, which is the next request in the hierarchy.
138
+ */
139
+ export interface ChatRequestHierarchy<TRequest extends ChatRequestModel = ChatRequestModel> {
140
+ readonly branch: ChatHierarchyBranch<TRequest>
141
+
142
+ onDidChange: Event<ChangeActiveBranchEvent<TRequest>>;
143
+
144
+ append(request: TRequest): ChatHierarchyBranch<TRequest>;
145
+ activeRequests(): TRequest[];
146
+ activeBranches(): ChatHierarchyBranch<TRequest>[];
147
+ findRequest(requestId: string): TRequest | undefined;
148
+ findBranch(requestId: string): ChatHierarchyBranch<TRequest> | undefined;
149
+
150
+ notifyChange(event: ChangeActiveBranchEvent<TRequest>): void
151
+ }
152
+
153
+ export interface ChangeActiveBranchEvent<TRequest extends ChatRequestModel = ChatRequestModel> {
154
+ branch: ChatHierarchyBranch<TRequest>,
155
+ item: ChatHierarchyBranchItem<TRequest>
156
+ }
157
+
158
+ /**
159
+ * A branch of the chat request hierarchy.
160
+ * It contains a list of items, each representing a request.
161
+ * Those items can have a next branch, which is the next request in the hierarchy.
162
+ */
163
+ export interface ChatHierarchyBranch<TRequest extends ChatRequestModel = ChatRequestModel> {
164
+ readonly id: string;
165
+ readonly hierarchy: ChatRequestHierarchy<TRequest>;
166
+ readonly previous?: ChatHierarchyBranch<TRequest>;
167
+ readonly items: ChatHierarchyBranchItem<TRequest>[];
168
+ readonly activeBranchIndex: number;
169
+
170
+ next(): ChatHierarchyBranch<TRequest> | undefined;
171
+ get(): TRequest;
172
+ add(request: TRequest): void;
173
+ remove(request: TRequest | string): void;
174
+ /**
175
+ * Create a new branch by inserting it as the next branch of the active item.
176
+ */
177
+ continue(request: TRequest): ChatHierarchyBranch<TRequest>;
178
+
179
+ enable(request: TRequest): ChatHierarchyBranchItem<TRequest>;
180
+ enablePrevious(): ChatHierarchyBranchItem<TRequest>;
181
+ enableNext(): ChatHierarchyBranchItem<TRequest>;
182
+
183
+ succeedingBranches(): ChatHierarchyBranch<TRequest>[];
184
+ }
185
+
186
+ export interface ChatHierarchyBranchItem<TRequest extends ChatRequestModel = ChatRequestModel> {
187
+ readonly element: TRequest;
188
+ readonly next?: ChatHierarchyBranch<TRequest>;
189
+ }
190
+
92
191
  export interface ChatModel {
93
192
  readonly onDidChange: Event<ChatChangeEvent>;
94
193
  readonly id: string;
95
194
  readonly location: ChatAgentLocation;
96
195
  readonly changeSet?: ChangeSet;
97
196
  readonly context: ChatContextManager;
197
+ readonly suggestions: readonly ChatSuggestion[];
98
198
  readonly settings?: { [key: string]: unknown };
99
199
  getRequests(): ChatRequestModel[];
200
+ getBranches(): ChatHierarchyBranch<ChatRequestModel>[];
100
201
  isEmpty(): boolean;
101
202
  }
102
203
 
@@ -107,8 +208,26 @@ export interface ChangeSet extends Disposable {
107
208
  dispose(): void;
108
209
  }
109
210
 
211
+ export interface ChatSuggestionCallback {
212
+ kind: 'callback',
213
+ callback: () => unknown;
214
+ content: string | MarkdownString;
215
+ }
216
+ export namespace ChatSuggestionCallback {
217
+ export function is(candidate: ChatSuggestion): candidate is ChatSuggestionCallback {
218
+ return typeof candidate === 'object' && 'callback' in candidate;
219
+ }
220
+ export function containsCallbackLink(candidate: ChatSuggestion): candidate is ChatSuggestionCallback {
221
+ if (!is(candidate)) { return false; }
222
+ const text = typeof candidate.content === 'string' ? candidate.content : candidate.content.value;
223
+ return text.includes('](_callback)');
224
+ }
225
+ }
226
+
227
+ export type ChatSuggestion = | string | MarkdownString | ChatSuggestionCallback;
228
+
110
229
  export interface ChatContextManager {
111
- onDidChange: Event<ChatAddVariableEvent | ChatRemoveVariableEvent>;
230
+ onDidChange: Event<ChatAddVariableEvent | ChatRemoveVariableEvent | ChatSetVariablesEvent>;
112
231
  getVariables(): readonly AIVariableResolutionRequest[]
113
232
  addVariables(...variables: AIVariableResolutionRequest[]): void;
114
233
  deleteVariables(...indices: number[]): void;
@@ -134,9 +253,21 @@ export interface ChangeSetElement {
134
253
  dispose?(): void;
135
254
  }
136
255
 
256
+ export interface ChangeSetDecoration {
257
+ readonly priority?: number;
258
+ readonly additionalInfoSuffixIcon?: string[];
259
+ }
260
+
137
261
  export interface ChatRequest {
138
262
  readonly text: string;
139
263
  readonly displayText?: string;
264
+ /**
265
+ * If the request has been triggered in the context of
266
+ * an existing request, this id will be set to the id of the
267
+ * referenced request.
268
+ */
269
+ readonly referencedRequestId?: string;
270
+ readonly variables?: readonly AIVariableResolutionRequest[];
140
271
  }
141
272
 
142
273
  export interface ChatContext {
@@ -180,6 +311,29 @@ export namespace ChatRequestModel {
180
311
  }
181
312
  }
182
313
 
314
+ export interface EditableChatRequestModel extends ChatRequestModel {
315
+ readonly isEditing: boolean;
316
+ editContextManager: ChatContextManagerImpl;
317
+ enableEdit(): void;
318
+ cancelEdit(): void;
319
+ submitEdit(newRequest: ChatRequest): void;
320
+ }
321
+
322
+ export namespace EditableChatRequestModel {
323
+ export function is(request: unknown): request is EditableChatRequestModel {
324
+ return !!(
325
+ ChatRequestModel.is(request) &&
326
+ 'enableEdit' in request &&
327
+ 'cancelEdit' in request &&
328
+ 'submitEdit' in request
329
+ );
330
+ }
331
+
332
+ export function isEditing(request: unknown): request is EditableChatRequestModel {
333
+ return is(request) && request.isEditing;
334
+ }
335
+ }
336
+
183
337
  export interface ChatProgressMessage {
184
338
  kind: 'progressMessage';
185
339
  id: string;
@@ -536,25 +690,44 @@ export class MutableChatModel implements ChatModel, Disposable {
536
690
  protected readonly _onDidChangeEmitter = new Emitter<ChatChangeEvent>();
537
691
  onDidChange: Event<ChatChangeEvent> = this._onDidChangeEmitter.event;
538
692
 
539
- protected _requests: MutableChatRequestModel[];
693
+ protected readonly toDispose = new DisposableCollection();
694
+
695
+ protected _hierarchy: ChatRequestHierarchy<MutableChatRequestModel>;
540
696
  protected _id: string;
541
697
  protected _changeSet?: ChangeSetImpl;
698
+ protected _suggestions: readonly ChatSuggestion[] = [];
542
699
  protected readonly _contextManager = new ChatContextManagerImpl();
543
700
  protected _settings: { [key: string]: unknown };
544
701
 
545
702
  constructor(public readonly location = ChatAgentLocation.Panel) {
546
703
  // TODO accept serialized data as a parameter to restore a previously saved ChatModel
547
- this._requests = [];
704
+ this._hierarchy = new ChatRequestHierarchyImpl<MutableChatRequestModel>();
548
705
  this._id = generateUuid();
549
- this._contextManager.onDidChange(e => this._onDidChangeEmitter.fire(e));
706
+
707
+ this.toDispose.pushAll([
708
+ this._onDidChangeEmitter,
709
+ this._contextManager.onDidChange(e => this._onDidChangeEmitter.fire(e)),
710
+ this._hierarchy.onDidChange(event => this._onDidChangeEmitter.fire({
711
+ kind: 'changeHierarchyBranch',
712
+ branch: event.branch,
713
+ })),
714
+ ]);
715
+ }
716
+
717
+ getBranches(): ChatHierarchyBranch<ChatRequestModel>[] {
718
+ return this._hierarchy.activeBranches();
719
+ }
720
+
721
+ getBranch(requestId: string): ChatHierarchyBranch<ChatRequestModel> | undefined {
722
+ return this._hierarchy.findBranch(requestId);
550
723
  }
551
724
 
552
725
  getRequests(): MutableChatRequestModel[] {
553
- return this._requests;
726
+ return this._hierarchy.activeRequests();
554
727
  }
555
728
 
556
729
  getRequest(id: string): MutableChatRequestModel | undefined {
557
- return this._requests.find(request => request.id === id);
730
+ return this.getRequests().find(request => request.id === id);
558
731
  }
559
732
 
560
733
  get id(): string {
@@ -565,6 +738,10 @@ export class MutableChatModel implements ChatModel, Disposable {
565
738
  return this._changeSet;
566
739
  }
567
740
 
741
+ get suggestions(): readonly ChatSuggestion[] {
742
+ return this._suggestions;
743
+ }
744
+
568
745
  get context(): ChatContextManager {
569
746
  return this._contextManager;
570
747
  }
@@ -610,8 +787,13 @@ export class MutableChatModel implements ChatModel, Disposable {
610
787
  }
611
788
 
612
789
  addRequest(parsedChatRequest: ParsedChatRequest, agentId?: string, context: ChatContext = { variables: [] }): MutableChatRequestModel {
790
+ if (parsedChatRequest.request.referencedRequestId) {
791
+ return this.applyEdit(parsedChatRequest, agentId, context);
792
+ }
793
+
613
794
  const requestModel = new MutableChatRequestModel(this, parsedChatRequest, agentId, context);
614
- this._requests.push(requestModel);
795
+ this.toDispose.push(requestModel);
796
+ this._hierarchy.append(requestModel);
615
797
  this._onDidChangeEmitter.fire({
616
798
  kind: 'addRequest',
617
799
  request: requestModel,
@@ -619,13 +801,217 @@ export class MutableChatModel implements ChatModel, Disposable {
619
801
  return requestModel;
620
802
  }
621
803
 
804
+ setSuggestions(suggestions: ChatSuggestion[]): void {
805
+ this._suggestions = Object.freeze(suggestions);
806
+ this._onDidChangeEmitter.fire({
807
+ kind: 'suggestionsChanged',
808
+ suggestions
809
+ });
810
+ }
811
+
622
812
  isEmpty(): boolean {
623
- return this._requests.length === 0;
813
+ return this.getRequests().length === 0;
814
+ }
815
+
816
+ applyEdit(parsedChatRequest: ParsedChatRequest, agentId?: string, context: ChatContext = { variables: [] }): MutableChatRequestModel {
817
+ const requestId = parsedChatRequest.request.referencedRequestId!;
818
+ const branch = this._hierarchy.findBranch(requestId);
819
+ if (!branch) {
820
+ throw new Error(`Cannot find branch for requestId: ${requestId}`);
821
+ }
822
+
823
+ const requestModel = new MutableChatRequestModel(this, parsedChatRequest, agentId, context);
824
+ this.toDispose.push(requestModel);
825
+ branch.add(requestModel);
826
+ this.removeChangeSet();
827
+
828
+ this._onDidChangeEmitter.fire({
829
+ kind: 'addRequest',
830
+ request: requestModel,
831
+ });
832
+
833
+ return requestModel;
624
834
  }
625
835
 
626
836
  dispose(): void {
627
837
  this.removeChangeSet(); // Signal disposal of last change set.
628
- this._onDidChangeEmitter.dispose();
838
+ this.toDispose.dispose();
839
+ }
840
+
841
+ emit(event: ChatChangeEvent): void {
842
+ this._onDidChangeEmitter.fire(event);
843
+ }
844
+ }
845
+
846
+ export class ChatRequestHierarchyImpl<TRequest extends ChatRequestModel = ChatRequestModel> implements ChatRequestHierarchy<TRequest> {
847
+ protected readonly onDidChangeActiveBranchEmitter = new Emitter<ChangeActiveBranchEvent<TRequest>>();
848
+ readonly onDidChange = this.onDidChangeActiveBranchEmitter.event;
849
+
850
+ readonly branch: ChatHierarchyBranch<TRequest> = new ChatRequestHierarchyBranchImpl<TRequest>(this);
851
+
852
+ append(request: TRequest): ChatHierarchyBranch<TRequest> {
853
+ const branches = this.activeBranches();
854
+
855
+ if (branches.length === 0) {
856
+ this.branch.add(request);
857
+ return this.branch;
858
+ }
859
+
860
+ return branches.at(-1)!.continue(request);
861
+ }
862
+
863
+ activeRequests(): TRequest[] {
864
+ return this.activeBranches().map(h => h.get());
865
+ }
866
+
867
+ activeBranches(): ChatHierarchyBranch<TRequest>[] {
868
+ return Array.from(this.iterateBranches());
869
+ }
870
+
871
+ protected *iterateBranches(): Generator<ChatHierarchyBranch<TRequest>> {
872
+ let current: ChatHierarchyBranch<TRequest> | undefined = this.branch;
873
+ while (current) {
874
+ if (current.items.length > 0) {
875
+ yield current;
876
+ current = current.next();
877
+ } else {
878
+ break;
879
+ }
880
+ }
881
+ }
882
+
883
+ findRequest(requestId: string): TRequest | undefined {
884
+ const branch = this.findInBranch(this.branch, requestId);
885
+ return branch?.items.find(item => item.element.id === requestId)?.element;
886
+ }
887
+
888
+ findBranch(requestId: string): ChatHierarchyBranch<TRequest> | undefined {
889
+ return this.findInBranch(this.branch, requestId);
890
+ }
891
+
892
+ protected findInBranch(branch: ChatHierarchyBranch<TRequest>, requestId: string): ChatHierarchyBranch<TRequest> | undefined {
893
+ for (const item of branch.items) {
894
+ if (item.element.id === requestId) {
895
+ return branch;
896
+ }
897
+ }
898
+ for (const item of branch.items) {
899
+ if (item.next) {
900
+ const found = this.findInBranch(item.next, requestId);
901
+ if (found) {
902
+ return found;
903
+ }
904
+ }
905
+ }
906
+ return undefined;
907
+ }
908
+
909
+ notifyChange(event: ChangeActiveBranchEvent<TRequest>): void {
910
+ this.onDidChangeActiveBranchEmitter.fire(event);
911
+ }
912
+ }
913
+
914
+ export class ChatRequestHierarchyBranchImpl<TRequest extends ChatRequestModel> implements ChatHierarchyBranch<TRequest> {
915
+ readonly id = generateUuid();
916
+
917
+ constructor(
918
+ readonly hierarchy: ChatRequestHierarchy<TRequest>,
919
+ readonly previous?: ChatHierarchyBranch<TRequest>,
920
+ readonly items: ChatHierarchyBranchItem<TRequest>[] = [],
921
+ protected _activeIndex = -1
922
+ ) { }
923
+
924
+ get activeBranchIndex(): number {
925
+ return this._activeIndex;
926
+ }
927
+
928
+ protected set activeBranchIndex(value: number) {
929
+ this._activeIndex = value;
930
+ this.hierarchy.notifyChange({
931
+ branch: this,
932
+ item: this.items[this._activeIndex]
933
+ });
934
+ }
935
+
936
+ next(): ChatHierarchyBranch<TRequest> | undefined {
937
+ return this.items[this.activeBranchIndex]?.next;
938
+ }
939
+
940
+ get(): TRequest {
941
+ return this.items[this.activeBranchIndex].element;
942
+ }
943
+
944
+ add(request: TRequest): void {
945
+ const branch: ChatHierarchyBranchItem<TRequest> = {
946
+ element: request
947
+ };
948
+ this.items.push(branch);
949
+ this.activeBranchIndex = this.items.length - 1;
950
+ }
951
+
952
+ remove(request: TRequest | string): void {
953
+ const requestId = typeof request === 'string' ? request : request.id;
954
+ const index = this.items.findIndex(version => version.element.id === requestId);
955
+ if (index !== -1) {
956
+ this.items.splice(index, 1);
957
+ if (this.activeBranchIndex >= index) {
958
+ this.activeBranchIndex--;
959
+ }
960
+ }
961
+ }
962
+
963
+ continue(request: TRequest): ChatHierarchyBranch<TRequest> {
964
+ if (this.items.length === 0) {
965
+ this.add(request);
966
+ return this;
967
+ }
968
+
969
+ const item = this.items[this.activeBranchIndex];
970
+
971
+ if (item) {
972
+ const next = new ChatRequestHierarchyBranchImpl(this.hierarchy, this, [{ element: request }], 0);
973
+ this.items[this.activeBranchIndex] = {
974
+ ...item,
975
+ next
976
+ };
977
+ return next;
978
+ }
979
+
980
+ throw new Error(`No current branch to continue from. Active Index: ${this.activeBranchIndex}`);
981
+ }
982
+
983
+ enable(request: TRequest): ChatHierarchyBranchItem<TRequest> {
984
+ this.activeBranchIndex = this.items.findIndex(pred => pred.element.id === request.id);
985
+ return this.items[this.activeBranchIndex];
986
+ }
987
+
988
+ enablePrevious(): ChatHierarchyBranchItem<TRequest> {
989
+ if (this.activeBranchIndex > 0) {
990
+ this.activeBranchIndex--;
991
+ return this.items[this.activeBranchIndex];
992
+ }
993
+ return this.items[0];
994
+ }
995
+
996
+ enableNext(): ChatHierarchyBranchItem<TRequest> {
997
+ if (this.activeBranchIndex < this.items.length - 1) {
998
+ this.activeBranchIndex++;
999
+ return this.items[this.activeBranchIndex];
1000
+ }
1001
+
1002
+ return this.items[this.activeBranchIndex];
1003
+ }
1004
+
1005
+ succeedingBranches(): ChatHierarchyBranch<TRequest>[] {
1006
+ const branches: ChatHierarchyBranch<TRequest>[] = [];
1007
+
1008
+ let current: ChatHierarchyBranch<TRequest> | undefined = this;
1009
+ while (current !== undefined) {
1010
+ branches.push(current);
1011
+ current = current.next();
1012
+ }
1013
+
1014
+ return branches;
629
1015
  }
630
1016
  }
631
1017
 
@@ -693,11 +1079,17 @@ export class ChangeSetImpl implements ChangeSet {
693
1079
 
694
1080
  export class ChatContextManagerImpl implements ChatContextManager {
695
1081
  protected readonly variables = new Array<AIVariableResolutionRequest>();
696
- protected readonly onDidChangeEmitter = new Emitter<ChatAddVariableEvent | ChatRemoveVariableEvent>();
697
- get onDidChange(): Event<ChatAddVariableEvent | ChatRemoveVariableEvent> {
1082
+ protected readonly onDidChangeEmitter = new Emitter<ChatAddVariableEvent | ChatRemoveVariableEvent | ChatSetVariablesEvent>();
1083
+ get onDidChange(): Event<ChatAddVariableEvent | ChatRemoveVariableEvent | ChatSetVariablesEvent> {
698
1084
  return this.onDidChangeEmitter.event;
699
1085
  }
700
1086
 
1087
+ constructor(context?: ChatContext) {
1088
+ if (context) {
1089
+ this.variables.push(...context.variables.map(AIVariableResolutionRequest.fromResolved));
1090
+ }
1091
+ }
1092
+
701
1093
  getVariables(): readonly AIVariableResolutionRequest[] {
702
1094
  const result = this.variables.slice();
703
1095
  Object.freeze(result);
@@ -728,6 +1120,17 @@ export class ChatContextManagerImpl implements ChatContextManager {
728
1120
  }
729
1121
  }
730
1122
 
1123
+ setVariables(variables: AIVariableResolutionRequest[]): void {
1124
+ this.variables.length = 0;
1125
+ variables.forEach(variable => {
1126
+ if (this.variables.some(existing => existing.variable.id === variable.variable.id && existing.arg === variable.arg)) {
1127
+ return;
1128
+ }
1129
+ this.variables.push(variable);
1130
+ });
1131
+ this.onDidChangeEmitter.fire({ kind: 'setVariables' });
1132
+ }
1133
+
731
1134
  clear(): void {
732
1135
  if (this.variables.length) {
733
1136
  this.variables.length = 0;
@@ -736,7 +1139,7 @@ export class ChatContextManagerImpl implements ChatContextManager {
736
1139
  }
737
1140
  }
738
1141
 
739
- export class MutableChatRequestModel implements ChatRequestModel {
1142
+ export class MutableChatRequestModel implements ChatRequestModel, EditableChatRequestModel, Disposable {
740
1143
  protected readonly _id: string;
741
1144
  protected _session: MutableChatModel;
742
1145
  protected _request: ChatRequest;
@@ -744,6 +1147,10 @@ export class MutableChatRequestModel implements ChatRequestModel {
744
1147
  protected _context: ChatContext;
745
1148
  protected _agentId?: string;
746
1149
  protected _data: { [key: string]: unknown };
1150
+ protected _isEditing = false;
1151
+
1152
+ protected readonly toDispose = new DisposableCollection();
1153
+ readonly editContextManager: ChatContextManagerImpl;
747
1154
 
748
1155
  constructor(session: MutableChatModel, public readonly message: ParsedChatRequest, agentId?: string,
749
1156
  context: ChatContext = { variables: [] }, data: { [key: string]: unknown } = {}) {
@@ -755,6 +1162,20 @@ export class MutableChatRequestModel implements ChatRequestModel {
755
1162
  this._context = context;
756
1163
  this._agentId = agentId;
757
1164
  this._data = data;
1165
+
1166
+ this.editContextManager = new ChatContextManagerImpl(context);
1167
+ this.toDispose.pushAll([
1168
+ this.editContextManager.onDidChange(e => this.session.emit(e))
1169
+ ]);
1170
+ }
1171
+
1172
+ get isEditing(): boolean {
1173
+ return this._isEditing;
1174
+ }
1175
+
1176
+ enableEdit(): void {
1177
+ this._isEditing = true;
1178
+ this.emitEditRequest(this);
758
1179
  }
759
1180
 
760
1181
  get data(): { [key: string]: unknown } | undefined {
@@ -793,9 +1214,78 @@ export class MutableChatRequestModel implements ChatRequestModel {
793
1214
  return this._agentId;
794
1215
  }
795
1216
 
1217
+ cancelEdit(): void {
1218
+ if (this.isEditing) {
1219
+ this._isEditing = false;
1220
+ this.emitCancelEdit(this);
1221
+
1222
+ this.clearEditContext();
1223
+ }
1224
+ }
1225
+
1226
+ submitEdit(newRequest: ChatRequest): void {
1227
+ if (this.isEditing) {
1228
+ this._isEditing = false;
1229
+ const variables = this.editContextManager.getVariables() ?? [];
1230
+
1231
+ this.emitSubmitEdit(this, {
1232
+ ...newRequest,
1233
+ referencedRequestId: this.id,
1234
+ variables
1235
+ });
1236
+
1237
+ this.clearEditContext();
1238
+ }
1239
+ }
1240
+
796
1241
  cancel(): void {
797
1242
  this.response.cancel();
798
1243
  }
1244
+
1245
+ dispose(): void {
1246
+ this.toDispose.dispose();
1247
+ }
1248
+
1249
+ protected clearEditContext(): void {
1250
+ this.editContextManager.setVariables(this.context.variables.map(AIVariableResolutionRequest.fromResolved));
1251
+ }
1252
+
1253
+ protected emitEditRequest(request: MutableChatRequestModel): void {
1254
+ const branch = this.session.getBranch(request.id);
1255
+ if (!branch) {
1256
+ throw new Error(`Cannot find hierarchy for requestId: ${request.id}`);
1257
+ }
1258
+ this.session.emit({
1259
+ kind: 'enableEdit',
1260
+ request,
1261
+ branch,
1262
+ });
1263
+ }
1264
+
1265
+ protected emitCancelEdit(request: MutableChatRequestModel): void {
1266
+ const branch = this.session.getBranch(request.id);
1267
+ if (!branch) {
1268
+ throw new Error(`Cannot find branch for requestId: ${request.id}`);
1269
+ }
1270
+ this.session.emit({
1271
+ kind: 'cancelEdit',
1272
+ request,
1273
+ branch,
1274
+ });
1275
+ }
1276
+
1277
+ protected emitSubmitEdit(request: MutableChatRequestModel, newRequest: ChatRequest): void {
1278
+ const branch = this.session.getBranch(request.id);
1279
+ if (!branch) {
1280
+ throw new Error(`Cannot find branch for requestId: ${request.id}`);
1281
+ }
1282
+ this.session.emit({
1283
+ kind: 'submitEdit',
1284
+ request,
1285
+ branch,
1286
+ newRequest
1287
+ });
1288
+ }
799
1289
  }
800
1290
 
801
1291
  export class ErrorChatResponseContentImpl implements ErrorChatResponseContent {
@@ -1043,7 +1533,7 @@ export class ToolCallChatResponseContentImpl implements ToolCallChatResponseCont
1043
1533
  actor: 'ai',
1044
1534
  type: 'tool_use',
1045
1535
  id: this.id ?? '',
1046
- input: (this.arguments && JSON.parse(this.arguments)) ?? undefined,
1536
+ input: this.arguments && this.arguments.length !== 0 ? JSON.parse(this.arguments) : {},
1047
1537
  name: this.name ?? ''
1048
1538
  }, {
1049
1539
  actor: 'user',
@@ -1061,8 +1551,7 @@ export const COMMAND_CHAT_RESPONSE_COMMAND: Command = {
1061
1551
  export class CommandChatResponseContentImpl implements CommandChatResponseContent {
1062
1552
  readonly kind = 'command';
1063
1553
 
1064
- constructor(public command?: Command, public customCallback?: CustomCallback, protected args?: unknown[]) {
1065
- }
1554
+ constructor(public command?: Command, public customCallback?: CustomCallback, protected args?: unknown[]) { }
1066
1555
 
1067
1556
  get arguments(): unknown[] {
1068
1557
  return this.args ?? [];