@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,120 @@
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
+ import * as sinon from 'sinon';
18
+ import { ChatAgentServiceImpl } from './chat-agent-service';
19
+ import { ChatRequestParserImpl } from './chat-request-parser';
20
+ import { ChatAgentLocation } from './chat-agents';
21
+ import { ChatRequest } from './chat-model';
22
+ import { expect } from 'chai';
23
+ import { DefaultAIVariableService, ToolInvocationRegistry, ToolInvocationRegistryImpl } from '@theia/ai-core';
24
+
25
+ describe('ChatRequestParserImpl', () => {
26
+ const chatAgentService = sinon.createStubInstance(ChatAgentServiceImpl);
27
+ const variableService = sinon.createStubInstance(DefaultAIVariableService);
28
+ const toolInvocationRegistry: ToolInvocationRegistry = sinon.createStubInstance(ToolInvocationRegistryImpl);
29
+ const parser = new ChatRequestParserImpl(chatAgentService, variableService, toolInvocationRegistry);
30
+
31
+ it('parses simple text', () => {
32
+ const req: ChatRequest = {
33
+ text: 'What is the best pizza topping?'
34
+ };
35
+ const result = parser.parseChatRequest(req, ChatAgentLocation.Panel);
36
+ expect(result.parts).to.deep.contain({
37
+ text: 'What is the best pizza topping?',
38
+ range: { start: 0, endExclusive: 31 }
39
+ });
40
+ });
41
+
42
+ it('parses text with variable name', () => {
43
+ const req: ChatRequest = {
44
+ text: 'What is the #best pizza topping?'
45
+ };
46
+ const result = parser.parseChatRequest(req, ChatAgentLocation.Panel);
47
+ expect(result).to.deep.contain({
48
+ parts: [{
49
+ text: 'What is the ',
50
+ range: { start: 0, endExclusive: 12 }
51
+ }, {
52
+ variableName: 'best',
53
+ variableArg: undefined,
54
+ range: { start: 12, endExclusive: 17 }
55
+ }, {
56
+ text: ' pizza topping?',
57
+ range: { start: 17, endExclusive: 32 }
58
+ }]
59
+ });
60
+ });
61
+
62
+ it('parses text with variable name with argument', () => {
63
+ const req: ChatRequest = {
64
+ text: 'What is the #best:by-poll pizza topping?'
65
+ };
66
+ const result = parser.parseChatRequest(req, ChatAgentLocation.Panel);
67
+ expect(result).to.deep.contain({
68
+ parts: [{
69
+ text: 'What is the ',
70
+ range: { start: 0, endExclusive: 12 }
71
+ }, {
72
+ variableName: 'best',
73
+ variableArg: 'by-poll',
74
+ range: { start: 12, endExclusive: 25 }
75
+ }, {
76
+ text: ' pizza topping?',
77
+ range: { start: 25, endExclusive: 40 }
78
+ }]
79
+ });
80
+ });
81
+
82
+ it('parses text with variable name with numeric argument', () => {
83
+ const req: ChatRequest = {
84
+ text: '#size-class:2'
85
+ };
86
+ const result = parser.parseChatRequest(req, ChatAgentLocation.Panel);
87
+ expect(result.parts[0]).to.contain(
88
+ {
89
+ variableName: 'size-class',
90
+ variableArg: '2'
91
+ }
92
+ );
93
+ });
94
+
95
+ it('parses text with variable name with POSIX path argument', () => {
96
+ const req: ChatRequest = {
97
+ text: '#file:/path/to/file.ext'
98
+ };
99
+ const result = parser.parseChatRequest(req, ChatAgentLocation.Panel);
100
+ expect(result.parts[0]).to.contain(
101
+ {
102
+ variableName: 'file',
103
+ variableArg: '/path/to/file.ext'
104
+ }
105
+ );
106
+ });
107
+
108
+ it('parses text with variable name with Win32 path argument', () => {
109
+ const req: ChatRequest = {
110
+ text: '#file:c:\\path\\to\\file.ext'
111
+ };
112
+ const result = parser.parseChatRequest(req, ChatAgentLocation.Panel);
113
+ expect(result.parts[0]).to.contain(
114
+ {
115
+ variableName: 'file',
116
+ variableArg: 'c:\\path\\to\\file.ext'
117
+ }
118
+ );
119
+ });
120
+ });
@@ -0,0 +1,220 @@
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/chatRequestParser.ts
21
+
22
+ import { inject, injectable } from '@theia/core/shared/inversify';
23
+ import { ChatAgentService } from './chat-agent-service';
24
+ import { ChatAgentLocation } from './chat-agents';
25
+ import { ChatRequest } from './chat-model';
26
+ import {
27
+ chatAgentLeader,
28
+ chatFunctionLeader,
29
+ ParsedChatRequestAgentPart,
30
+ ParsedChatRequestFunctionPart,
31
+ ParsedChatRequestTextPart,
32
+ ParsedChatRequestVariablePart,
33
+ chatVariableLeader,
34
+ OffsetRange,
35
+ ParsedChatRequest,
36
+ ParsedChatRequestPart,
37
+ } from './parsed-chat-request';
38
+ import { AIVariable, AIVariableService, ToolInvocationRegistry, ToolRequest } from '@theia/ai-core';
39
+
40
+ const agentReg = /^@([\w_\-\.]+)(?=(\s|$|\b))/i; // An @-agent
41
+ const functionReg = /^~([\w_\-\.]+)(?=(\s|$|\b))/i; // A ~ tool function
42
+ const variableReg = /^#([\w_\-]+)(?::([\w_\-_\/\\.:]+))?(?=(\s|$|\b))/i; // A #-variable with an optional : arg (#file:workspace/path/name.ext)
43
+
44
+ export const ChatRequestParser = Symbol('ChatRequestParser');
45
+ export interface ChatRequestParser {
46
+ parseChatRequest(request: ChatRequest, location: ChatAgentLocation): ParsedChatRequest;
47
+ }
48
+
49
+ function offsetRange(start: number, endExclusive: number): OffsetRange {
50
+ if (start > endExclusive) {
51
+ throw new Error(`Invalid range: start=${start} endExclusive=${endExclusive}`);
52
+ }
53
+ return { start, endExclusive };
54
+ }
55
+ @injectable()
56
+ export class ChatRequestParserImpl {
57
+ constructor(
58
+ @inject(ChatAgentService) private readonly agentService: ChatAgentService,
59
+ @inject(AIVariableService) private readonly variableService: AIVariableService,
60
+ @inject(ToolInvocationRegistry) private readonly toolInvocationRegistry: ToolInvocationRegistry
61
+ ) { }
62
+
63
+ parseChatRequest(request: ChatRequest, location: ChatAgentLocation): ParsedChatRequest {
64
+ const parts: ParsedChatRequestPart[] = [];
65
+ const variables = new Map<string, AIVariable>();
66
+ const toolRequests = new Map<string, ToolRequest>();
67
+ const message = request.text;
68
+ for (let i = 0; i < message.length; i++) {
69
+ const previousChar = message.charAt(i - 1);
70
+ const char = message.charAt(i);
71
+ let newPart: ParsedChatRequestPart | undefined;
72
+
73
+ if (previousChar.match(/\s/) || i === 0) {
74
+ if (char === chatFunctionLeader) {
75
+ const functionPart = this.tryParseFunction(
76
+ message.slice(i),
77
+ i
78
+ );
79
+ newPart = functionPart;
80
+ if (functionPart) {
81
+ toolRequests.set(functionPart.toolRequest.id, functionPart.toolRequest);
82
+ }
83
+ } else if (char === chatVariableLeader) {
84
+ const variablePart = this.tryToParseVariable(
85
+ message.slice(i),
86
+ i,
87
+ parts
88
+ );
89
+ newPart = variablePart;
90
+ if (variablePart) {
91
+ const variable = this.variableService.getVariable(variablePart.variableName);
92
+ if (variable) {
93
+ variables.set(variable.name, variable);
94
+ }
95
+ }
96
+ } else if (char === chatAgentLeader) {
97
+ newPart = this.tryToParseAgent(
98
+ message.slice(i),
99
+ i,
100
+ parts,
101
+ location
102
+ );
103
+ }
104
+ }
105
+
106
+ if (newPart) {
107
+ if (i !== 0) {
108
+ // Insert a part for all the text we passed over, then insert the new parsed part
109
+ const previousPart = parts.at(-1);
110
+ const previousPartEnd =
111
+ previousPart?.range.endExclusive ?? 0;
112
+ parts.push(
113
+ new ParsedChatRequestTextPart(
114
+ offsetRange(previousPartEnd, i),
115
+ message.slice(previousPartEnd, i)
116
+ )
117
+ );
118
+ }
119
+
120
+ parts.push(newPart);
121
+ }
122
+ }
123
+
124
+ const lastPart = parts.at(-1);
125
+ const lastPartEnd = lastPart?.range.endExclusive ?? 0;
126
+ if (lastPartEnd < message.length) {
127
+ parts.push(
128
+ new ParsedChatRequestTextPart(
129
+ offsetRange(lastPartEnd, message.length),
130
+ message.slice(lastPartEnd, message.length)
131
+ )
132
+ );
133
+ }
134
+
135
+ return { request, parts, toolRequests, variables };
136
+ }
137
+
138
+ private tryToParseAgent(
139
+ message: string,
140
+ offset: number,
141
+ parts: ReadonlyArray<ParsedChatRequestPart>,
142
+ location: ChatAgentLocation
143
+ ): ParsedChatRequestAgentPart | ParsedChatRequestVariablePart | undefined {
144
+ const nextAgentMatch = message.match(agentReg);
145
+ if (!nextAgentMatch) {
146
+ return;
147
+ }
148
+
149
+ const [full, name] = nextAgentMatch;
150
+ const agentRange = offsetRange(offset, offset + full.length);
151
+
152
+ let agents = this.agentService.getAgents().filter(a => a.name === name);
153
+ if (!agents.length) {
154
+ const fqAgent = this.agentService.getAgent(name);
155
+ if (fqAgent) {
156
+ agents = [fqAgent];
157
+ }
158
+ }
159
+
160
+ // If there is more than one agent with this name, and the user picked it from the suggest widget, then the selected agent should be in the
161
+ // context and we use that one. Otherwise just pick the first.
162
+ const agent = agents[0];
163
+ if (!agent || !agent.locations.includes(location)) {
164
+ return;
165
+ }
166
+
167
+ if (parts.some(p => p instanceof ParsedChatRequestAgentPart)) {
168
+ // Only one agent allowed
169
+ return;
170
+ }
171
+
172
+ // The agent must come first
173
+ if (
174
+ parts.some(
175
+ p =>
176
+ (p instanceof ParsedChatRequestTextPart &&
177
+ p.text.trim() !== '') ||
178
+ !(p instanceof ParsedChatRequestAgentPart)
179
+ )
180
+ ) {
181
+ return;
182
+ }
183
+
184
+ return new ParsedChatRequestAgentPart(agentRange, agent.id, agent.name);
185
+ }
186
+
187
+ private tryToParseVariable(
188
+ message: string,
189
+ offset: number,
190
+ _parts: ReadonlyArray<ParsedChatRequestPart>
191
+ ): ParsedChatRequestVariablePart | undefined {
192
+ const nextVariableMatch = message.match(variableReg);
193
+ if (!nextVariableMatch) {
194
+ return;
195
+ }
196
+
197
+ const [full, name] = nextVariableMatch;
198
+ const variableArg = nextVariableMatch[2];
199
+ const varRange = offsetRange(offset, offset + full.length);
200
+
201
+ return new ParsedChatRequestVariablePart(varRange, name, variableArg);
202
+ }
203
+
204
+ private tryParseFunction(message: string, offset: number): ParsedChatRequestFunctionPart | undefined {
205
+ const nextFunctionMatch = message.match(functionReg);
206
+ if (!nextFunctionMatch) {
207
+ return;
208
+ }
209
+
210
+ const [full, id] = nextFunctionMatch;
211
+
212
+ const maybeToolRequest = this.toolInvocationRegistry.getFunction(id);
213
+ if (!maybeToolRequest) {
214
+ return;
215
+ }
216
+
217
+ const functionRange = offsetRange(offset, offset + full.length);
218
+ return new ParsedChatRequestFunctionPart(functionRange, maybeToolRequest);
219
+ }
220
+ }
@@ -0,0 +1,236 @@
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/chatService.ts
21
+
22
+ import { inject, injectable, optional } from '@theia/core/shared/inversify';
23
+ import {
24
+ ChatModel,
25
+ ChatModelImpl,
26
+ ChatRequest,
27
+ ChatRequestModel,
28
+ ChatResponseModel,
29
+ ErrorChatResponseModelImpl,
30
+ } from './chat-model';
31
+ import { ChatAgentService } from './chat-agent-service';
32
+ import { Emitter, ILogger, generateUuid } from '@theia/core';
33
+ import { ChatRequestParser } from './chat-request-parser';
34
+ import { ChatAgent, ChatAgentLocation } from './chat-agents';
35
+ import { ParsedChatRequestAgentPart, ParsedChatRequestVariablePart, ParsedChatRequest } from './parsed-chat-request';
36
+ import { AIVariableService } from '@theia/ai-core';
37
+ import { Event } from '@theia/core/shared/vscode-languageserver-protocol';
38
+
39
+ export interface ChatRequestInvocation {
40
+ /**
41
+ * Promise which completes once the request preprocessing is complete.
42
+ */
43
+ requestCompleted: Promise<ChatRequestModel>;
44
+ /**
45
+ * Promise which completes once a response is expected to arrive.
46
+ */
47
+ responseCreated: Promise<ChatResponseModel>;
48
+ /**
49
+ * Promise which completes once the response is complete.
50
+ */
51
+ responseCompleted: Promise<ChatResponseModel>;
52
+ }
53
+
54
+ export interface ChatSession {
55
+ id: string;
56
+ title?: string;
57
+ model: ChatModel;
58
+ isActive: boolean;
59
+ }
60
+
61
+ export interface ActiveSessionChangedEvent {
62
+ sessionId: string | undefined;
63
+ focus?: boolean;
64
+ }
65
+
66
+ export interface SessionOptions {
67
+ focus?: boolean;
68
+ }
69
+
70
+ export const DefaultChatAgentId = Symbol('DefaultChatAgentId');
71
+ export interface DefaultChatAgentId {
72
+ id: string;
73
+ }
74
+
75
+ export const ChatService = Symbol('ChatService');
76
+ export interface ChatService {
77
+ onActiveSessionChanged: Event<ActiveSessionChangedEvent>
78
+
79
+ getSession(id: string): ChatSession | undefined;
80
+ getSessions(): ChatSession[];
81
+ createSession(location?: ChatAgentLocation, options?: SessionOptions): ChatSession;
82
+ deleteSession(sessionId: string): void;
83
+ setActiveSession(sessionId: string, options?: SessionOptions): void;
84
+
85
+ sendRequest(
86
+ sessionId: string,
87
+ request: ChatRequest
88
+ ): Promise<ChatRequestInvocation | undefined>;
89
+ }
90
+
91
+ interface ChatSessionInternal extends ChatSession {
92
+ model: ChatModelImpl;
93
+ }
94
+
95
+ @injectable()
96
+ export class ChatServiceImpl implements ChatService {
97
+ protected readonly onActiveSessionChangedEmitter = new Emitter<ActiveSessionChangedEvent>();
98
+ onActiveSessionChanged = this.onActiveSessionChangedEmitter.event;
99
+
100
+ @inject(ChatAgentService)
101
+ protected chatAgentService: ChatAgentService;
102
+
103
+ @inject(DefaultChatAgentId) @optional()
104
+ protected defaultChatAgentId: DefaultChatAgentId | undefined;
105
+
106
+ @inject(ChatRequestParser)
107
+ protected chatRequestParser: ChatRequestParser;
108
+
109
+ @inject(AIVariableService)
110
+ protected variableService: AIVariableService;
111
+
112
+ @inject(ILogger)
113
+ protected logger: ILogger;
114
+
115
+ protected _sessions: ChatSessionInternal[] = [];
116
+
117
+ getSessions(): ChatSessionInternal[] {
118
+ return [...this._sessions];
119
+ }
120
+
121
+ getSession(id: string): ChatSessionInternal | undefined {
122
+ return this._sessions.find(session => session.id === id);
123
+ }
124
+
125
+ createSession(location = ChatAgentLocation.Panel, options?: SessionOptions): ChatSession {
126
+ const model = new ChatModelImpl(location);
127
+ const session: ChatSessionInternal = {
128
+ id: model.id,
129
+ model,
130
+ isActive: true
131
+ };
132
+ this._sessions.push(session);
133
+ this.setActiveSession(session.id, options);
134
+ return session;
135
+ }
136
+
137
+ deleteSession(sessionId: string): void {
138
+ // If the removed session is the active one, set the newest one as active
139
+ if (this.getSession(sessionId)?.isActive) {
140
+ this.setActiveSession(this._sessions[this._sessions.length - 1]?.id);
141
+ }
142
+ this._sessions = this._sessions.filter(item => item.id !== sessionId);
143
+ }
144
+
145
+ setActiveSession(sessionId: string | undefined, options?: SessionOptions): void {
146
+ this._sessions.forEach(session => {
147
+ session.isActive = session.id === sessionId;
148
+ });
149
+ this.onActiveSessionChangedEmitter.fire({ sessionId: sessionId, ...options });
150
+ }
151
+
152
+ async sendRequest(
153
+ sessionId: string,
154
+ request: ChatRequest
155
+ ): Promise<ChatRequestInvocation | undefined> {
156
+ const session = this.getSession(sessionId);
157
+ if (!session) {
158
+ return undefined;
159
+ }
160
+ session.title = request.text;
161
+
162
+ const parsedRequest = this.chatRequestParser.parseChatRequest(request, session.model.location);
163
+
164
+ const agent = this.getAgent(parsedRequest);
165
+ if (agent === undefined) {
166
+ const error = 'No ChatAgents available to handle request!';
167
+ this.logger.error(error);
168
+ const chatResponseModel = new ErrorChatResponseModelImpl(generateUuid(), new Error(error));
169
+ return {
170
+ requestCompleted: Promise.reject(error),
171
+ responseCreated: Promise.reject(error),
172
+ responseCompleted: Promise.resolve(chatResponseModel),
173
+ };
174
+ }
175
+ const requestModel = session.model.addRequest(parsedRequest, agent?.id);
176
+
177
+ for (const part of parsedRequest.parts) {
178
+ if (part instanceof ParsedChatRequestVariablePart) {
179
+ const resolvedVariable = await this.variableService.resolveVariable(
180
+ { variable: part.variableName, arg: part.variableArg },
181
+ { request, model: session }
182
+ );
183
+ if (resolvedVariable) {
184
+ part.resolution = resolvedVariable;
185
+ } else {
186
+ this.logger.warn(`Failed to resolve variable ${part.variableName} for ${session.model.location}`);
187
+ }
188
+ }
189
+ }
190
+
191
+ let resolveResponseCreated: (responseModel: ChatResponseModel) => void;
192
+ let resolveResponseCompleted: (responseModel: ChatResponseModel) => void;
193
+ const invocation: ChatRequestInvocation = {
194
+ requestCompleted: Promise.resolve(requestModel),
195
+ responseCreated: new Promise(resolve => {
196
+ resolveResponseCreated = resolve;
197
+ }),
198
+ responseCompleted: new Promise(resolve => {
199
+ resolveResponseCompleted = resolve;
200
+ }),
201
+ };
202
+
203
+ resolveResponseCreated!(requestModel.response);
204
+ requestModel.response.onDidChange(() => {
205
+ if (requestModel.response.isComplete) {
206
+ resolveResponseCompleted!(requestModel.response);
207
+ }
208
+ if (requestModel.response.isError) {
209
+ resolveResponseCompleted!(requestModel.response);
210
+ }
211
+ });
212
+
213
+ if (agent) {
214
+ agent.invoke(requestModel).catch(error => requestModel.response.error(error));
215
+ } else {
216
+ this.logger.error('No ChatAgents available to handle request!', requestModel);
217
+ }
218
+
219
+ return invocation;
220
+ }
221
+
222
+ protected getAgent(parsedRequest: ParsedChatRequest): ChatAgent | undefined {
223
+ const agentPart = this.getMentionedAgent(parsedRequest);
224
+ if (agentPart) {
225
+ return this.chatAgentService.getAgent(agentPart.agentId);
226
+ }
227
+ if (this.defaultChatAgentId) {
228
+ return this.chatAgentService.getAgent(this.defaultChatAgentId.id);
229
+ }
230
+ return this.chatAgentService.getAgents()[0] ?? undefined;
231
+ }
232
+
233
+ protected getMentionedAgent(parsedRequest: ParsedChatRequest): ParsedChatRequestAgentPart | undefined {
234
+ return parsedRequest.parts.find(p => p instanceof ParsedChatRequestAgentPart) as ParsedChatRequestAgentPart | undefined;
235
+ }
236
+ }