@theia/ai-chat 1.60.2 → 1.61.0

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 (118) 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 +32 -20
  51. package/lib/common/chat-agents.js.map +1 -1
  52. package/lib/common/chat-model.d.ts +191 -8
  53. package/lib/common/chat-model.d.ts.map +1 -1
  54. package/lib/common/chat-model.js +369 -12
  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 +9 -11
  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/lib/common/parse-contents-with-incomplete-parts.spec.d.ts +2 -0
  78. package/lib/common/parse-contents-with-incomplete-parts.spec.d.ts.map +1 -0
  79. package/lib/common/parse-contents-with-incomplete-parts.spec.js +103 -0
  80. package/lib/common/parse-contents-with-incomplete-parts.spec.js.map +1 -0
  81. package/lib/common/parse-contents.d.ts +1 -0
  82. package/lib/common/parse-contents.d.ts.map +1 -1
  83. package/lib/common/parse-contents.js +45 -5
  84. package/lib/common/parse-contents.js.map +1 -1
  85. package/lib/common/parse-contents.spec.d.ts +1 -0
  86. package/lib/common/parse-contents.spec.d.ts.map +1 -1
  87. package/lib/common/parse-contents.spec.js +25 -13
  88. package/lib/common/parse-contents.spec.js.map +1 -1
  89. package/lib/common/response-content-matcher.d.ts +6 -0
  90. package/lib/common/response-content-matcher.d.ts.map +1 -1
  91. package/lib/common/response-content-matcher.js +14 -3
  92. package/lib/common/response-content-matcher.js.map +1 -1
  93. package/package.json +11 -11
  94. package/src/browser/ai-chat-frontend-contribution.ts +49 -0
  95. package/src/browser/ai-chat-frontend-module.ts +25 -4
  96. package/src/browser/change-set-decorator-service.ts +72 -0
  97. package/src/browser/change-set-file-element.ts +18 -13
  98. package/src/browser/change-set-file-resource.ts +1 -138
  99. package/src/browser/change-set-variable.ts +14 -6
  100. package/src/browser/context-file-variable-label-provider.ts +1 -1
  101. package/src/browser/file-chat-variable-contribution.ts +26 -29
  102. package/src/browser/task-context-service.ts +144 -0
  103. package/src/browser/task-context-storage-service.ts +75 -0
  104. package/src/browser/task-context-variable-contribution.ts +93 -0
  105. package/src/browser/task-context-variable-label-provider.ts +67 -0
  106. package/src/browser/task-context-variable.ts +28 -0
  107. package/src/common/chat-agents.ts +38 -22
  108. package/src/common/chat-model.ts +566 -17
  109. package/src/common/chat-request-parser.ts +2 -2
  110. package/src/common/chat-service.ts +17 -26
  111. package/src/common/chat-session-naming-service.ts +13 -15
  112. package/src/common/chat-session-summary-agent-prompt.ts +28 -0
  113. package/src/common/chat-session-summary-agent.ts +42 -0
  114. package/src/common/context-summary-variable.ts +1 -1
  115. package/src/common/parse-contents-with-incomplete-parts.spec.ts +114 -0
  116. package/src/common/parse-contents.spec.ts +24 -12
  117. package/src/common/parse-contents.ts +52 -6
  118. package/src/common/response-content-matcher.ts +21 -3
@@ -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;
@@ -278,6 +432,12 @@ export interface ThinkingChatResponseContent
278
432
  signature: string;
279
433
  }
280
434
 
435
+ export interface ProgressChatResponseContent
436
+ extends Required<ChatResponseContent> {
437
+ kind: 'progress';
438
+ message: string;
439
+ }
440
+
281
441
  export interface Location {
282
442
  uri: URI;
283
443
  position: Position;
@@ -414,6 +574,17 @@ export namespace ThinkingChatResponseContent {
414
574
  }
415
575
  }
416
576
 
577
+ export namespace ProgressChatResponseContent {
578
+ export function is(obj: unknown): obj is ProgressChatResponseContent {
579
+ return (
580
+ ChatResponseContent.is(obj) &&
581
+ obj.kind === 'progress' &&
582
+ 'message' in obj &&
583
+ typeof obj.message === 'string'
584
+ );
585
+ }
586
+ }
587
+
417
588
  export type QuestionResponseHandler = (
418
589
  selectedOption: { text: string, value?: string },
419
590
  ) => void;
@@ -519,25 +690,44 @@ export class MutableChatModel implements ChatModel, Disposable {
519
690
  protected readonly _onDidChangeEmitter = new Emitter<ChatChangeEvent>();
520
691
  onDidChange: Event<ChatChangeEvent> = this._onDidChangeEmitter.event;
521
692
 
522
- protected _requests: MutableChatRequestModel[];
693
+ protected readonly toDispose = new DisposableCollection();
694
+
695
+ protected _hierarchy: ChatRequestHierarchy<MutableChatRequestModel>;
523
696
  protected _id: string;
524
697
  protected _changeSet?: ChangeSetImpl;
698
+ protected _suggestions: readonly ChatSuggestion[] = [];
525
699
  protected readonly _contextManager = new ChatContextManagerImpl();
526
700
  protected _settings: { [key: string]: unknown };
527
701
 
528
702
  constructor(public readonly location = ChatAgentLocation.Panel) {
529
703
  // TODO accept serialized data as a parameter to restore a previously saved ChatModel
530
- this._requests = [];
704
+ this._hierarchy = new ChatRequestHierarchyImpl<MutableChatRequestModel>();
531
705
  this._id = generateUuid();
532
- 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);
533
723
  }
534
724
 
535
725
  getRequests(): MutableChatRequestModel[] {
536
- return this._requests;
726
+ return this._hierarchy.activeRequests();
537
727
  }
538
728
 
539
729
  getRequest(id: string): MutableChatRequestModel | undefined {
540
- return this._requests.find(request => request.id === id);
730
+ return this.getRequests().find(request => request.id === id);
541
731
  }
542
732
 
543
733
  get id(): string {
@@ -548,6 +738,10 @@ export class MutableChatModel implements ChatModel, Disposable {
548
738
  return this._changeSet;
549
739
  }
550
740
 
741
+ get suggestions(): readonly ChatSuggestion[] {
742
+ return this._suggestions;
743
+ }
744
+
551
745
  get context(): ChatContextManager {
552
746
  return this._contextManager;
553
747
  }
@@ -593,8 +787,13 @@ export class MutableChatModel implements ChatModel, Disposable {
593
787
  }
594
788
 
595
789
  addRequest(parsedChatRequest: ParsedChatRequest, agentId?: string, context: ChatContext = { variables: [] }): MutableChatRequestModel {
790
+ if (parsedChatRequest.request.referencedRequestId) {
791
+ return this.applyEdit(parsedChatRequest, agentId, context);
792
+ }
793
+
596
794
  const requestModel = new MutableChatRequestModel(this, parsedChatRequest, agentId, context);
597
- this._requests.push(requestModel);
795
+ this.toDispose.push(requestModel);
796
+ this._hierarchy.append(requestModel);
598
797
  this._onDidChangeEmitter.fire({
599
798
  kind: 'addRequest',
600
799
  request: requestModel,
@@ -602,13 +801,217 @@ export class MutableChatModel implements ChatModel, Disposable {
602
801
  return requestModel;
603
802
  }
604
803
 
804
+ setSuggestions(suggestions: ChatSuggestion[]): void {
805
+ this._suggestions = Object.freeze(suggestions);
806
+ this._onDidChangeEmitter.fire({
807
+ kind: 'suggestionsChanged',
808
+ suggestions
809
+ });
810
+ }
811
+
605
812
  isEmpty(): boolean {
606
- 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;
607
834
  }
608
835
 
609
836
  dispose(): void {
610
837
  this.removeChangeSet(); // Signal disposal of last change set.
611
- 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;
612
1015
  }
613
1016
  }
614
1017
 
@@ -676,11 +1079,17 @@ export class ChangeSetImpl implements ChangeSet {
676
1079
 
677
1080
  export class ChatContextManagerImpl implements ChatContextManager {
678
1081
  protected readonly variables = new Array<AIVariableResolutionRequest>();
679
- protected readonly onDidChangeEmitter = new Emitter<ChatAddVariableEvent | ChatRemoveVariableEvent>();
680
- get onDidChange(): Event<ChatAddVariableEvent | ChatRemoveVariableEvent> {
1082
+ protected readonly onDidChangeEmitter = new Emitter<ChatAddVariableEvent | ChatRemoveVariableEvent | ChatSetVariablesEvent>();
1083
+ get onDidChange(): Event<ChatAddVariableEvent | ChatRemoveVariableEvent | ChatSetVariablesEvent> {
681
1084
  return this.onDidChangeEmitter.event;
682
1085
  }
683
1086
 
1087
+ constructor(context?: ChatContext) {
1088
+ if (context) {
1089
+ this.variables.push(...context.variables.map(AIVariableResolutionRequest.fromResolved));
1090
+ }
1091
+ }
1092
+
684
1093
  getVariables(): readonly AIVariableResolutionRequest[] {
685
1094
  const result = this.variables.slice();
686
1095
  Object.freeze(result);
@@ -711,6 +1120,17 @@ export class ChatContextManagerImpl implements ChatContextManager {
711
1120
  }
712
1121
  }
713
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
+
714
1134
  clear(): void {
715
1135
  if (this.variables.length) {
716
1136
  this.variables.length = 0;
@@ -719,7 +1139,7 @@ export class ChatContextManagerImpl implements ChatContextManager {
719
1139
  }
720
1140
  }
721
1141
 
722
- export class MutableChatRequestModel implements ChatRequestModel {
1142
+ export class MutableChatRequestModel implements ChatRequestModel, EditableChatRequestModel, Disposable {
723
1143
  protected readonly _id: string;
724
1144
  protected _session: MutableChatModel;
725
1145
  protected _request: ChatRequest;
@@ -727,6 +1147,10 @@ export class MutableChatRequestModel implements ChatRequestModel {
727
1147
  protected _context: ChatContext;
728
1148
  protected _agentId?: string;
729
1149
  protected _data: { [key: string]: unknown };
1150
+ protected _isEditing = false;
1151
+
1152
+ protected readonly toDispose = new DisposableCollection();
1153
+ readonly editContextManager: ChatContextManagerImpl;
730
1154
 
731
1155
  constructor(session: MutableChatModel, public readonly message: ParsedChatRequest, agentId?: string,
732
1156
  context: ChatContext = { variables: [] }, data: { [key: string]: unknown } = {}) {
@@ -738,6 +1162,20 @@ export class MutableChatRequestModel implements ChatRequestModel {
738
1162
  this._context = context;
739
1163
  this._agentId = agentId;
740
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);
741
1179
  }
742
1180
 
743
1181
  get data(): { [key: string]: unknown } | undefined {
@@ -776,9 +1214,78 @@ export class MutableChatRequestModel implements ChatRequestModel {
776
1214
  return this._agentId;
777
1215
  }
778
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
+
779
1241
  cancel(): void {
780
1242
  this.response.cancel();
781
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
+ }
782
1289
  }
783
1290
 
784
1291
  export class ErrorChatResponseContentImpl implements ErrorChatResponseContent {
@@ -1044,8 +1551,7 @@ export const COMMAND_CHAT_RESPONSE_COMMAND: Command = {
1044
1551
  export class CommandChatResponseContentImpl implements CommandChatResponseContent {
1045
1552
  readonly kind = 'command';
1046
1553
 
1047
- constructor(public command?: Command, public customCallback?: CustomCallback, protected args?: unknown[]) {
1048
- }
1554
+ constructor(public command?: Command, public customCallback?: CustomCallback, protected args?: unknown[]) { }
1049
1555
 
1050
1556
  get arguments(): unknown[] {
1051
1557
  return this.args ?? [];
@@ -1127,6 +1633,12 @@ class ChatResponseImpl implements ChatResponse {
1127
1633
  return this._content;
1128
1634
  }
1129
1635
 
1636
+ clearContent(): void {
1637
+ this._content = [];
1638
+ this._updateResponseRepresentation();
1639
+ this._onDidChangeEmitter.fire();
1640
+ }
1641
+
1130
1642
  addContents(contents: ChatResponseContent[]): void {
1131
1643
  contents.forEach(c => this.doAddContent(c));
1132
1644
  this._onDidChangeEmitter.fire();
@@ -1353,3 +1865,40 @@ export class ErrorChatResponseModel extends MutableChatResponseModel {
1353
1865
  this.error(error);
1354
1866
  }
1355
1867
  }
1868
+
1869
+ export class ProgressChatResponseContentImpl implements ProgressChatResponseContent {
1870
+ readonly kind = 'progress';
1871
+ protected _message: string;
1872
+
1873
+ constructor(message: string) {
1874
+ this._message = message;
1875
+ }
1876
+
1877
+ get message(): string {
1878
+ return this._message;
1879
+ }
1880
+
1881
+ asString(): string {
1882
+ return JSON.stringify({
1883
+ type: 'progress',
1884
+ message: this.message
1885
+ });
1886
+ }
1887
+
1888
+ asDisplayString(): string | undefined {
1889
+ return `<Progress>${this.message}</Progress>`;
1890
+ }
1891
+
1892
+ merge(nextChatResponseContent: ProgressChatResponseContent): boolean {
1893
+ this._message = nextChatResponseContent.message;
1894
+ return true;
1895
+ }
1896
+
1897
+ toLanguageModelMessage(): TextMessage {
1898
+ return {
1899
+ actor: 'ai',
1900
+ type: 'text',
1901
+ text: this.message
1902
+ };
1903
+ }
1904
+ }