@theia/ai-chat 1.54.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 (77) hide show
  1. package/README.md +30 -0
  2. package/lib/browser/ai-chat-frontend-module.d.ts +4 -0
  3. package/lib/browser/ai-chat-frontend-module.d.ts.map +1 -0
  4. package/lib/browser/ai-chat-frontend-module.js +51 -0
  5. package/lib/browser/ai-chat-frontend-module.js.map +1 -0
  6. package/lib/browser/ai-chat-preferences.d.ts +4 -0
  7. package/lib/browser/ai-chat-preferences.d.ts.map +1 -0
  8. package/lib/browser/ai-chat-preferences.js +32 -0
  9. package/lib/browser/ai-chat-preferences.js.map +1 -0
  10. package/lib/browser/frontend-chat-service.d.ts +8 -0
  11. package/lib/browser/frontend-chat-service.d.ts.map +1 -0
  12. package/lib/browser/frontend-chat-service.js +67 -0
  13. package/lib/browser/frontend-chat-service.js.map +1 -0
  14. package/lib/common/chat-agent-service.d.ts +34 -0
  15. package/lib/common/chat-agent-service.d.ts.map +1 -0
  16. package/lib/common/chat-agent-service.js +75 -0
  17. package/lib/common/chat-agent-service.js.map +1 -0
  18. package/lib/common/chat-agents-variable-contribution.d.ts +17 -0
  19. package/lib/common/chat-agents-variable-contribution.d.ts.map +1 -0
  20. package/lib/common/chat-agents-variable-contribution.js +51 -0
  21. package/lib/common/chat-agents-variable-contribution.js.map +1 -0
  22. package/lib/common/chat-agents.d.ts +86 -0
  23. package/lib/common/chat-agents.d.ts.map +1 -0
  24. package/lib/common/chat-agents.js +307 -0
  25. package/lib/common/chat-agents.js.map +1 -0
  26. package/lib/common/chat-model.d.ts +319 -0
  27. package/lib/common/chat-model.d.ts.map +1 -0
  28. package/lib/common/chat-model.js +527 -0
  29. package/lib/common/chat-model.js.map +1 -0
  30. package/lib/common/chat-request-parser.d.ts +20 -0
  31. package/lib/common/chat-request-parser.d.ts.map +1 -0
  32. package/lib/common/chat-request-parser.js +158 -0
  33. package/lib/common/chat-request-parser.js.map +1 -0
  34. package/lib/common/chat-request-parser.spec.d.ts +2 -0
  35. package/lib/common/chat-request-parser.spec.d.ts.map +1 -0
  36. package/lib/common/chat-request-parser.spec.js +108 -0
  37. package/lib/common/chat-request-parser.spec.js.map +1 -0
  38. package/lib/common/chat-service.d.ts +72 -0
  39. package/lib/common/chat-service.d.ts.map +1 -0
  40. package/lib/common/chat-service.js +170 -0
  41. package/lib/common/chat-service.js.map +1 -0
  42. package/lib/common/command-chat-agents.d.ts +33 -0
  43. package/lib/common/command-chat-agents.d.ts.map +1 -0
  44. package/lib/common/command-chat-agents.js +327 -0
  45. package/lib/common/command-chat-agents.js.map +1 -0
  46. package/lib/common/index.d.ts +10 -0
  47. package/lib/common/index.d.ts.map +1 -0
  48. package/lib/common/index.js +28 -0
  49. package/lib/common/index.js.map +1 -0
  50. package/lib/common/orchestrator-chat-agent.d.ts +22 -0
  51. package/lib/common/orchestrator-chat-agent.d.ts.map +1 -0
  52. package/lib/common/orchestrator-chat-agent.js +140 -0
  53. package/lib/common/orchestrator-chat-agent.js.map +1 -0
  54. package/lib/common/parsed-chat-request.d.ts +66 -0
  55. package/lib/common/parsed-chat-request.d.ts.map +1 -0
  56. package/lib/common/parsed-chat-request.js +83 -0
  57. package/lib/common/parsed-chat-request.js.map +1 -0
  58. package/lib/common/universal-chat-agent.d.ts +15 -0
  59. package/lib/common/universal-chat-agent.d.ts.map +1 -0
  60. package/lib/common/universal-chat-agent.js +102 -0
  61. package/lib/common/universal-chat-agent.js.map +1 -0
  62. package/package.json +54 -0
  63. package/src/browser/ai-chat-frontend-module.ts +66 -0
  64. package/src/browser/ai-chat-preferences.ts +32 -0
  65. package/src/browser/frontend-chat-service.ts +66 -0
  66. package/src/common/chat-agent-service.ts +85 -0
  67. package/src/common/chat-agents-variable-contribution.ts +81 -0
  68. package/src/common/chat-agents.ts +384 -0
  69. package/src/common/chat-model.ts +776 -0
  70. package/src/common/chat-request-parser.spec.ts +120 -0
  71. package/src/common/chat-request-parser.ts +220 -0
  72. package/src/common/chat-service.ts +236 -0
  73. package/src/common/command-chat-agents.ts +352 -0
  74. package/src/common/index.ts +24 -0
  75. package/src/common/orchestrator-chat-agent.ts +151 -0
  76. package/src/common/parsed-chat-request.ts +112 -0
  77. package/src/common/universal-chat-agent.ts +109 -0
@@ -0,0 +1,776 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2024 EclipseSource GmbH.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+ /*---------------------------------------------------------------------------------------------
17
+ * Copyright (c) Microsoft Corporation. All rights reserved.
18
+ * Licensed under the MIT License. See License.txt in the project root for license information.
19
+ *--------------------------------------------------------------------------------------------*/
20
+ // Partially copied from https://github.com/microsoft/vscode/blob/a2cab7255c0df424027be05d58e1b7b941f4ea60/src/vs/workbench/contrib/chat/common/chatModel.ts
21
+
22
+ import { Command, Emitter, Event, generateUuid, URI } from '@theia/core';
23
+ import { MarkdownString, MarkdownStringImpl } from '@theia/core/lib/common/markdown-rendering';
24
+ import { Position } from '@theia/core/shared/vscode-languageserver-protocol';
25
+ import { ChatAgentLocation } from './chat-agents';
26
+ import { ParsedChatRequest } from './parsed-chat-request';
27
+
28
+ /**********************
29
+ * INTERFACES AND TYPE GUARDS
30
+ **********************/
31
+
32
+ export type ChatChangeEvent =
33
+ | ChatAddRequestEvent
34
+ | ChatAddResponseEvent
35
+ | ChatRemoveRequestEvent;
36
+
37
+ export interface ChatAddRequestEvent {
38
+ kind: 'addRequest';
39
+ request: ChatRequestModel;
40
+ }
41
+
42
+ export interface ChatAddResponseEvent {
43
+ kind: 'addResponse';
44
+ response: ChatResponseModel;
45
+ }
46
+
47
+ export type ChatRequestRemovalReason = 'removal' | 'resend' | 'adoption';
48
+
49
+ export interface ChatRemoveRequestEvent {
50
+ kind: 'removeRequest';
51
+ requestId: string;
52
+ responseId?: string;
53
+ reason: ChatRequestRemovalReason;
54
+ }
55
+
56
+ export interface ChatModel {
57
+ readonly onDidChange: Event<ChatChangeEvent>;
58
+ readonly id: string;
59
+ readonly location: ChatAgentLocation;
60
+ getRequests(): ChatRequestModel[];
61
+ isEmpty(): boolean;
62
+ }
63
+
64
+ export interface ChatRequest {
65
+ readonly text: string;
66
+ readonly displayText?: string;
67
+ }
68
+
69
+ export interface ChatRequestModel {
70
+ readonly id: string;
71
+ readonly session: ChatModel;
72
+ readonly request: ChatRequest;
73
+ readonly response: ChatResponseModel;
74
+ readonly message: ParsedChatRequest;
75
+ readonly agentId?: string;
76
+ }
77
+
78
+ export interface ChatProgressMessage {
79
+ kind: 'progressMessage';
80
+ id: string;
81
+ status: 'inProgress' | 'completed' | 'failed';
82
+ content: string;
83
+ }
84
+
85
+ export interface ChatResponseContent {
86
+ kind: string;
87
+ /**
88
+ * Represents the content as a string. Returns `undefined` if the content
89
+ * is purely informational and/or visual and should not be included in the overall
90
+ * representation of the response.
91
+ */
92
+ asString?(): string | undefined;
93
+ merge?(nextChatResponseContent: ChatResponseContent): boolean;
94
+ }
95
+
96
+ export namespace ChatResponseContent {
97
+ export function is(obj: unknown): obj is ChatResponseContent {
98
+ return !!(
99
+ obj &&
100
+ typeof obj === 'object' &&
101
+ 'kind' in obj &&
102
+ typeof (obj as { kind: unknown }).kind === 'string'
103
+ );
104
+ }
105
+ export function hasAsString(
106
+ obj: ChatResponseContent
107
+ ): obj is Required<Pick<ChatResponseContent, 'asString'>> & ChatResponseContent {
108
+ return typeof obj.asString === 'function';
109
+ }
110
+ export function hasMerge(
111
+ obj: ChatResponseContent
112
+ ): obj is Required<Pick<ChatResponseContent, 'merge'>> & ChatResponseContent {
113
+ return typeof obj.merge === 'function';
114
+ }
115
+ }
116
+
117
+ export interface TextChatResponseContent
118
+ extends Required<ChatResponseContent> {
119
+ kind: 'text';
120
+ content: string;
121
+ }
122
+
123
+ export interface ErrorChatResponseContent extends ChatResponseContent {
124
+ kind: 'error';
125
+ error: Error;
126
+ }
127
+
128
+ export interface MarkdownChatResponseContent
129
+ extends Required<ChatResponseContent> {
130
+ kind: 'markdownContent';
131
+ content: MarkdownString;
132
+ }
133
+
134
+ export interface CodeChatResponseContent
135
+ extends ChatResponseContent {
136
+ kind: 'code';
137
+ code: string;
138
+ language?: string;
139
+ location?: Location;
140
+ }
141
+
142
+ export interface HorizontalLayoutChatResponseContent extends Required<ChatResponseContent> {
143
+ kind: 'horizontal';
144
+ content: ChatResponseContent[];
145
+ }
146
+
147
+ export interface ToolCallChatResponseContent extends Required<ChatResponseContent> {
148
+ kind: 'toolCall';
149
+ id?: string;
150
+ name?: string;
151
+ arguments?: string;
152
+ finished: boolean;
153
+ result?: string;
154
+ }
155
+
156
+ export interface Location {
157
+ uri: URI;
158
+ position: Position;
159
+ }
160
+ export namespace Location {
161
+ export function is(obj: unknown): obj is Location {
162
+ return !!obj && typeof obj === 'object' &&
163
+ 'uri' in obj && (obj as { uri: unknown }).uri instanceof URI &&
164
+ 'position' in obj && Position.is((obj as { position: unknown }).position);
165
+ }
166
+ }
167
+
168
+ export interface CustomCallback {
169
+ label: string;
170
+ callback: () => Promise<void>;
171
+ }
172
+
173
+ /**
174
+ * A command chat response content represents a command that is offered to the user for execution.
175
+ * It either refers to an already registered Theia command or provides a custom callback.
176
+ * If both are given, the custom callback will be preferred.
177
+ */
178
+ export interface CommandChatResponseContent extends ChatResponseContent {
179
+ kind: 'command';
180
+ command?: Command;
181
+ customCallback?: CustomCallback;
182
+ arguments?: unknown[];
183
+ }
184
+
185
+ /**
186
+ * An informational chat response content represents a message that is purely informational and should not be included in the overall representation of the response.
187
+ */
188
+ export interface InformationalChatResponseContent extends ChatResponseContent {
189
+ kind: 'informational';
190
+ content: MarkdownString;
191
+ }
192
+
193
+ export namespace TextChatResponseContent {
194
+ export function is(obj: unknown): obj is TextChatResponseContent {
195
+ return (
196
+ ChatResponseContent.is(obj) &&
197
+ obj.kind === 'text' &&
198
+ 'content' in obj &&
199
+ typeof (obj as { content: unknown }).content === 'string'
200
+ );
201
+ }
202
+ }
203
+
204
+ export namespace MarkdownChatResponseContent {
205
+ export function is(obj: unknown): obj is MarkdownChatResponseContent {
206
+ return (
207
+ ChatResponseContent.is(obj) &&
208
+ obj.kind === 'markdownContent' &&
209
+ 'content' in obj &&
210
+ MarkdownString.is((obj as { content: unknown }).content)
211
+ );
212
+ }
213
+ }
214
+
215
+ export namespace InformationalChatResponseContent {
216
+ export function is(obj: unknown): obj is InformationalChatResponseContent {
217
+ return (
218
+ ChatResponseContent.is(obj) &&
219
+ obj.kind === 'informational' &&
220
+ 'content' in obj &&
221
+ MarkdownString.is((obj as { content: unknown }).content)
222
+ );
223
+ }
224
+ }
225
+
226
+ export namespace CommandChatResponseContent {
227
+ export function is(obj: unknown): obj is CommandChatResponseContent {
228
+ return (
229
+ ChatResponseContent.is(obj) &&
230
+ obj.kind === 'command' &&
231
+ 'command' in obj &&
232
+ Command.is((obj as { command: unknown }).command)
233
+ );
234
+ }
235
+ }
236
+
237
+ export namespace CodeChatResponseContent {
238
+ export function is(obj: unknown): obj is CodeChatResponseContent {
239
+ return (
240
+ ChatResponseContent.is(obj) &&
241
+ obj.kind === 'code' &&
242
+ 'code' in obj &&
243
+ typeof (obj as { code: unknown }).code === 'string'
244
+ );
245
+ }
246
+ }
247
+
248
+ export namespace HorizontalLayoutChatResponseContent {
249
+ export function is(
250
+ obj: unknown
251
+ ): obj is HorizontalLayoutChatResponseContent {
252
+ return (
253
+ ChatResponseContent.is(obj) &&
254
+ obj.kind === 'horizontal' &&
255
+ 'content' in obj &&
256
+ Array.isArray((obj as { content: unknown }).content) &&
257
+ (obj as { content: unknown[] }).content.every(
258
+ ChatResponseContent.is
259
+ )
260
+ );
261
+ }
262
+ }
263
+
264
+ export namespace ToolCallChatResponseContent {
265
+ export function is(obj: unknown): obj is ToolCallChatResponseContent {
266
+ return ChatResponseContent.is(obj) && obj.kind === 'toolCall';
267
+ }
268
+ }
269
+
270
+ export namespace ErrorChatResponseContent {
271
+ export function is(obj: unknown): obj is ErrorChatResponseContent {
272
+ return (
273
+ ChatResponseContent.is(obj) &&
274
+ obj.kind === 'error' &&
275
+ 'error' in obj &&
276
+ obj.error instanceof Error
277
+ );
278
+ }
279
+ }
280
+
281
+ export interface ChatResponse {
282
+ readonly content: ChatResponseContent[];
283
+ asString(): string;
284
+ }
285
+
286
+ export interface ChatResponseModel {
287
+ readonly onDidChange: Event<void>;
288
+ readonly id: string;
289
+ readonly requestId: string;
290
+ readonly progressMessages: ChatProgressMessage[];
291
+ readonly response: ChatResponse;
292
+ readonly isComplete: boolean;
293
+ readonly isCanceled: boolean;
294
+ readonly isError: boolean;
295
+ readonly agentId?: string
296
+ readonly errorObject?: Error;
297
+ }
298
+
299
+ /**********************
300
+ * Implementations
301
+ **********************/
302
+
303
+ export class ChatModelImpl implements ChatModel {
304
+ protected readonly _onDidChangeEmitter = new Emitter<ChatChangeEvent>();
305
+ onDidChange: Event<ChatChangeEvent> = this._onDidChangeEmitter.event;
306
+
307
+ protected _requests: ChatRequestModelImpl[];
308
+ protected _id: string;
309
+
310
+ constructor(public readonly location = ChatAgentLocation.Panel) {
311
+ // TODO accept serialized data as a parameter to restore a previously saved ChatModel
312
+ this._requests = [];
313
+ this._id = generateUuid();
314
+ }
315
+
316
+ getRequests(): ChatRequestModelImpl[] {
317
+ return this._requests;
318
+ }
319
+
320
+ get id(): string {
321
+ return this._id;
322
+ }
323
+
324
+ addRequest(parsedChatRequest: ParsedChatRequest, agentId?: string): ChatRequestModelImpl {
325
+ const requestModel = new ChatRequestModelImpl(this, parsedChatRequest, agentId);
326
+ this._requests.push(requestModel);
327
+ this._onDidChangeEmitter.fire({
328
+ kind: 'addRequest',
329
+ request: requestModel,
330
+ });
331
+ return requestModel;
332
+ }
333
+
334
+ isEmpty(): boolean {
335
+ return this._requests.length === 0;
336
+ }
337
+ }
338
+
339
+ export class ChatRequestModelImpl implements ChatRequestModel {
340
+ protected readonly _id: string;
341
+ protected _session: ChatModel;
342
+ protected _request: ChatRequest;
343
+ protected _response: ChatResponseModelImpl;
344
+ protected _agentId?: string;
345
+
346
+ constructor(session: ChatModel, public readonly message: ParsedChatRequest, agentId?: string) {
347
+ // TODO accept serialized data as a parameter to restore a previously saved ChatRequestModel
348
+ this._request = message.request;
349
+ this._id = generateUuid();
350
+ this._session = session;
351
+ this._response = new ChatResponseModelImpl(this._id, agentId);
352
+ this._agentId = agentId;
353
+ }
354
+
355
+ get id(): string {
356
+ return this._id;
357
+ }
358
+
359
+ get session(): ChatModel {
360
+ return this._session;
361
+ }
362
+
363
+ get request(): ChatRequest {
364
+ return this._request;
365
+ }
366
+
367
+ get response(): ChatResponseModelImpl {
368
+ return this._response;
369
+ }
370
+
371
+ get agentId(): string | undefined {
372
+ return this._agentId;
373
+ }
374
+ }
375
+
376
+ export class ErrorChatResponseContentImpl implements ErrorChatResponseContent {
377
+ readonly kind = 'error';
378
+ protected _error: Error;
379
+ constructor(error: Error) {
380
+ this._error = error;
381
+ }
382
+ get error(): Error {
383
+ return this._error;
384
+ }
385
+ asString(): string | undefined {
386
+ return undefined;
387
+ }
388
+ }
389
+
390
+ export class TextChatResponseContentImpl implements TextChatResponseContent {
391
+ readonly kind = 'text';
392
+ protected _content: string;
393
+
394
+ constructor(content: string) {
395
+ this._content = content;
396
+ }
397
+
398
+ get content(): string {
399
+ return this._content;
400
+ }
401
+
402
+ asString(): string {
403
+ return this._content;
404
+ }
405
+
406
+ merge(nextChatResponseContent: TextChatResponseContent): boolean {
407
+ this._content += nextChatResponseContent.content;
408
+ return true;
409
+ }
410
+ }
411
+
412
+ export class MarkdownChatResponseContentImpl implements MarkdownChatResponseContent {
413
+ readonly kind = 'markdownContent';
414
+ protected _content: MarkdownStringImpl = new MarkdownStringImpl();
415
+
416
+ constructor(content: string) {
417
+ this._content.appendMarkdown(content);
418
+ }
419
+
420
+ get content(): MarkdownString {
421
+ return this._content;
422
+ }
423
+
424
+ asString(): string {
425
+ return this._content.value;
426
+ }
427
+
428
+ merge(nextChatResponseContent: MarkdownChatResponseContent): boolean {
429
+ this._content.appendMarkdown(nextChatResponseContent.content.value);
430
+ return true;
431
+ }
432
+ }
433
+
434
+ export class InformationalChatResponseContentImpl implements InformationalChatResponseContent {
435
+ readonly kind = 'informational';
436
+ protected _content: MarkdownStringImpl;
437
+
438
+ constructor(content: string) {
439
+ this._content = new MarkdownStringImpl(content);
440
+ }
441
+
442
+ get content(): MarkdownString {
443
+ return this._content;
444
+ }
445
+
446
+ asString(): string | undefined {
447
+ return undefined;
448
+ }
449
+
450
+ merge(nextChatResponseContent: InformationalChatResponseContent): boolean {
451
+ this._content.appendMarkdown(nextChatResponseContent.content.value);
452
+ return true;
453
+ }
454
+ }
455
+
456
+ export class CodeChatResponseContentImpl implements CodeChatResponseContent {
457
+ readonly kind = 'code';
458
+ protected _code: string;
459
+ protected _language?: string;
460
+ protected _location?: Location;
461
+
462
+ constructor(code: string, language?: string, location?: Location) {
463
+ this._code = code;
464
+ this._language = language;
465
+ this._location = location;
466
+ }
467
+
468
+ get code(): string {
469
+ return this._code;
470
+ }
471
+
472
+ get language(): string | undefined {
473
+ return this._language;
474
+ }
475
+
476
+ get location(): Location | undefined {
477
+ return this._location;
478
+ }
479
+
480
+ asString(): string {
481
+ return `\`\`\`${this._language ?? ''}\n${this._code}\n\`\`\``;
482
+ }
483
+
484
+ merge(nextChatResponseContent: CodeChatResponseContent): boolean {
485
+ this._code += `${nextChatResponseContent.code}`;
486
+ return true;
487
+ }
488
+ }
489
+
490
+ export class ToolCallChatResponseContentImpl implements ToolCallChatResponseContent {
491
+ readonly kind = 'toolCall';
492
+ protected _id?: string;
493
+ protected _name?: string;
494
+ protected _arguments?: string;
495
+ protected _finished?: boolean;
496
+ protected _result?: string;
497
+
498
+ constructor(id?: string, name?: string, arg_string?: string, finished?: boolean, result?: string) {
499
+ this._id = id;
500
+ this._name = name;
501
+ this._arguments = arg_string;
502
+ this._finished = finished;
503
+ this._result = result;
504
+ }
505
+
506
+ get id(): string | undefined {
507
+ return this._id;
508
+ }
509
+
510
+ get name(): string | undefined {
511
+ return this._name;
512
+ }
513
+
514
+ get arguments(): string | undefined {
515
+ return this._arguments;
516
+ }
517
+
518
+ get finished(): boolean {
519
+ return this._finished === undefined ? false : this._finished;
520
+ }
521
+ get result(): string | undefined {
522
+ return this._result;
523
+ }
524
+
525
+ asString(): string {
526
+ return `Tool call: ${this._name}(${this._arguments ?? ''})`;
527
+ }
528
+ merge(nextChatResponseContent: ToolCallChatResponseContent): boolean {
529
+ if (nextChatResponseContent.id === this.id) {
530
+ this._finished = nextChatResponseContent.finished;
531
+ this._result = nextChatResponseContent.result;
532
+ return true;
533
+ }
534
+ if (nextChatResponseContent.name !== undefined) {
535
+ return false;
536
+ }
537
+ if (nextChatResponseContent.arguments === undefined) {
538
+ return false;
539
+ }
540
+ this._arguments += `${nextChatResponseContent.arguments}`;
541
+ return true;
542
+ }
543
+ }
544
+
545
+ export const COMMAND_CHAT_RESPONSE_COMMAND: Command = {
546
+ id: 'ai-chat.command-chat-response.generic'
547
+ };
548
+ export class CommandChatResponseContentImpl implements CommandChatResponseContent {
549
+ readonly kind = 'command';
550
+
551
+ constructor(public command?: Command, public customCallback?: CustomCallback, protected args?: unknown[]) {
552
+ }
553
+
554
+ get arguments(): unknown[] {
555
+ return this.args ?? [];
556
+ }
557
+
558
+ asString(): string {
559
+ return this.command?.id || this.customCallback?.label || 'command';
560
+ }
561
+ }
562
+
563
+ export class HorizontalLayoutChatResponseContentImpl implements HorizontalLayoutChatResponseContent {
564
+ readonly kind = 'horizontal';
565
+ protected _content: ChatResponseContent[];
566
+
567
+ constructor(content: ChatResponseContent[] = []) {
568
+ this._content = content;
569
+ }
570
+
571
+ get content(): ChatResponseContent[] {
572
+ return this._content;
573
+ }
574
+
575
+ asString(): string {
576
+ return this._content.map(child => child.asString && child.asString()).join(' ');
577
+ }
578
+
579
+ merge(nextChatResponseContent: ChatResponseContent): boolean {
580
+ if (HorizontalLayoutChatResponseContent.is(nextChatResponseContent)) {
581
+ this._content.push(...nextChatResponseContent.content);
582
+ } else {
583
+ this._content.push(nextChatResponseContent);
584
+ }
585
+ return true;
586
+ }
587
+ }
588
+
589
+ class ChatResponseImpl implements ChatResponse {
590
+ protected readonly _onDidChangeEmitter = new Emitter<void>();
591
+ onDidChange: Event<void> = this._onDidChangeEmitter.event;
592
+ protected _content: ChatResponseContent[];
593
+ protected _responseRepresentation: string;
594
+
595
+ constructor() {
596
+ // TODO accept serialized data as a parameter to restore a previously saved ChatResponse
597
+ this._content = [];
598
+ }
599
+
600
+ get content(): ChatResponseContent[] {
601
+ return this._content;
602
+ }
603
+
604
+ addContent(nextContent: ChatResponseContent): void {
605
+ // TODO: Support more complex merges affecting different content than the last, e.g. via some kind of ProcessorRegistry
606
+ // TODO: Support more of the built-in VS Code behavior, see
607
+ // https://github.com/microsoft/vscode/blob/a2cab7255c0df424027be05d58e1b7b941f4ea60/src/vs/workbench/contrib/chat/common/chatModel.ts#L188-L244
608
+ if (ToolCallChatResponseContent.is(nextContent) && nextContent.id !== undefined) {
609
+ const fittingTool = this._content.find(c => ToolCallChatResponseContent.is(c) && c.id === nextContent.id);
610
+ if (fittingTool !== undefined) {
611
+ fittingTool.merge?.(nextContent);
612
+ } else {
613
+ this._content.push(nextContent);
614
+ }
615
+ } else {
616
+ const lastElement =
617
+ this._content.length > 0
618
+ ? this._content[this._content.length - 1]
619
+ : undefined;
620
+ if (lastElement?.kind === nextContent.kind && ChatResponseContent.hasMerge(lastElement)) {
621
+ const mergeSuccess = lastElement.merge(nextContent);
622
+ if (!mergeSuccess) {
623
+ this._content.push(nextContent);
624
+ }
625
+ } else {
626
+ this._content.push(nextContent);
627
+ }
628
+ }
629
+ this._updateResponseRepresentation();
630
+ this._onDidChangeEmitter.fire();
631
+ }
632
+
633
+ protected _updateResponseRepresentation(): void {
634
+ this._responseRepresentation = this._content
635
+ .map(responseContent => {
636
+ if (ChatResponseContent.hasAsString(responseContent)) {
637
+ return responseContent.asString();
638
+ }
639
+ if (TextChatResponseContent.is(responseContent)) {
640
+ return responseContent.content;
641
+ }
642
+ console.warn(
643
+ 'Was not able to map responseContent to a string',
644
+ responseContent
645
+ );
646
+ return undefined;
647
+ })
648
+ .filter(text => text !== undefined)
649
+ .join('\n\n');
650
+ }
651
+
652
+ asString(): string {
653
+ return this._responseRepresentation;
654
+ }
655
+ }
656
+
657
+ class ChatResponseModelImpl implements ChatResponseModel {
658
+ protected readonly _onDidChangeEmitter = new Emitter<void>();
659
+ onDidChange: Event<void> = this._onDidChangeEmitter.event;
660
+
661
+ protected _id: string;
662
+ protected _requestId: string;
663
+ protected _progressMessages: ChatProgressMessage[];
664
+ protected _response: ChatResponseImpl;
665
+ protected _isComplete: boolean;
666
+ protected _isCanceled: boolean;
667
+ protected _agentId?: string;
668
+ protected _isError: boolean;
669
+ protected _errorObject: Error | undefined;
670
+
671
+ constructor(requestId: string, agentId?: string) {
672
+ // TODO accept serialized data as a parameter to restore a previously saved ChatResponseModel
673
+ this._requestId = requestId;
674
+ this._id = generateUuid();
675
+ this._progressMessages = [];
676
+ const response = new ChatResponseImpl();
677
+ response.onDidChange(() => this._onDidChangeEmitter.fire());
678
+ this._response = response;
679
+ this._isComplete = false;
680
+ this._isCanceled = false;
681
+ this._agentId = agentId;
682
+ }
683
+
684
+ get id(): string {
685
+ return this._id;
686
+ }
687
+
688
+ get requestId(): string {
689
+ return this._requestId;
690
+ }
691
+
692
+ get progressMessages(): ChatProgressMessage[] {
693
+ return this._progressMessages;
694
+ }
695
+
696
+ addProgressMessage(message: { content: string } & Partial<Omit<ChatProgressMessage, 'kind'>>): ChatProgressMessage {
697
+ const id = message.id ?? generateUuid();
698
+ const existingMessage = this.getProgressMessage(id);
699
+ if (existingMessage) {
700
+ this.updateProgressMessage({ id, ...message });
701
+ return existingMessage;
702
+ }
703
+ const newMessage: ChatProgressMessage = {
704
+ kind: 'progressMessage',
705
+ id,
706
+ status: message.status ?? 'inProgress',
707
+ ...message,
708
+ };
709
+ this._progressMessages.push(newMessage);
710
+ this._onDidChangeEmitter.fire();
711
+ return newMessage;
712
+ }
713
+
714
+ getProgressMessage(id: string): ChatProgressMessage | undefined {
715
+ return this._progressMessages.find(message => message.id === id);
716
+ }
717
+
718
+ updateProgressMessage(message: { id: string } & Partial<Omit<ChatProgressMessage, 'kind'>>): void {
719
+ const progressMessage = this.getProgressMessage(message.id);
720
+ if (progressMessage) {
721
+ Object.assign(progressMessage, message);
722
+ this._onDidChangeEmitter.fire();
723
+ }
724
+ }
725
+
726
+ get response(): ChatResponseImpl {
727
+ return this._response;
728
+ }
729
+
730
+ get isComplete(): boolean {
731
+ return this._isComplete;
732
+ }
733
+
734
+ get isCanceled(): boolean {
735
+ return this._isCanceled;
736
+ }
737
+
738
+ get agentId(): string | undefined {
739
+ return this._agentId;
740
+ }
741
+
742
+ overrideAgentId(agentId: string): void {
743
+ this._agentId = agentId;
744
+ }
745
+
746
+ complete(): void {
747
+ this._isComplete = true;
748
+ this._onDidChangeEmitter.fire();
749
+ }
750
+
751
+ cancel(): void {
752
+ this._isComplete = true;
753
+ this._isCanceled = true;
754
+ this._onDidChangeEmitter.fire();
755
+ }
756
+ error(error: Error): void {
757
+ this._isComplete = true;
758
+ this._isCanceled = false;
759
+ this._isError = true;
760
+ this._errorObject = error;
761
+ this._onDidChangeEmitter.fire();
762
+ }
763
+ get errorObject(): Error | undefined {
764
+ return this._errorObject;
765
+ }
766
+ get isError(): boolean {
767
+ return this._isError;
768
+ }
769
+ }
770
+
771
+ export class ErrorChatResponseModelImpl extends ChatResponseModelImpl {
772
+ constructor(requestId: string, error: Error, agentId?: string) {
773
+ super(requestId, agentId);
774
+ this.error(error);
775
+ }
776
+ }