@theia/ai-chat 1.55.0-next.97 → 1.56.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 (40) hide show
  1. package/lib/common/chat-agents.d.ts +10 -2
  2. package/lib/common/chat-agents.d.ts.map +1 -1
  3. package/lib/common/chat-agents.js +23 -18
  4. package/lib/common/chat-agents.js.map +1 -1
  5. package/lib/common/chat-model-util.d.ts +7 -0
  6. package/lib/common/chat-model-util.d.ts.map +1 -0
  7. package/lib/common/chat-model-util.js +50 -0
  8. package/lib/common/chat-model-util.js.map +1 -0
  9. package/lib/common/chat-model.d.ts +59 -0
  10. package/lib/common/chat-model.d.ts.map +1 -1
  11. package/lib/common/chat-model.js +70 -2
  12. package/lib/common/chat-model.js.map +1 -1
  13. package/lib/common/index.d.ts +1 -0
  14. package/lib/common/index.d.ts.map +1 -1
  15. package/lib/common/index.js +1 -0
  16. package/lib/common/index.js.map +1 -1
  17. package/lib/common/parse-contents.d.ts +2 -2
  18. package/lib/common/parse-contents.d.ts.map +1 -1
  19. package/lib/common/parse-contents.js +4 -4
  20. package/lib/common/parse-contents.js.map +1 -1
  21. package/lib/common/parse-contents.spec.d.ts.map +1 -1
  22. package/lib/common/parse-contents.spec.js +14 -13
  23. package/lib/common/parse-contents.spec.js.map +1 -1
  24. package/lib/common/response-content-matcher.d.ts +3 -3
  25. package/lib/common/response-content-matcher.d.ts.map +1 -1
  26. package/lib/common/response-content-matcher.js +2 -2
  27. package/lib/common/response-content-matcher.js.map +1 -1
  28. package/lib/common/universal-chat-agent.d.ts +1 -0
  29. package/lib/common/universal-chat-agent.d.ts.map +1 -1
  30. package/lib/common/universal-chat-agent.js +7 -2
  31. package/lib/common/universal-chat-agent.js.map +1 -1
  32. package/package.json +8 -8
  33. package/src/common/chat-agents.ts +25 -17
  34. package/src/common/chat-model-util.ts +44 -0
  35. package/src/common/chat-model.ts +89 -0
  36. package/src/common/index.ts +1 -0
  37. package/src/common/parse-contents.spec.ts +16 -14
  38. package/src/common/parse-contents.ts +5 -4
  39. package/src/common/response-content-matcher.ts +4 -3
  40. package/src/common/universal-chat-agent.ts +7 -1
@@ -80,6 +80,7 @@ export interface ChatProgressMessage {
80
80
  kind: 'progressMessage';
81
81
  id: string;
82
82
  status: 'inProgress' | 'completed' | 'failed';
83
+ show: 'untilFirstContent' | 'whileIncomplete' | 'forever';
83
84
  content: string;
84
85
  }
85
86
 
@@ -279,6 +280,42 @@ export namespace ErrorChatResponseContent {
279
280
  }
280
281
  }
281
282
 
283
+ export type QuestionResponseHandler = (
284
+ selectedOption: { text: string, value?: string },
285
+ ) => void;
286
+
287
+ export interface QuestionResponseContent extends ChatResponseContent {
288
+ kind: 'question';
289
+ question: string;
290
+ options: { text: string, value?: string }[];
291
+ selectedOption?: { text: string, value?: string };
292
+ handler: QuestionResponseHandler;
293
+ request: ChatRequestModelImpl;
294
+ }
295
+
296
+ export namespace QuestionResponseContent {
297
+ export function is(obj: unknown): obj is QuestionResponseContent {
298
+ return (
299
+ ChatResponseContent.is(obj) &&
300
+ obj.kind === 'question' &&
301
+ 'question' in obj &&
302
+ typeof (obj as { question: unknown }).question === 'string' &&
303
+ 'options' in obj &&
304
+ Array.isArray((obj as { options: unknown }).options) &&
305
+ (obj as { options: unknown[] }).options.every(option =>
306
+ typeof option === 'object' &&
307
+ option && 'text' in option &&
308
+ typeof (option as { text: unknown }).text === 'string' &&
309
+ ('value' in option ? typeof (option as { value: unknown }).value === 'string' || typeof (option as { value: unknown }).value === 'undefined' : true)
310
+ ) &&
311
+ 'handler' in obj &&
312
+ typeof (obj as { handler: unknown }).handler === 'function' &&
313
+ 'request' in obj &&
314
+ obj.request instanceof ChatRequestModelImpl
315
+ );
316
+ }
317
+ }
318
+
282
319
  export interface ChatResponse {
283
320
  readonly content: ChatResponseContent[];
284
321
  asString(): string;
@@ -292,6 +329,7 @@ export interface ChatResponseModel {
292
329
  readonly response: ChatResponse;
293
330
  readonly isComplete: boolean;
294
331
  readonly isCanceled: boolean;
332
+ readonly isWaitingForInput: boolean;
295
333
  readonly isError: boolean;
296
334
  readonly agentId?: string
297
335
  readonly errorObject?: Error;
@@ -602,6 +640,31 @@ export class HorizontalLayoutChatResponseContentImpl implements HorizontalLayout
602
640
  }
603
641
  }
604
642
 
643
+ /**
644
+ * Default implementation for the QuestionResponseContent.
645
+ */
646
+ export class QuestionResponseContentImpl implements QuestionResponseContent {
647
+ readonly kind = 'question';
648
+ protected _selectedOption: { text: string; value?: string } | undefined;
649
+ constructor(public question: string, public options: { text: string, value?: string }[],
650
+ public request: ChatRequestModelImpl, public handler: QuestionResponseHandler) {
651
+ }
652
+ set selectedOption(option: { text: string; value?: string; } | undefined) {
653
+ this._selectedOption = option;
654
+ this.request.response.response.responseContentChanged();
655
+ }
656
+ get selectedOption(): { text: string; value?: string; } | undefined {
657
+ return this._selectedOption;
658
+ }
659
+ asString?(): string | undefined {
660
+ return `Question: ${this.question}
661
+ ${this.selectedOption ? `Answer: ${this.selectedOption?.text}` : 'No answer'}`;
662
+ }
663
+ merge?(): boolean {
664
+ return false;
665
+ }
666
+ }
667
+
605
668
  class ChatResponseImpl implements ChatResponse {
606
669
  protected readonly _onDidChangeEmitter = new Emitter<void>();
607
670
  onDidChange: Event<void> = this._onDidChangeEmitter.event;
@@ -654,6 +717,11 @@ class ChatResponseImpl implements ChatResponse {
654
717
  this._updateResponseRepresentation();
655
718
  }
656
719
 
720
+ responseContentChanged(): void {
721
+ this._updateResponseRepresentation();
722
+ this._onDidChangeEmitter.fire();
723
+ }
724
+
657
725
  protected _updateResponseRepresentation(): void {
658
726
  this._responseRepresentation = this._content
659
727
  .map(responseContent => {
@@ -688,6 +756,7 @@ class ChatResponseModelImpl implements ChatResponseModel {
688
756
  protected _response: ChatResponseImpl;
689
757
  protected _isComplete: boolean;
690
758
  protected _isCanceled: boolean;
759
+ protected _isWaitingForInput: boolean;
691
760
  protected _agentId?: string;
692
761
  protected _isError: boolean;
693
762
  protected _errorObject: Error | undefined;
@@ -702,6 +771,7 @@ class ChatResponseModelImpl implements ChatResponseModel {
702
771
  this._response = response;
703
772
  this._isComplete = false;
704
773
  this._isCanceled = false;
774
+ this._isWaitingForInput = false;
705
775
  this._agentId = agentId;
706
776
  }
707
777
 
@@ -728,6 +798,7 @@ class ChatResponseModelImpl implements ChatResponseModel {
728
798
  kind: 'progressMessage',
729
799
  id,
730
800
  status: message.status ?? 'inProgress',
801
+ show: message.show ?? 'untilFirstContent',
731
802
  ...message,
732
803
  };
733
804
  this._progressMessages.push(newMessage);
@@ -759,6 +830,10 @@ class ChatResponseModelImpl implements ChatResponseModel {
759
830
  return this._isCanceled;
760
831
  }
761
832
 
833
+ get isWaitingForInput(): boolean {
834
+ return this._isWaitingForInput;
835
+ }
836
+
762
837
  get agentId(): string | undefined {
763
838
  return this._agentId;
764
839
  }
@@ -769,17 +844,31 @@ class ChatResponseModelImpl implements ChatResponseModel {
769
844
 
770
845
  complete(): void {
771
846
  this._isComplete = true;
847
+ this._isWaitingForInput = false;
772
848
  this._onDidChangeEmitter.fire();
773
849
  }
774
850
 
775
851
  cancel(): void {
776
852
  this._isComplete = true;
777
853
  this._isCanceled = true;
854
+ this._isWaitingForInput = false;
778
855
  this._onDidChangeEmitter.fire();
779
856
  }
857
+
858
+ waitForInput(): void {
859
+ this._isWaitingForInput = true;
860
+ this._onDidChangeEmitter.fire();
861
+ }
862
+
863
+ stopWaitingForInput(): void {
864
+ this._isWaitingForInput = false;
865
+ this._onDidChangeEmitter.fire();
866
+ }
867
+
780
868
  error(error: Error): void {
781
869
  this._isComplete = true;
782
870
  this._isCanceled = false;
871
+ this._isWaitingForInput = false;
783
872
  this._isError = true;
784
873
  this._errorObject = error;
785
874
  this._onDidChangeEmitter.fire();
@@ -16,6 +16,7 @@
16
16
  export * from './chat-agents';
17
17
  export * from './chat-agent-service';
18
18
  export * from './chat-model';
19
+ export * from './chat-model-util';
19
20
  export * from './chat-request-parser';
20
21
  export * from './chat-service';
21
22
  export * from './command-chat-agents';
@@ -15,7 +15,7 @@
15
15
  // *****************************************************************************
16
16
 
17
17
  import { expect } from 'chai';
18
- import { ChatResponseContent, CodeChatResponseContentImpl, MarkdownChatResponseContentImpl } from './chat-model';
18
+ import { ChatRequestModelImpl, ChatResponseContent, CodeChatResponseContentImpl, MarkdownChatResponseContentImpl } from './chat-model';
19
19
  import { parseContents } from './parse-contents';
20
20
  import { CodeContentMatcher, ResponseContentMatcher } from './response-content-matcher';
21
21
 
@@ -33,22 +33,24 @@ export const CommandContentMatcher: ResponseContentMatcher = {
33
33
  }
34
34
  };
35
35
 
36
+ const fakeRequest = {} as ChatRequestModelImpl;
37
+
36
38
  describe('parseContents', () => {
37
39
  it('should parse code content', () => {
38
40
  const text = '```typescript\nconsole.log("Hello World");\n```';
39
- const result = parseContents(text);
41
+ const result = parseContents(text, fakeRequest);
40
42
  expect(result).to.deep.equal([new CodeChatResponseContentImpl('console.log("Hello World");', 'typescript')]);
41
43
  });
42
44
 
43
45
  it('should parse markdown content', () => {
44
46
  const text = 'Hello **World**';
45
- const result = parseContents(text);
47
+ const result = parseContents(text, fakeRequest);
46
48
  expect(result).to.deep.equal([new MarkdownChatResponseContentImpl('Hello **World**')]);
47
49
  });
48
50
 
49
51
  it('should parse multiple content blocks', () => {
50
52
  const text = '```typescript\nconsole.log("Hello World");\n```\nHello **World**';
51
- const result = parseContents(text);
53
+ const result = parseContents(text, fakeRequest);
52
54
  expect(result).to.deep.equal([
53
55
  new CodeChatResponseContentImpl('console.log("Hello World");', 'typescript'),
54
56
  new MarkdownChatResponseContentImpl('\nHello **World**')
@@ -57,7 +59,7 @@ describe('parseContents', () => {
57
59
 
58
60
  it('should parse multiple content blocks with different languages', () => {
59
61
  const text = '```typescript\nconsole.log("Hello World");\n```\n```python\nprint("Hello World")\n```';
60
- const result = parseContents(text);
62
+ const result = parseContents(text, fakeRequest);
61
63
  expect(result).to.deep.equal([
62
64
  new CodeChatResponseContentImpl('console.log("Hello World");', 'typescript'),
63
65
  new CodeChatResponseContentImpl('print("Hello World")', 'python')
@@ -66,7 +68,7 @@ describe('parseContents', () => {
66
68
 
67
69
  it('should parse multiple content blocks with different languages and markdown', () => {
68
70
  const text = '```typescript\nconsole.log("Hello World");\n```\nHello **World**\n```python\nprint("Hello World")\n```';
69
- const result = parseContents(text);
71
+ const result = parseContents(text, fakeRequest);
70
72
  expect(result).to.deep.equal([
71
73
  new CodeChatResponseContentImpl('console.log("Hello World");', 'typescript'),
72
74
  new MarkdownChatResponseContentImpl('\nHello **World**\n'),
@@ -76,7 +78,7 @@ describe('parseContents', () => {
76
78
 
77
79
  it('should parse content blocks with empty content', () => {
78
80
  const text = '```typescript\n```\nHello **World**\n```python\nprint("Hello World")\n```';
79
- const result = parseContents(text);
81
+ const result = parseContents(text, fakeRequest);
80
82
  expect(result).to.deep.equal([
81
83
  new CodeChatResponseContentImpl('', 'typescript'),
82
84
  new MarkdownChatResponseContentImpl('\nHello **World**\n'),
@@ -86,7 +88,7 @@ describe('parseContents', () => {
86
88
 
87
89
  it('should parse content with markdown, code, and markdown', () => {
88
90
  const text = 'Hello **World**\n```typescript\nconsole.log("Hello World");\n```\nGoodbye **World**';
89
- const result = parseContents(text);
91
+ const result = parseContents(text, fakeRequest);
90
92
  expect(result).to.deep.equal([
91
93
  new MarkdownChatResponseContentImpl('Hello **World**\n'),
92
94
  new CodeChatResponseContentImpl('console.log("Hello World");', 'typescript'),
@@ -96,25 +98,25 @@ describe('parseContents', () => {
96
98
 
97
99
  it('should handle text with no special content', () => {
98
100
  const text = 'Just some plain text.';
99
- const result = parseContents(text);
101
+ const result = parseContents(text, fakeRequest);
100
102
  expect(result).to.deep.equal([new MarkdownChatResponseContentImpl('Just some plain text.')]);
101
103
  });
102
104
 
103
105
  it('should handle text with only start code block', () => {
104
106
  const text = '```typescript\nconsole.log("Hello World");';
105
- const result = parseContents(text);
107
+ const result = parseContents(text, fakeRequest);
106
108
  expect(result).to.deep.equal([new MarkdownChatResponseContentImpl('```typescript\nconsole.log("Hello World");')]);
107
109
  });
108
110
 
109
111
  it('should handle text with only end code block', () => {
110
112
  const text = 'console.log("Hello World");\n```';
111
- const result = parseContents(text);
113
+ const result = parseContents(text, fakeRequest);
112
114
  expect(result).to.deep.equal([new MarkdownChatResponseContentImpl('console.log("Hello World");\n```')]);
113
115
  });
114
116
 
115
117
  it('should handle text with unmatched code block', () => {
116
118
  const text = '```typescript\nconsole.log("Hello World");\n```\n```python\nprint("Hello World")';
117
- const result = parseContents(text);
119
+ const result = parseContents(text, fakeRequest);
118
120
  expect(result).to.deep.equal([
119
121
  new CodeChatResponseContentImpl('console.log("Hello World");', 'typescript'),
120
122
  new MarkdownChatResponseContentImpl('\n```python\nprint("Hello World")')
@@ -123,7 +125,7 @@ describe('parseContents', () => {
123
125
 
124
126
  it('should parse code block without newline after language', () => {
125
127
  const text = '```typescript console.log("Hello World");```';
126
- const result = parseContents(text);
128
+ const result = parseContents(text, fakeRequest);
127
129
  expect(result).to.deep.equal([
128
130
  new MarkdownChatResponseContentImpl('```typescript console.log("Hello World");```')
129
131
  ]);
@@ -131,7 +133,7 @@ describe('parseContents', () => {
131
133
 
132
134
  it('should parse with matches of multiple different matchers and default', () => {
133
135
  const text = '<command>\nMY_SPECIAL_COMMAND\n</command>\nHello **World**\n```python\nprint("Hello World")\n```\n<command>\nMY_SPECIAL_COMMAND2\n</command>';
134
- const result = parseContents(text, [CodeContentMatcher, CommandContentMatcher]);
136
+ const result = parseContents(text, fakeRequest, [CodeContentMatcher, CommandContentMatcher]);
135
137
  expect(result).to.deep.equal([
136
138
  new CommandChatResponseContentImpl('MY_SPECIAL_COMMAND'),
137
139
  new MarkdownChatResponseContentImpl('\nHello **World**\n'),
@@ -13,7 +13,7 @@
13
13
  *
14
14
  * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
15
  */
16
- import { ChatResponseContent } from './chat-model';
16
+ import { ChatRequestModelImpl, ChatResponseContent } from './chat-model';
17
17
  import { CodeContentMatcher, MarkdownContentFactory, ResponseContentFactory, ResponseContentMatcher } from './response-content-matcher';
18
18
 
19
19
  interface Match {
@@ -24,6 +24,7 @@ interface Match {
24
24
 
25
25
  export function parseContents(
26
26
  text: string,
27
+ request: ChatRequestModelImpl,
27
28
  contentMatchers: ResponseContentMatcher[] = [CodeContentMatcher],
28
29
  defaultContentFactory: ResponseContentFactory = MarkdownContentFactory
29
30
  ): ChatResponseContent[] {
@@ -36,7 +37,7 @@ export function parseContents(
36
37
  if (!match) {
37
38
  // Add the remaining text as default content
38
39
  if (remainingText.length > 0) {
39
- result.push(defaultContentFactory(remainingText));
40
+ result.push(defaultContentFactory(remainingText, request));
40
41
  }
41
42
  break;
42
43
  }
@@ -45,11 +46,11 @@ export function parseContents(
45
46
  if (match.index > 0) {
46
47
  const precedingContent = remainingText.substring(0, match.index);
47
48
  if (precedingContent.trim().length > 0) {
48
- result.push(defaultContentFactory(precedingContent));
49
+ result.push(defaultContentFactory(precedingContent, request));
49
50
  }
50
51
  }
51
52
  // 2. Add the matched content object
52
- result.push(match.matcher.contentFactory(match.content));
53
+ result.push(match.matcher.contentFactory(match.content, request));
53
54
  // Update currentIndex to the end of the end of the match
54
55
  // And continue with the search after the end of the match
55
56
  currentIndex += match.index + match.content.length;
@@ -14,13 +14,14 @@
14
14
  * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
15
  */
16
16
  import {
17
+ ChatRequestModelImpl,
17
18
  ChatResponseContent,
18
19
  CodeChatResponseContentImpl,
19
20
  MarkdownChatResponseContentImpl
20
21
  } from './chat-model';
21
22
  import { injectable } from '@theia/core/shared/inversify';
22
23
 
23
- export type ResponseContentFactory = (content: string) => ChatResponseContent;
24
+ export type ResponseContentFactory = (content: string, request: ChatRequestModelImpl) => ChatResponseContent;
24
25
 
25
26
  export const MarkdownContentFactory: ResponseContentFactory = (content: string) =>
26
27
  new MarkdownChatResponseContentImpl(content);
@@ -33,8 +34,8 @@ export const MarkdownContentFactory: ResponseContentFactory = (content: string)
33
34
  */
34
35
  @injectable()
35
36
  export class DefaultResponseContentFactory {
36
- create(content: string): ChatResponseContent {
37
- return MarkdownContentFactory(content);
37
+ create(content: string, request: ChatRequestModelImpl): ChatResponseContent {
38
+ return MarkdownContentFactory(content, request);
38
39
  }
39
40
  }
40
41
 
@@ -78,6 +78,12 @@ simple solutions.
78
78
  `
79
79
  };
80
80
 
81
+ export const universalTemplateVariant: PromptTemplate = {
82
+ id: 'universal-system-empty',
83
+ template: '',
84
+ variantOf: universalTemplate.id,
85
+ };
86
+
81
87
  @injectable()
82
88
  export class UniversalChatAgent extends AbstractStreamParsingChatAgent implements ChatAgent {
83
89
  name: string;
@@ -98,7 +104,7 @@ export class UniversalChatAgent extends AbstractStreamParsingChatAgent implement
98
104
  + 'questions the user might ask. The universal agent currently does not have any context by default, i.e. it cannot '
99
105
  + 'access the current user context or the workspace.';
100
106
  this.variables = [];
101
- this.promptTemplates = [universalTemplate];
107
+ this.promptTemplates = [universalTemplate, universalTemplateVariant];
102
108
  this.functions = [];
103
109
  this.agentSpecificVariables = [];
104
110
  }