@theia/ai-chat 1.54.0 → 1.55.0-next.14

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.
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ // *****************************************************************************
3
+ // Copyright (C) 2024 EclipseSource GmbH.
4
+ //
5
+ // This program and the accompanying materials are made available under the
6
+ // terms of the Eclipse Public License v. 2.0 which is available at
7
+ // http://www.eclipse.org/legal/epl-2.0.
8
+ //
9
+ // This Source Code may also be made available under the following Secondary
10
+ // Licenses when the conditions for such availability set forth in the Eclipse
11
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
12
+ // with the GNU Classpath Exception which is available at
13
+ // https://www.gnu.org/software/classpath/license.html.
14
+ //
15
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
16
+ // *****************************************************************************
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.CommandContentMatcher = exports.CommandChatResponseContentImpl = void 0;
19
+ const chai_1 = require("chai");
20
+ const chat_model_1 = require("./chat-model");
21
+ const parse_contents_1 = require("./parse-contents");
22
+ const response_content_matcher_1 = require("./response-content-matcher");
23
+ class CommandChatResponseContentImpl {
24
+ constructor(command) {
25
+ this.command = command;
26
+ this.kind = 'command';
27
+ }
28
+ }
29
+ exports.CommandChatResponseContentImpl = CommandChatResponseContentImpl;
30
+ exports.CommandContentMatcher = {
31
+ start: /^<command>$/m,
32
+ end: /^<\/command>$/m,
33
+ contentFactory: (content) => {
34
+ const code = content.replace(/^<command>\n|<\/command>$/g, '');
35
+ return new CommandChatResponseContentImpl(code.trim());
36
+ }
37
+ };
38
+ describe('parseContents', () => {
39
+ it('should parse code content', () => {
40
+ const text = '```typescript\nconsole.log("Hello World");\n```';
41
+ const result = (0, parse_contents_1.parseContents)(text);
42
+ (0, chai_1.expect)(result).to.deep.equal([new chat_model_1.CodeChatResponseContentImpl('console.log("Hello World");', 'typescript')]);
43
+ });
44
+ it('should parse markdown content', () => {
45
+ const text = 'Hello **World**';
46
+ const result = (0, parse_contents_1.parseContents)(text);
47
+ (0, chai_1.expect)(result).to.deep.equal([new chat_model_1.MarkdownChatResponseContentImpl('Hello **World**')]);
48
+ });
49
+ it('should parse multiple content blocks', () => {
50
+ const text = '```typescript\nconsole.log("Hello World");\n```\nHello **World**';
51
+ const result = (0, parse_contents_1.parseContents)(text);
52
+ (0, chai_1.expect)(result).to.deep.equal([
53
+ new chat_model_1.CodeChatResponseContentImpl('console.log("Hello World");', 'typescript'),
54
+ new chat_model_1.MarkdownChatResponseContentImpl('\nHello **World**')
55
+ ]);
56
+ });
57
+ it('should parse multiple content blocks with different languages', () => {
58
+ const text = '```typescript\nconsole.log("Hello World");\n```\n```python\nprint("Hello World")\n```';
59
+ const result = (0, parse_contents_1.parseContents)(text);
60
+ (0, chai_1.expect)(result).to.deep.equal([
61
+ new chat_model_1.CodeChatResponseContentImpl('console.log("Hello World");', 'typescript'),
62
+ new chat_model_1.CodeChatResponseContentImpl('print("Hello World")', 'python')
63
+ ]);
64
+ });
65
+ it('should parse multiple content blocks with different languages and markdown', () => {
66
+ const text = '```typescript\nconsole.log("Hello World");\n```\nHello **World**\n```python\nprint("Hello World")\n```';
67
+ const result = (0, parse_contents_1.parseContents)(text);
68
+ (0, chai_1.expect)(result).to.deep.equal([
69
+ new chat_model_1.CodeChatResponseContentImpl('console.log("Hello World");', 'typescript'),
70
+ new chat_model_1.MarkdownChatResponseContentImpl('\nHello **World**\n'),
71
+ new chat_model_1.CodeChatResponseContentImpl('print("Hello World")', 'python')
72
+ ]);
73
+ });
74
+ it('should parse content blocks with empty content', () => {
75
+ const text = '```typescript\n```\nHello **World**\n```python\nprint("Hello World")\n```';
76
+ const result = (0, parse_contents_1.parseContents)(text);
77
+ (0, chai_1.expect)(result).to.deep.equal([
78
+ new chat_model_1.CodeChatResponseContentImpl('', 'typescript'),
79
+ new chat_model_1.MarkdownChatResponseContentImpl('\nHello **World**\n'),
80
+ new chat_model_1.CodeChatResponseContentImpl('print("Hello World")', 'python')
81
+ ]);
82
+ });
83
+ it('should parse content with markdown, code, and markdown', () => {
84
+ const text = 'Hello **World**\n```typescript\nconsole.log("Hello World");\n```\nGoodbye **World**';
85
+ const result = (0, parse_contents_1.parseContents)(text);
86
+ (0, chai_1.expect)(result).to.deep.equal([
87
+ new chat_model_1.MarkdownChatResponseContentImpl('Hello **World**\n'),
88
+ new chat_model_1.CodeChatResponseContentImpl('console.log("Hello World");', 'typescript'),
89
+ new chat_model_1.MarkdownChatResponseContentImpl('\nGoodbye **World**')
90
+ ]);
91
+ });
92
+ it('should handle text with no special content', () => {
93
+ const text = 'Just some plain text.';
94
+ const result = (0, parse_contents_1.parseContents)(text);
95
+ (0, chai_1.expect)(result).to.deep.equal([new chat_model_1.MarkdownChatResponseContentImpl('Just some plain text.')]);
96
+ });
97
+ it('should handle text with only start code block', () => {
98
+ const text = '```typescript\nconsole.log("Hello World");';
99
+ const result = (0, parse_contents_1.parseContents)(text);
100
+ (0, chai_1.expect)(result).to.deep.equal([new chat_model_1.MarkdownChatResponseContentImpl('```typescript\nconsole.log("Hello World");')]);
101
+ });
102
+ it('should handle text with only end code block', () => {
103
+ const text = 'console.log("Hello World");\n```';
104
+ const result = (0, parse_contents_1.parseContents)(text);
105
+ (0, chai_1.expect)(result).to.deep.equal([new chat_model_1.MarkdownChatResponseContentImpl('console.log("Hello World");\n```')]);
106
+ });
107
+ it('should handle text with unmatched code block', () => {
108
+ const text = '```typescript\nconsole.log("Hello World");\n```\n```python\nprint("Hello World")';
109
+ const result = (0, parse_contents_1.parseContents)(text);
110
+ (0, chai_1.expect)(result).to.deep.equal([
111
+ new chat_model_1.CodeChatResponseContentImpl('console.log("Hello World");', 'typescript'),
112
+ new chat_model_1.MarkdownChatResponseContentImpl('\n```python\nprint("Hello World")')
113
+ ]);
114
+ });
115
+ it('should parse code block without newline after language', () => {
116
+ const text = '```typescript console.log("Hello World");```';
117
+ const result = (0, parse_contents_1.parseContents)(text);
118
+ (0, chai_1.expect)(result).to.deep.equal([
119
+ new chat_model_1.MarkdownChatResponseContentImpl('```typescript console.log("Hello World");```')
120
+ ]);
121
+ });
122
+ it('should parse with matches of multiple different matchers and default', () => {
123
+ const text = '<command>\nMY_SPECIAL_COMMAND\n</command>\nHello **World**\n```python\nprint("Hello World")\n```\n<command>\nMY_SPECIAL_COMMAND2\n</command>';
124
+ const result = (0, parse_contents_1.parseContents)(text, [response_content_matcher_1.CodeContentMatcher, exports.CommandContentMatcher]);
125
+ (0, chai_1.expect)(result).to.deep.equal([
126
+ new CommandChatResponseContentImpl('MY_SPECIAL_COMMAND'),
127
+ new chat_model_1.MarkdownChatResponseContentImpl('\nHello **World**\n'),
128
+ new chat_model_1.CodeChatResponseContentImpl('print("Hello World")', 'python'),
129
+ new CommandChatResponseContentImpl('MY_SPECIAL_COMMAND2'),
130
+ ]);
131
+ });
132
+ });
133
+ //# sourceMappingURL=parse-contents.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-contents.spec.js","sourceRoot":"","sources":["../../src/common/parse-contents.spec.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ import { ChatResponseContent } from './chat-model';
2
+ export type ResponseContentFactory = (content: string) => ChatResponseContent;
3
+ export declare const MarkdownContentFactory: ResponseContentFactory;
4
+ /**
5
+ * Default response content factory used if no other `ResponseContentMatcher` applies.
6
+ * By default, this factory creates a markdown content object.
7
+ *
8
+ * @see MarkdownChatResponseContentImpl
9
+ */
10
+ export declare class DefaultResponseContentFactory {
11
+ create(content: string): ChatResponseContent;
12
+ }
13
+ /**
14
+ * Clients can contribute response content matchers to parse a chat response into specific
15
+ * `ChatResponseContent` instances.
16
+ */
17
+ export interface ResponseContentMatcher {
18
+ /** Regular expression for finding the start delimiter. */
19
+ start: RegExp;
20
+ /** Regular expression for finding the start delimiter. */
21
+ end: RegExp;
22
+ /**
23
+ * The factory creating a response content from the matching content,
24
+ * from start index to end index of the match (including delimiters).
25
+ */
26
+ contentFactory: ResponseContentFactory;
27
+ }
28
+ export declare const CodeContentMatcher: ResponseContentMatcher;
29
+ /**
30
+ * Clients can contribute response content matchers to parse the response content.
31
+ *
32
+ * The default chat user interface will collect all contributed matchers and use them
33
+ * to parse the response into structured content parts (e.g. code blocks, markdown blocks),
34
+ * which are then rendered with a `ChatResponsePartRenderer` registered for the respective
35
+ * content part type.
36
+ *
37
+ * ### Example
38
+ * ```ts
39
+ * bind(ResponseContentMatcherProvider).to(MyResponseContentMatcherProvider);
40
+ * ...
41
+ * @injectable()
42
+ * export class MyResponseContentMatcherProvider implements ResponseContentMatcherProvider {
43
+ * readonly matchers: ResponseContentMatcher[] = [{
44
+ * start: /^<command>$/m,
45
+ * end: /^</command>$/m,
46
+ * contentFactory: (content: string) => {
47
+ * const command = content.replace(/^<command>\n|<\/command>$/g, '');
48
+ * return new MyChatResponseContentImpl(command.trim());
49
+ * }
50
+ * }];
51
+ * }
52
+ * ```
53
+ *
54
+ * @see ResponseContentMatcher
55
+ */
56
+ export declare const ResponseContentMatcherProvider: unique symbol;
57
+ export interface ResponseContentMatcherProvider {
58
+ readonly matchers: ResponseContentMatcher[];
59
+ }
60
+ export declare class DefaultResponseContentMatcherProvider implements ResponseContentMatcherProvider {
61
+ readonly matchers: ResponseContentMatcher[];
62
+ }
63
+ //# sourceMappingURL=response-content-matcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response-content-matcher.d.ts","sourceRoot":"","sources":["../../src/common/response-content-matcher.ts"],"names":[],"mappings":"AAeA,OAAO,EACH,mBAAmB,EAGtB,MAAM,cAAc,CAAC;AAGtB,MAAM,MAAM,sBAAsB,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,mBAAmB,CAAC;AAE9E,eAAO,MAAM,sBAAsB,EAAE,sBACW,CAAC;AAEjD;;;;;GAKG;AACH,qBACa,6BAA6B;IACtC,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,mBAAmB;CAG/C;AAED;;;GAGG;AACH,MAAM,WAAW,sBAAsB;IACnC,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAC;IACd,0DAA0D;IAC1D,GAAG,EAAE,MAAM,CAAC;IACZ;;;OAGG;IACH,cAAc,EAAE,sBAAsB,CAAC;CAC1C;AAED,eAAO,MAAM,kBAAkB,EAAE,sBAQhC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,8BAA8B,eAA2C,CAAC;AACvF,MAAM,WAAW,8BAA8B;IAC3C,QAAQ,CAAC,QAAQ,EAAE,sBAAsB,EAAE,CAAC;CAC/C;AAED,qBACa,qCAAsC,YAAW,8BAA8B;IACxF,QAAQ,CAAC,QAAQ,EAAE,sBAAsB,EAAE,CAAwB;CACtE"}
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DefaultResponseContentMatcherProvider = exports.ResponseContentMatcherProvider = exports.CodeContentMatcher = exports.DefaultResponseContentFactory = exports.MarkdownContentFactory = void 0;
4
+ const tslib_1 = require("tslib");
5
+ /*
6
+ * Copyright (C) 2024 EclipseSource GmbH.
7
+ *
8
+ * This program and the accompanying materials are made available under the
9
+ * terms of the Eclipse Public License v. 2.0 which is available at
10
+ * http://www.eclipse.org/legal/epl-2.0.
11
+ *
12
+ * This Source Code may also be made available under the following Secondary
13
+ * Licenses when the conditions for such availability set forth in the Eclipse
14
+ * Public License v. 2.0 are satisfied: GNU General Public License, version 2
15
+ * with the GNU Classpath Exception which is available at
16
+ * https://www.gnu.org/software/classpath/license.html.
17
+ *
18
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
19
+ */
20
+ const chat_model_1 = require("./chat-model");
21
+ const inversify_1 = require("@theia/core/shared/inversify");
22
+ const MarkdownContentFactory = (content) => new chat_model_1.MarkdownChatResponseContentImpl(content);
23
+ exports.MarkdownContentFactory = MarkdownContentFactory;
24
+ /**
25
+ * Default response content factory used if no other `ResponseContentMatcher` applies.
26
+ * By default, this factory creates a markdown content object.
27
+ *
28
+ * @see MarkdownChatResponseContentImpl
29
+ */
30
+ let DefaultResponseContentFactory = class DefaultResponseContentFactory {
31
+ create(content) {
32
+ return (0, exports.MarkdownContentFactory)(content);
33
+ }
34
+ };
35
+ exports.DefaultResponseContentFactory = DefaultResponseContentFactory;
36
+ exports.DefaultResponseContentFactory = DefaultResponseContentFactory = tslib_1.__decorate([
37
+ (0, inversify_1.injectable)()
38
+ ], DefaultResponseContentFactory);
39
+ exports.CodeContentMatcher = {
40
+ start: /^```.*?$/m,
41
+ end: /^```$/m,
42
+ contentFactory: (content) => {
43
+ var _a;
44
+ const language = ((_a = content.match(/^```(\w+)/)) === null || _a === void 0 ? void 0 : _a[1]) || '';
45
+ const code = content.replace(/^```(\w+)\n|```$/g, '');
46
+ return new chat_model_1.CodeChatResponseContentImpl(code.trim(), language);
47
+ }
48
+ };
49
+ /**
50
+ * Clients can contribute response content matchers to parse the response content.
51
+ *
52
+ * The default chat user interface will collect all contributed matchers and use them
53
+ * to parse the response into structured content parts (e.g. code blocks, markdown blocks),
54
+ * which are then rendered with a `ChatResponsePartRenderer` registered for the respective
55
+ * content part type.
56
+ *
57
+ * ### Example
58
+ * ```ts
59
+ * bind(ResponseContentMatcherProvider).to(MyResponseContentMatcherProvider);
60
+ * ...
61
+ * @injectable()
62
+ * export class MyResponseContentMatcherProvider implements ResponseContentMatcherProvider {
63
+ * readonly matchers: ResponseContentMatcher[] = [{
64
+ * start: /^<command>$/m,
65
+ * end: /^</command>$/m,
66
+ * contentFactory: (content: string) => {
67
+ * const command = content.replace(/^<command>\n|<\/command>$/g, '');
68
+ * return new MyChatResponseContentImpl(command.trim());
69
+ * }
70
+ * }];
71
+ * }
72
+ * ```
73
+ *
74
+ * @see ResponseContentMatcher
75
+ */
76
+ exports.ResponseContentMatcherProvider = Symbol('ResponseContentMatcherProvider');
77
+ let DefaultResponseContentMatcherProvider = class DefaultResponseContentMatcherProvider {
78
+ constructor() {
79
+ this.matchers = [exports.CodeContentMatcher];
80
+ }
81
+ };
82
+ exports.DefaultResponseContentMatcherProvider = DefaultResponseContentMatcherProvider;
83
+ exports.DefaultResponseContentMatcherProvider = DefaultResponseContentMatcherProvider = tslib_1.__decorate([
84
+ (0, inversify_1.injectable)()
85
+ ], DefaultResponseContentMatcherProvider);
86
+ //# sourceMappingURL=response-content-matcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response-content-matcher.js","sourceRoot":"","sources":["../../src/common/response-content-matcher.ts"],"names":[],"mappings":";;;;AAAA;;;;;;;;;;;;;;GAcG;AACH,6CAIsB;AACtB,4DAA0D;AAInD,MAAM,sBAAsB,GAA2B,CAAC,OAAe,EAAE,EAAE,CAC9E,IAAI,4CAA+B,CAAC,OAAO,CAAC,CAAC;AADpC,QAAA,sBAAsB,0BACc;AAEjD;;;;;GAKG;AAEI,IAAM,6BAA6B,GAAnC,MAAM,6BAA6B;IACtC,MAAM,CAAC,OAAe;QAClB,OAAO,IAAA,8BAAsB,EAAC,OAAO,CAAC,CAAC;IAC3C,CAAC;CACJ,CAAA;AAJY,sEAA6B;wCAA7B,6BAA6B;IADzC,IAAA,sBAAU,GAAE;GACA,6BAA6B,CAIzC;AAkBY,QAAA,kBAAkB,GAA2B;IACtD,KAAK,EAAE,WAAW;IAClB,GAAG,EAAE,QAAQ;IACb,cAAc,EAAE,CAAC,OAAe,EAAE,EAAE;;QAChC,MAAM,QAAQ,GAAG,CAAA,MAAA,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,0CAAG,CAAC,CAAC,KAAI,EAAE,CAAC;QACvD,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC,CAAC;QACtD,OAAO,IAAI,wCAA2B,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,QAAQ,CAAC,CAAC;IAClE,CAAC;CACJ,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACU,QAAA,8BAA8B,GAAG,MAAM,CAAC,gCAAgC,CAAC,CAAC;AAMhF,IAAM,qCAAqC,GAA3C,MAAM,qCAAqC;IAA3C;QACM,aAAQ,GAA6B,CAAC,0BAAkB,CAAC,CAAC;IACvE,CAAC;CAAA,CAAA;AAFY,sFAAqC;gDAArC,qCAAqC;IADjD,IAAA,sBAAU,GAAE;GACA,qCAAqC,CAEjD"}
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@theia/ai-chat",
3
- "version": "1.54.0",
3
+ "version": "1.55.0-next.14+d999fe031",
4
4
  "description": "Theia - AI Chat Extension",
5
5
  "dependencies": {
6
- "@theia/ai-core": "1.54.0",
7
- "@theia/ai-history": "1.54.0",
8
- "@theia/core": "1.54.0",
9
- "@theia/filesystem": "1.54.0",
10
- "@theia/workspace": "1.54.0",
6
+ "@theia/ai-core": "1.55.0-next.14+d999fe031",
7
+ "@theia/ai-history": "1.55.0-next.14+d999fe031",
8
+ "@theia/core": "1.55.0-next.14+d999fe031",
9
+ "@theia/filesystem": "1.55.0-next.14+d999fe031",
10
+ "@theia/workspace": "1.55.0-next.14+d999fe031",
11
11
  "minimatch": "^5.1.0",
12
12
  "tslib": "^2.6.2"
13
13
  },
@@ -50,5 +50,5 @@
50
50
  "nyc": {
51
51
  "extends": "../../configs/nyc.json"
52
52
  },
53
- "gitHead": "8fb36a237db744cff6e78eaff1481e1f36bb7a69"
53
+ "gitHead": "d999fe0315a29bc5b154067621dbc1eb180fa567"
54
54
  }
@@ -33,6 +33,7 @@ import { UniversalChatAgent } from '../common/universal-chat-agent';
33
33
  import { aiChatPreferences } from './ai-chat-preferences';
34
34
  import { ChatAgentsVariableContribution } from '../common/chat-agents-variable-contribution';
35
35
  import { FrontendChatServiceImpl } from './frontend-chat-service';
36
+ import { DefaultResponseContentMatcherProvider, DefaultResponseContentFactory, ResponseContentMatcherProvider } from '../common/response-content-matcher';
36
37
 
37
38
  export default new ContainerModule(bind => {
38
39
  bindContributionProvider(bind, Agent);
@@ -42,6 +43,11 @@ export default new ContainerModule(bind => {
42
43
  bind(ChatAgentService).toService(ChatAgentServiceImpl);
43
44
  bind(DefaultChatAgentId).toConstantValue({ id: OrchestratorChatAgentId });
44
45
 
46
+ bindContributionProvider(bind, ResponseContentMatcherProvider);
47
+ bind(DefaultResponseContentMatcherProvider).toSelf().inSingletonScope();
48
+ bind(ResponseContentMatcherProvider).toService(DefaultResponseContentMatcherProvider);
49
+ bind(DefaultResponseContentFactory).toSelf().inSingletonScope();
50
+
45
51
  bind(AIVariableContribution).to(ChatAgentsVariableContribution).inSingletonScope();
46
52
 
47
53
  bind(ChatRequestParserImpl).toSelf().inSingletonScope();
@@ -25,6 +25,7 @@ import {
25
25
  LanguageModel,
26
26
  LanguageModelRequirement,
27
27
  LanguageModelResponse,
28
+ LanguageModelStreamResponse,
28
29
  PromptService,
29
30
  ResolvedPromptTemplate,
30
31
  ToolRequest,
@@ -37,19 +38,20 @@ import {
37
38
  LanguageModelStreamResponsePart,
38
39
  MessageActor,
39
40
  } from '@theia/ai-core/lib/common';
40
- import { CancellationToken, CancellationTokenSource, ILogger, isArray } from '@theia/core';
41
- import { inject, injectable } from '@theia/core/shared/inversify';
41
+ import { CancellationToken, CancellationTokenSource, ContributionProvider, ILogger, isArray } from '@theia/core';
42
+ import { inject, injectable, named, postConstruct } from '@theia/core/shared/inversify';
42
43
  import { ChatAgentService } from './chat-agent-service';
43
44
  import {
44
45
  ChatModel,
45
46
  ChatRequestModel,
46
47
  ChatRequestModelImpl,
47
48
  ChatResponseContent,
48
- CodeChatResponseContentImpl,
49
49
  ErrorChatResponseContentImpl,
50
50
  MarkdownChatResponseContentImpl,
51
51
  ToolCallChatResponseContentImpl
52
52
  } from './chat-model';
53
+ import { findFirstMatch, parseContents } from './parse-contents';
54
+ import { DefaultResponseContentFactory, ResponseContentMatcher, ResponseContentMatcherProvider } from './response-content-matcher';
53
55
 
54
56
  /**
55
57
  * A conversation consists of a sequence of ChatMessages.
@@ -121,6 +123,14 @@ export abstract class AbstractChatAgent {
121
123
  @inject(ILogger) protected logger: ILogger;
122
124
  @inject(CommunicationRecordingService) protected recordingService: CommunicationRecordingService;
123
125
  @inject(PromptService) protected promptService: PromptService;
126
+
127
+ @inject(ContributionProvider) @named(ResponseContentMatcherProvider)
128
+ protected contentMatcherProviders: ContributionProvider<ResponseContentMatcherProvider>;
129
+ protected contentMatchers: ResponseContentMatcher[] = [];
130
+
131
+ @inject(DefaultResponseContentFactory)
132
+ protected defaultContentFactory: DefaultResponseContentFactory;
133
+
124
134
  constructor(
125
135
  public id: string,
126
136
  public languageModelRequirements: LanguageModelRequirement[],
@@ -130,6 +140,11 @@ export abstract class AbstractChatAgent {
130
140
  public tags: String[] = ['Chat']) {
131
141
  }
132
142
 
143
+ @postConstruct()
144
+ init(): void {
145
+ this.contentMatchers = this.contentMatcherProviders.getContributions().flatMap(provider => provider.matchers);
146
+ }
147
+
133
148
  async invoke(request: ChatRequestModelImpl): Promise<void> {
134
149
  try {
135
150
  const languageModel = await this.getLanguageModel(this.defaultLanguageModelPurpose);
@@ -189,6 +204,14 @@ export abstract class AbstractChatAgent {
189
204
  }
190
205
  }
191
206
 
207
+ protected parseContents(text: string): ChatResponseContent[] {
208
+ return parseContents(
209
+ text,
210
+ this.contentMatchers,
211
+ this.defaultContentFactory?.create.bind(this.defaultContentFactory)
212
+ );
213
+ };
214
+
192
215
  protected handleError(request: ChatRequestModelImpl, error: Error): void {
193
216
  request.response.response.addContent(new ErrorChatResponseContentImpl(error));
194
217
  request.response.error(error);
@@ -281,9 +304,8 @@ export abstract class AbstractStreamParsingChatAgent extends AbstractChatAgent {
281
304
 
282
305
  protected override async addContentsToResponse(languageModelResponse: LanguageModelResponse, request: ChatRequestModelImpl): Promise<void> {
283
306
  if (isLanguageModelTextResponse(languageModelResponse)) {
284
- request.response.response.addContent(
285
- new MarkdownChatResponseContentImpl(languageModelResponse.text)
286
- );
307
+ const contents = this.parseContents(languageModelResponse.text);
308
+ request.response.response.addContents(contents);
287
309
  request.response.complete();
288
310
  this.recordingService.recordResponse({
289
311
  agentId: this.id,
@@ -295,57 +317,7 @@ export abstract class AbstractStreamParsingChatAgent extends AbstractChatAgent {
295
317
  return;
296
318
  }
297
319
  if (isLanguageModelStreamResponse(languageModelResponse)) {
298
- for await (const token of languageModelResponse.stream) {
299
- const newContents = this.parse(token, request.response.response.content);
300
- if (isArray(newContents)) {
301
- newContents.forEach(newContent => request.response.response.addContent(newContent));
302
- } else {
303
- request.response.response.addContent(newContents);
304
- }
305
-
306
- const lastContent = request.response.response.content.pop();
307
- if (lastContent === undefined) {
308
- return;
309
- }
310
- const text = lastContent.asString?.();
311
- if (text === undefined) {
312
- return;
313
- }
314
- let curSearchIndex = 0;
315
- const result: ChatResponseContent[] = [];
316
- while (curSearchIndex < text.length) {
317
- // find start of code block: ```[language]\n<code>[\n]```
318
- const codeStartIndex = text.indexOf('```', curSearchIndex);
319
- if (codeStartIndex === -1) {
320
- break;
321
- }
322
-
323
- // find language specifier if present
324
- const newLineIndex = text.indexOf('\n', codeStartIndex + 3);
325
- const language = codeStartIndex + 3 < newLineIndex ? text.substring(codeStartIndex + 3, newLineIndex) : undefined;
326
-
327
- // find end of code block
328
- const codeEndIndex = text.indexOf('```', codeStartIndex + 3);
329
- if (codeEndIndex === -1) {
330
- break;
331
- }
332
-
333
- // add text before code block as markdown content
334
- result.push(new MarkdownChatResponseContentImpl(text.substring(curSearchIndex, codeStartIndex)));
335
- // add code block as code content
336
- const codeText = text.substring(newLineIndex + 1, codeEndIndex).trimEnd();
337
- result.push(new CodeChatResponseContentImpl(codeText, language));
338
- curSearchIndex = codeEndIndex + 3;
339
- }
340
-
341
- if (result.length > 0) {
342
- result.forEach(r => {
343
- request.response.response.addContent(r);
344
- });
345
- } else {
346
- request.response.response.addContent(lastContent);
347
- }
348
- }
320
+ await this.addStreamResponse(languageModelResponse, request);
349
321
  request.response.complete();
350
322
  this.recordingService.recordResponse({
351
323
  agentId: this.id,
@@ -366,11 +338,38 @@ export abstract class AbstractStreamParsingChatAgent extends AbstractChatAgent {
366
338
  );
367
339
  }
368
340
 
369
- private parse(token: LanguageModelStreamResponsePart, previousContent: ChatResponseContent[]): ChatResponseContent | ChatResponseContent[] {
341
+ protected async addStreamResponse(languageModelResponse: LanguageModelStreamResponse, request: ChatRequestModelImpl): Promise<void> {
342
+ for await (const token of languageModelResponse.stream) {
343
+ const newContents = this.parse(token, request.response.response.content);
344
+ if (isArray(newContents)) {
345
+ request.response.response.addContents(newContents);
346
+ } else {
347
+ request.response.response.addContent(newContents);
348
+ }
349
+
350
+ const lastContent = request.response.response.content.pop();
351
+ if (lastContent === undefined) {
352
+ return;
353
+ }
354
+ const text = lastContent.asString?.();
355
+ if (text === undefined) {
356
+ return;
357
+ }
358
+
359
+ const result: ChatResponseContent[] = findFirstMatch(this.contentMatchers, text) ? this.parseContents(text) : [];
360
+ if (result.length > 0) {
361
+ request.response.response.addContents(result);
362
+ } else {
363
+ request.response.response.addContent(lastContent);
364
+ }
365
+ }
366
+ }
367
+
368
+ protected parse(token: LanguageModelStreamResponsePart, previousContent: ChatResponseContent[]): ChatResponseContent | ChatResponseContent[] {
370
369
  const content = token.content;
371
370
  // eslint-disable-next-line no-null/no-null
372
371
  if (content !== undefined && content !== null) {
373
- return new MarkdownChatResponseContentImpl(content);
372
+ return this.defaultContentFactory.create(content);
374
373
  }
375
374
  const toolCalls = token.tool_calls;
376
375
  if (toolCalls !== undefined) {
@@ -378,7 +377,7 @@ export abstract class AbstractStreamParsingChatAgent extends AbstractChatAgent {
378
377
  new ToolCallChatResponseContentImpl(toolCall.id, toolCall.function?.name, toolCall.function?.arguments, toolCall.finished, toolCall.result));
379
378
  return toolCallContents;
380
379
  }
381
- return new MarkdownChatResponseContentImpl('');
380
+ return this.defaultContentFactory.create('');
382
381
  }
383
382
 
384
383
  }
@@ -601,10 +601,20 @@ class ChatResponseImpl implements ChatResponse {
601
601
  return this._content;
602
602
  }
603
603
 
604
+ addContents(contents: ChatResponseContent[]): void {
605
+ contents.forEach(c => this.doAddContent(c));
606
+ this._onDidChangeEmitter.fire();
607
+ }
608
+
604
609
  addContent(nextContent: ChatResponseContent): void {
605
610
  // TODO: Support more complex merges affecting different content than the last, e.g. via some kind of ProcessorRegistry
606
611
  // TODO: Support more of the built-in VS Code behavior, see
607
612
  // https://github.com/microsoft/vscode/blob/a2cab7255c0df424027be05d58e1b7b941f4ea60/src/vs/workbench/contrib/chat/common/chatModel.ts#L188-L244
613
+ this.doAddContent(nextContent);
614
+ this._onDidChangeEmitter.fire();
615
+ }
616
+
617
+ protected doAddContent(nextContent: ChatResponseContent): void {
608
618
  if (ToolCallChatResponseContent.is(nextContent) && nextContent.id !== undefined) {
609
619
  const fittingTool = this._content.find(c => ToolCallChatResponseContent.is(c) && c.id === nextContent.id);
610
620
  if (fittingTool !== undefined) {
@@ -613,10 +623,9 @@ class ChatResponseImpl implements ChatResponse {
613
623
  this._content.push(nextContent);
614
624
  }
615
625
  } else {
616
- const lastElement =
617
- this._content.length > 0
618
- ? this._content[this._content.length - 1]
619
- : undefined;
626
+ const lastElement = this._content.length > 0
627
+ ? this._content[this._content.length - 1]
628
+ : undefined;
620
629
  if (lastElement?.kind === nextContent.kind && ChatResponseContent.hasMerge(lastElement)) {
621
630
  const mergeSuccess = lastElement.merge(nextContent);
622
631
  if (!mergeSuccess) {
@@ -627,7 +636,6 @@ class ChatResponseImpl implements ChatResponse {
627
636
  }
628
637
  }
629
638
  this._updateResponseRepresentation();
630
- this._onDidChangeEmitter.fire();
631
639
  }
632
640
 
633
641
  protected _updateResponseRepresentation(): void {