@theia/ai-chat 1.63.0-next.0 → 1.63.0-next.52
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.
- package/lib/browser/agent-delegation-tool.d.ts +25 -0
- package/lib/browser/agent-delegation-tool.d.ts.map +1 -0
- package/lib/browser/agent-delegation-tool.js +171 -0
- package/lib/browser/agent-delegation-tool.js.map +1 -0
- package/lib/browser/ai-chat-frontend-module.d.ts.map +1 -1
- package/lib/browser/ai-chat-frontend-module.js +15 -1
- package/lib/browser/ai-chat-frontend-module.js.map +1 -1
- package/lib/browser/change-set-file-element.d.ts +28 -7
- package/lib/browser/change-set-file-element.d.ts.map +1 -1
- package/lib/browser/change-set-file-element.js +86 -18
- package/lib/browser/change-set-file-element.js.map +1 -1
- package/lib/browser/change-set-variable.js +1 -2
- package/lib/browser/change-set-variable.js.map +1 -1
- package/lib/browser/chat-tool-preferences.d.ts +54 -0
- package/lib/browser/chat-tool-preferences.d.ts.map +1 -0
- package/lib/browser/chat-tool-preferences.js +170 -0
- package/lib/browser/chat-tool-preferences.js.map +1 -0
- package/lib/browser/chat-tool-request-service.d.ts +20 -0
- package/lib/browser/chat-tool-request-service.d.ts.map +1 -0
- package/lib/browser/chat-tool-request-service.js +89 -0
- package/lib/browser/chat-tool-request-service.js.map +1 -0
- package/lib/browser/delegation-response-content.d.ts +20 -0
- package/lib/browser/delegation-response-content.d.ts.map +1 -0
- package/lib/browser/delegation-response-content.js +51 -0
- package/lib/browser/delegation-response-content.js.map +1 -0
- package/lib/browser/file-chat-variable-contribution.d.ts +15 -1
- package/lib/browser/file-chat-variable-contribution.d.ts.map +1 -1
- package/lib/browser/file-chat-variable-contribution.js +111 -5
- package/lib/browser/file-chat-variable-contribution.js.map +1 -1
- package/lib/browser/frontend-chat-service.d.ts.map +1 -1
- package/lib/browser/frontend-chat-service.js +2 -6
- package/lib/browser/frontend-chat-service.js.map +1 -1
- package/lib/browser/image-context-variable-contribution.d.ts +27 -0
- package/lib/browser/image-context-variable-contribution.d.ts.map +1 -0
- package/lib/browser/image-context-variable-contribution.js +149 -0
- package/lib/browser/image-context-variable-contribution.js.map +1 -0
- package/lib/browser/task-context-service.d.ts +9 -3
- package/lib/browser/task-context-service.d.ts.map +1 -1
- package/lib/browser/task-context-service.js +111 -9
- package/lib/browser/task-context-service.js.map +1 -1
- package/lib/browser/task-context-storage-service.d.ts +1 -0
- package/lib/browser/task-context-storage-service.d.ts.map +1 -1
- package/lib/browser/task-context-storage-service.js +4 -1
- package/lib/browser/task-context-storage-service.js.map +1 -1
- package/lib/common/change-set.d.ts +78 -0
- package/lib/common/change-set.d.ts.map +1 -0
- package/lib/common/change-set.js +133 -0
- package/lib/common/change-set.js.map +1 -0
- package/lib/common/chat-agent-service.d.ts +1 -0
- package/lib/common/chat-agent-service.d.ts.map +1 -1
- package/lib/common/chat-agent-service.js +2 -1
- package/lib/common/chat-agent-service.js.map +1 -1
- package/lib/common/chat-agents.d.ts +2 -2
- package/lib/common/chat-agents.d.ts.map +1 -1
- package/lib/common/chat-agents.js +25 -6
- package/lib/common/chat-agents.js.map +1 -1
- package/lib/common/chat-model.d.ts +68 -80
- package/lib/common/chat-model.d.ts.map +1 -1
- package/lib/common/chat-model.js +224 -136
- package/lib/common/chat-model.js.map +1 -1
- package/lib/common/chat-request-parser.d.ts.map +1 -1
- package/lib/common/chat-request-parser.js +3 -0
- package/lib/common/chat-request-parser.js.map +1 -1
- package/lib/common/chat-service.d.ts +6 -5
- package/lib/common/chat-service.d.ts.map +1 -1
- package/lib/common/chat-service.js +9 -11
- package/lib/common/chat-service.js.map +1 -1
- package/lib/common/image-context-variable.d.ts +29 -0
- package/lib/common/image-context-variable.d.ts.map +1 -0
- package/lib/common/image-context-variable.js +99 -0
- package/lib/common/image-context-variable.js.map +1 -0
- package/package.json +10 -9
- package/src/browser/agent-delegation-tool.ts +207 -0
- package/src/browser/ai-chat-frontend-module.ts +28 -3
- package/src/browser/change-set-file-element.ts +97 -25
- package/src/browser/change-set-variable.ts +1 -1
- package/src/browser/chat-tool-preferences.ts +178 -0
- package/src/browser/chat-tool-request-service.ts +93 -0
- package/src/browser/delegation-response-content.ts +55 -0
- package/src/browser/file-chat-variable-contribution.ts +120 -6
- package/src/browser/frontend-chat-service.ts +3 -6
- package/src/browser/image-context-variable-contribution.ts +153 -0
- package/src/browser/task-context-service.ts +115 -9
- package/src/browser/task-context-storage-service.ts +5 -1
- package/src/common/change-set.ts +197 -0
- package/src/common/chat-agent-service.ts +1 -0
- package/src/common/chat-agents.ts +40 -19
- package/src/common/chat-model.ts +258 -208
- package/src/common/chat-request-parser.ts +3 -0
- package/src/common/chat-service.ts +11 -13
- package/src/common/image-context-variable.ts +116 -0
|
@@ -15,12 +15,15 @@
|
|
|
15
15
|
// *****************************************************************************
|
|
16
16
|
|
|
17
17
|
import { inject, injectable } from '@theia/core/shared/inversify';
|
|
18
|
-
import { MaybePromise, ProgressService, URI, generateUuid, Event } from '@theia/core';
|
|
18
|
+
import { MaybePromise, ProgressService, URI, generateUuid, Event, EOL } from '@theia/core';
|
|
19
19
|
import { ChatAgent, ChatAgentLocation, ChatService, ChatSession, MutableChatModel, MutableChatRequestModel, ParsedChatRequestTextPart } from '../common';
|
|
20
|
+
import { PreferenceService } from '@theia/core/lib/browser';
|
|
20
21
|
import { ChatSessionSummaryAgent } from '../common/chat-session-summary-agent';
|
|
21
22
|
import { Deferred } from '@theia/core/lib/common/promise-util';
|
|
22
|
-
import { AgentService, PromptService } from '@theia/ai-core';
|
|
23
|
+
import { AgentService, PromptService, ResolvedPromptFragment } from '@theia/ai-core';
|
|
23
24
|
import { CHAT_SESSION_SUMMARY_PROMPT } from '../common/chat-session-summary-agent-prompt';
|
|
25
|
+
import { ChangeSetFileElementFactory } from './change-set-file-element';
|
|
26
|
+
import * as yaml from 'js-yaml';
|
|
24
27
|
|
|
25
28
|
export interface SummaryMetadata {
|
|
26
29
|
label: string;
|
|
@@ -33,7 +36,7 @@ export interface Summary extends SummaryMetadata {
|
|
|
33
36
|
id: string;
|
|
34
37
|
}
|
|
35
38
|
|
|
36
|
-
export const TaskContextStorageService = Symbol('
|
|
39
|
+
export const TaskContextStorageService = Symbol('TaskContextStorageService');
|
|
37
40
|
export interface TaskContextStorageService {
|
|
38
41
|
onDidChange: Event<void>;
|
|
39
42
|
store(summary: Summary): MaybePromise<void>;
|
|
@@ -53,6 +56,9 @@ export class TaskContextService {
|
|
|
53
56
|
@inject(PromptService) protected readonly promptService: PromptService;
|
|
54
57
|
@inject(TaskContextStorageService) protected readonly storageService: TaskContextStorageService;
|
|
55
58
|
@inject(ProgressService) protected readonly progressService: ProgressService;
|
|
59
|
+
@inject(PreferenceService) protected readonly preferenceService: PreferenceService;
|
|
60
|
+
@inject(ChangeSetFileElementFactory)
|
|
61
|
+
protected readonly fileChangeFactory: ChangeSetFileElementFactory;
|
|
56
62
|
|
|
57
63
|
get onDidChange(): Event<void> {
|
|
58
64
|
return this.storageService.onDidChange;
|
|
@@ -77,18 +83,19 @@ export class TaskContextService {
|
|
|
77
83
|
}
|
|
78
84
|
|
|
79
85
|
/** Returns an ID that can be used to refer to the summary in the future. */
|
|
80
|
-
async summarize(session: ChatSession, promptId?: string, agent?: ChatAgent): Promise<string> {
|
|
86
|
+
async summarize(session: ChatSession, promptId?: string, agent?: ChatAgent, override = true): Promise<string> {
|
|
81
87
|
const pending = this.pendingSummaries.get(session.id);
|
|
82
88
|
if (pending) { return pending.then(({ id }) => id); }
|
|
83
89
|
const existing = this.getSummaryForSession(session);
|
|
84
|
-
if (existing) { return existing.id; }
|
|
90
|
+
if (existing && !override) { return existing.id; }
|
|
85
91
|
const summaryId = generateUuid();
|
|
86
92
|
const summaryDeferred = new Deferred<Summary>();
|
|
87
93
|
const progress = await this.progressService.showProgress({ text: `Summarize: ${session.title || session.id}`, options: { location: 'ai-chat' } });
|
|
88
94
|
this.pendingSummaries.set(session.id, summaryDeferred.promise);
|
|
89
95
|
try {
|
|
96
|
+
const prompt = await this.getSystemPrompt(session, promptId);
|
|
90
97
|
const newSummary: Summary = {
|
|
91
|
-
summary: await this.getLlmSummary(session,
|
|
98
|
+
summary: await this.getLlmSummary(session, prompt, agent),
|
|
92
99
|
label: session.title || session.id,
|
|
93
100
|
sessionId: session.id,
|
|
94
101
|
id: summaryId
|
|
@@ -97,6 +104,13 @@ export class TaskContextService {
|
|
|
97
104
|
return summaryId;
|
|
98
105
|
} catch (err) {
|
|
99
106
|
summaryDeferred.reject(err);
|
|
107
|
+
const errorSummary: Summary = {
|
|
108
|
+
summary: `Summary creation failed: ${err instanceof Error ? err.message : typeof err === 'string' ? err : 'Unknown error'}`,
|
|
109
|
+
label: session.title || session.id,
|
|
110
|
+
sessionId: session.id,
|
|
111
|
+
id: summaryId
|
|
112
|
+
};
|
|
113
|
+
await this.storageService.store(errorSummary);
|
|
100
114
|
throw err;
|
|
101
115
|
} finally {
|
|
102
116
|
progress.cancel();
|
|
@@ -104,7 +118,95 @@ export class TaskContextService {
|
|
|
104
118
|
}
|
|
105
119
|
}
|
|
106
120
|
|
|
107
|
-
|
|
121
|
+
async update(session: ChatSession, promptId?: string, agent?: ChatAgent, override = true): Promise<string> {
|
|
122
|
+
// Get the existing summary for the session
|
|
123
|
+
const existingSummary = this.getSummaryForSession(session);
|
|
124
|
+
if (!existingSummary) {
|
|
125
|
+
// If no summary exists, create one instead
|
|
126
|
+
// TODO: Maybe we could also look into the task context folder and ask for the existing ones with an additional menu to create a new one?
|
|
127
|
+
return this.summarize(session, promptId, agent, override);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const progress = await this.progressService.showProgress({ text: `Updating: ${session.title || session.id}`, options: { location: 'ai-chat' } });
|
|
131
|
+
try {
|
|
132
|
+
const prompt = await this.getSystemPrompt(session, promptId);
|
|
133
|
+
if (!prompt) {
|
|
134
|
+
return '';
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Get the task context file path
|
|
138
|
+
const taskContextStorageDirectory = this.preferenceService.get(
|
|
139
|
+
// preference key is defined in TASK_CONTEXT_STORAGE_DIRECTORY_PREF in @theia/ai-ide
|
|
140
|
+
'ai-features.promptTemplates.taskContextStorageDirectory',
|
|
141
|
+
'.prompts/task-contexts'
|
|
142
|
+
);
|
|
143
|
+
const taskContextFileVariable = session.model.context.getVariables().find(variableReq => variableReq.variable.id === 'file-provider' &&
|
|
144
|
+
typeof variableReq.arg === 'string' &&
|
|
145
|
+
(variableReq.arg.startsWith(taskContextStorageDirectory)));
|
|
146
|
+
|
|
147
|
+
// Check if we have a document path to update
|
|
148
|
+
if (taskContextFileVariable && typeof taskContextFileVariable.arg === 'string') {
|
|
149
|
+
// Set document path in prompt template
|
|
150
|
+
const documentPath = taskContextFileVariable.arg;
|
|
151
|
+
|
|
152
|
+
// Modify prompt to include the document path and content
|
|
153
|
+
prompt.text = prompt.text + '\nThe document to update is: ' + documentPath + '\n\n## Current Document Content\n\n' + existingSummary.summary;
|
|
154
|
+
|
|
155
|
+
// Get updated document content from LLM
|
|
156
|
+
const updatedDocumentContent = await this.getLlmSummary(session, prompt, agent);
|
|
157
|
+
|
|
158
|
+
if (existingSummary.uri) {
|
|
159
|
+
// updated document metadata shall be updated.
|
|
160
|
+
// otherwise, frontmatter won't be set
|
|
161
|
+
const frontmatter = {
|
|
162
|
+
sessionId: existingSummary.sessionId,
|
|
163
|
+
date: new Date().toISOString(),
|
|
164
|
+
label: existingSummary.label,
|
|
165
|
+
};
|
|
166
|
+
const content = yaml.dump(frontmatter).trim() + `${EOL}---${EOL}` + updatedDocumentContent;
|
|
167
|
+
|
|
168
|
+
session.model.changeSet.addElements(this.fileChangeFactory({
|
|
169
|
+
uri: existingSummary.uri,
|
|
170
|
+
type: 'modify',
|
|
171
|
+
state: 'pending',
|
|
172
|
+
targetState: content,
|
|
173
|
+
requestId: session.model.id, // not a request id, as no changeRequest made yet.
|
|
174
|
+
chatSessionId: session.id
|
|
175
|
+
}));
|
|
176
|
+
} else {
|
|
177
|
+
const updatedSummary: Summary = {
|
|
178
|
+
...existingSummary,
|
|
179
|
+
summary: updatedDocumentContent
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// Store the updated summary
|
|
183
|
+
await this.storageService.store(updatedSummary);
|
|
184
|
+
}
|
|
185
|
+
return existingSummary.id;
|
|
186
|
+
} else {
|
|
187
|
+
// Fall back to standard update if no document path is found
|
|
188
|
+
const updatedSummaryText = await this.getLlmSummary(session, prompt, agent);
|
|
189
|
+
const updatedSummary: Summary = {
|
|
190
|
+
...existingSummary,
|
|
191
|
+
summary: updatedSummaryText
|
|
192
|
+
};
|
|
193
|
+
await this.storageService.store(updatedSummary);
|
|
194
|
+
return updatedSummary.id;
|
|
195
|
+
}
|
|
196
|
+
} catch (err) {
|
|
197
|
+
const errorSummary: Summary = {
|
|
198
|
+
...existingSummary,
|
|
199
|
+
summary: `Summary update failed: ${err instanceof Error ? err.message : typeof err === 'string' ? err : 'Unknown error'}`
|
|
200
|
+
};
|
|
201
|
+
await this.storageService.store(errorSummary);
|
|
202
|
+
throw err;
|
|
203
|
+
} finally {
|
|
204
|
+
progress.cancel();
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
protected async getLlmSummary(session: ChatSession, prompt: ResolvedPromptFragment | undefined, agent?: ChatAgent): Promise<string> {
|
|
209
|
+
if (!prompt) { return ''; }
|
|
108
210
|
agent = agent || this.agentService.getAgents().find<ChatAgent>((candidate): candidate is ChatAgent =>
|
|
109
211
|
'invoke' in candidate
|
|
110
212
|
&& typeof candidate.invoke === 'function'
|
|
@@ -112,8 +214,7 @@ export class TaskContextService {
|
|
|
112
214
|
);
|
|
113
215
|
if (!agent) { throw new Error('Unable to identify agent for summary.'); }
|
|
114
216
|
const model = new MutableChatModel(ChatAgentLocation.Panel);
|
|
115
|
-
|
|
116
|
-
if (!prompt) { return ''; }
|
|
217
|
+
|
|
117
218
|
const messages = session.model.getRequests().filter((candidate): candidate is MutableChatRequestModel => candidate instanceof MutableChatRequestModel);
|
|
118
219
|
messages.forEach(message => model['_hierarchy'].append(message));
|
|
119
220
|
const summaryRequest = model.addRequest({
|
|
@@ -126,6 +227,11 @@ export class TaskContextService {
|
|
|
126
227
|
return summaryRequest.response.response.asDisplayString();
|
|
127
228
|
}
|
|
128
229
|
|
|
230
|
+
protected async getSystemPrompt(session: ChatSession, promptId: string = CHAT_SESSION_SUMMARY_PROMPT.id): Promise<ResolvedPromptFragment | undefined> {
|
|
231
|
+
const prompt = await this.promptService.getResolvedPromptFragment(promptId || CHAT_SESSION_SUMMARY_PROMPT.id, undefined, { model: session.model });
|
|
232
|
+
return prompt;
|
|
233
|
+
}
|
|
234
|
+
|
|
129
235
|
hasSummary(chatSession: ChatSession): boolean {
|
|
130
236
|
return !!this.getSummaryForSession(chatSession);
|
|
131
237
|
}
|
|
@@ -28,6 +28,10 @@ export class InMemoryTaskContextStorage implements TaskContextStorageService {
|
|
|
28
28
|
protected readonly onDidChangeEmitter = new Emitter<void>();
|
|
29
29
|
readonly onDidChange = this.onDidChangeEmitter.event;
|
|
30
30
|
|
|
31
|
+
protected sanitizeLabel(label: string): string {
|
|
32
|
+
return label.replace(/^[^\p{L}\p{N}]+/vg, '');
|
|
33
|
+
}
|
|
34
|
+
|
|
31
35
|
@inject(AIVariableResourceResolver)
|
|
32
36
|
protected readonly variableResourceResolver: AIVariableResourceResolver;
|
|
33
37
|
|
|
@@ -35,7 +39,7 @@ export class InMemoryTaskContextStorage implements TaskContextStorageService {
|
|
|
35
39
|
protected readonly openerService: OpenerService;
|
|
36
40
|
|
|
37
41
|
store(summary: Summary): void {
|
|
38
|
-
this.summaries.set(summary.id, summary);
|
|
42
|
+
this.summaries.set(summary.id, { ...summary, label: this.sanitizeLabel(summary.label) });
|
|
39
43
|
this.onDidChangeEmitter.fire();
|
|
40
44
|
}
|
|
41
45
|
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2025 EclipseSource GmbH and others.
|
|
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 { ArrayUtils, Disposable, Emitter, Event, URI } from '@theia/core';
|
|
18
|
+
|
|
19
|
+
export interface ChangeSetElement {
|
|
20
|
+
readonly uri: URI;
|
|
21
|
+
|
|
22
|
+
onDidChange?: Event<void>
|
|
23
|
+
readonly name?: string;
|
|
24
|
+
readonly icon?: string;
|
|
25
|
+
readonly additionalInfo?: string;
|
|
26
|
+
|
|
27
|
+
readonly state?: 'pending' | 'applied' | 'stale';
|
|
28
|
+
readonly type?: 'add' | 'modify' | 'delete';
|
|
29
|
+
readonly data?: { [key: string]: unknown };
|
|
30
|
+
|
|
31
|
+
/** Called when an element is shown in the UI */
|
|
32
|
+
onShow?(): void;
|
|
33
|
+
/** Called when an element is hidden in the UI */
|
|
34
|
+
onHide?(): void;
|
|
35
|
+
open?(): Promise<void>;
|
|
36
|
+
openChange?(): Promise<void>;
|
|
37
|
+
apply?(): Promise<void>;
|
|
38
|
+
revert?(): Promise<void>;
|
|
39
|
+
dispose?(): void;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ChatUpdateChangeSetEvent {
|
|
43
|
+
kind: 'updateChangeSet';
|
|
44
|
+
elements?: ChangeSetElement[];
|
|
45
|
+
title?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface ChangeSetChangeEvent {
|
|
49
|
+
title?: string;
|
|
50
|
+
added?: URI[],
|
|
51
|
+
removed?: URI[],
|
|
52
|
+
modified?: URI[],
|
|
53
|
+
/** Fired when only the state of a given element changes, not its contents */
|
|
54
|
+
state?: URI[],
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface ChangeSet extends Disposable {
|
|
58
|
+
onDidChange: Event<ChatUpdateChangeSetEvent>;
|
|
59
|
+
readonly title: string;
|
|
60
|
+
setTitle(title: string): void;
|
|
61
|
+
getElements(): ChangeSetElement[];
|
|
62
|
+
/**
|
|
63
|
+
* Find an element by URI.
|
|
64
|
+
* @param uri The URI to look for.
|
|
65
|
+
* @returns The element with the given URI, or undefined if not found.
|
|
66
|
+
*/
|
|
67
|
+
getElementByURI(uri: URI): ChangeSetElement | undefined;
|
|
68
|
+
/** @returns true if addition produces a change; false otherwise. */
|
|
69
|
+
addElements(...elements: ChangeSetElement[]): boolean;
|
|
70
|
+
setElements(...elements: ChangeSetElement[]): void;
|
|
71
|
+
/** @returns true if deletion produces a change; false otherwise. */
|
|
72
|
+
removeElements(...uris: URI[]): boolean;
|
|
73
|
+
dispose(): void;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export class ChangeSetImpl implements ChangeSet {
|
|
77
|
+
/** @param changeSets ordered from tip to root. */
|
|
78
|
+
static combine(changeSets: Iterable<ChangeSetImpl>): Map<string, ChangeSetElement | undefined> {
|
|
79
|
+
const result = new Map<string, ChangeSetElement | undefined>();
|
|
80
|
+
for (const next of changeSets) {
|
|
81
|
+
next._elements.forEach((value, key) => !result.has(key) && result.set(key, value));
|
|
82
|
+
// Break at the first element whose values were set, not just changed through addition and deletion.
|
|
83
|
+
if (next.hasBeenSet) {
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
protected readonly _onDidChangeEmitter = new Emitter<ChatUpdateChangeSetEvent>();
|
|
92
|
+
onDidChange: Event<ChatUpdateChangeSetEvent> = this._onDidChangeEmitter.event;
|
|
93
|
+
protected readonly _onDidChangeContentsEmitter = new Emitter<ChangeSetChangeEvent>();
|
|
94
|
+
onDidChangeContents: Event<ChangeSetChangeEvent> = this._onDidChangeContentsEmitter.event;
|
|
95
|
+
|
|
96
|
+
protected hasBeenSet = false;
|
|
97
|
+
protected _elements = new Map<string, ChangeSetElement | undefined>();
|
|
98
|
+
protected _title = 'Suggested Changes';
|
|
99
|
+
get title(): string {
|
|
100
|
+
return this._title;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
constructor(elements: ChangeSetElement[] = []) {
|
|
104
|
+
this.addElements(...elements);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
getElements(): ChangeSetElement[] {
|
|
108
|
+
return ArrayUtils.coalesce(Array.from(this._elements.values()));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Will replace any element that is already present, using URI as identity criterion. */
|
|
112
|
+
addElements(...elements: ChangeSetElement[]): boolean {
|
|
113
|
+
const added: URI[] = [];
|
|
114
|
+
const modified: URI[] = [];
|
|
115
|
+
elements.forEach(element => {
|
|
116
|
+
if (this.doAdd(element)) {
|
|
117
|
+
modified.push(element.uri);
|
|
118
|
+
} else {
|
|
119
|
+
added.push(element.uri);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
this.notifyChange({ added, modified });
|
|
123
|
+
return !!(added.length || modified.length);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
setTitle(title: string): void {
|
|
127
|
+
this._title = title;
|
|
128
|
+
this.notifyChange({ title });
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
protected doAdd(element: ChangeSetElement): boolean {
|
|
132
|
+
const id = element.uri.toString();
|
|
133
|
+
const existing = this._elements.get(id);
|
|
134
|
+
existing?.dispose?.();
|
|
135
|
+
this._elements.set(id, element);
|
|
136
|
+
element.onDidChange?.(() => this.notifyChange({ state: [element.uri] }));
|
|
137
|
+
return !!existing;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
setElements(...elements: ChangeSetElement[]): void {
|
|
141
|
+
this.hasBeenSet = true;
|
|
142
|
+
const added = [];
|
|
143
|
+
const modified = [];
|
|
144
|
+
const removed = [];
|
|
145
|
+
const toHandle = new Set(this._elements.keys());
|
|
146
|
+
for (const element of elements) {
|
|
147
|
+
toHandle.delete(element.uri.toString());
|
|
148
|
+
if (this.doAdd(element)) {
|
|
149
|
+
added.push(element.uri);
|
|
150
|
+
} else {
|
|
151
|
+
modified.push(element.uri);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
for (const toDelete of toHandle) {
|
|
155
|
+
const uri = new URI(toDelete);
|
|
156
|
+
if (this.doDelete(uri)) {
|
|
157
|
+
removed.push(uri);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
this.notifyChange({ added, modified, removed });
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
removeElements(...uris: URI[]): boolean {
|
|
164
|
+
const removed: URI[] = [];
|
|
165
|
+
for (const uri of uris) {
|
|
166
|
+
if (this.doDelete(uri)) {
|
|
167
|
+
removed.push(uri);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
this.notifyChange({ removed });
|
|
171
|
+
return !!removed.length;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
getElementByURI(uri: URI): ChangeSetElement | undefined {
|
|
175
|
+
return this._elements.get(uri.toString());
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
protected doDelete(uri: URI): boolean {
|
|
179
|
+
const id = uri.toString();
|
|
180
|
+
const delendum = this._elements.get(id);
|
|
181
|
+
if (delendum) {
|
|
182
|
+
delendum.dispose?.();
|
|
183
|
+
}
|
|
184
|
+
this._elements.set(id, undefined);
|
|
185
|
+
return !!delendum;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
protected notifyChange(change: ChangeSetChangeEvent): void {
|
|
189
|
+
this._onDidChangeContentsEmitter.fire(change);
|
|
190
|
+
this._onDidChangeEmitter.fire({ kind: 'updateChangeSet', elements: this.getElements(), title: this.title });
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
dispose(): void {
|
|
194
|
+
this._onDidChangeEmitter.dispose();
|
|
195
|
+
this._elements.forEach(element => element?.dispose?.());
|
|
196
|
+
}
|
|
197
|
+
}
|
|
@@ -25,6 +25,7 @@ import { ChatAgent } from './chat-agents';
|
|
|
25
25
|
import { AgentService } from '@theia/ai-core';
|
|
26
26
|
|
|
27
27
|
export const ChatAgentService = Symbol('ChatAgentService');
|
|
28
|
+
export const ChatAgentServiceFactory = Symbol('ChatAgentServiceFactory');
|
|
28
29
|
/**
|
|
29
30
|
* The ChatAgentService provides access to the available chat agents.
|
|
30
31
|
*/
|
|
@@ -54,17 +54,19 @@ import { inject, injectable, named, postConstruct } from '@theia/core/shared/inv
|
|
|
54
54
|
import { ChatAgentService } from './chat-agent-service';
|
|
55
55
|
import {
|
|
56
56
|
ChatModel,
|
|
57
|
-
|
|
57
|
+
ChatRequestModel,
|
|
58
58
|
ChatResponseContent,
|
|
59
59
|
ErrorChatResponseContentImpl,
|
|
60
60
|
MarkdownChatResponseContentImpl,
|
|
61
|
-
|
|
62
|
-
ChatRequestModel,
|
|
61
|
+
MutableChatRequestModel,
|
|
63
62
|
ThinkingChatResponseContentImpl,
|
|
63
|
+
ToolCallChatResponseContentImpl,
|
|
64
|
+
ErrorChatResponseContent,
|
|
64
65
|
} from './chat-model';
|
|
66
|
+
import { ChatToolRequest, ChatToolRequestService } from './chat-tool-request-service';
|
|
65
67
|
import { parseContents } from './parse-contents';
|
|
66
68
|
import { DefaultResponseContentFactory, ResponseContentMatcher, ResponseContentMatcherProvider } from './response-content-matcher';
|
|
67
|
-
import {
|
|
69
|
+
import { ImageContextVariable } from './image-context-variable';
|
|
68
70
|
|
|
69
71
|
/**
|
|
70
72
|
* System message content, enriched with function descriptions.
|
|
@@ -218,6 +220,7 @@ export abstract class AbstractChatAgent implements ChatAgent {
|
|
|
218
220
|
};
|
|
219
221
|
|
|
220
222
|
protected handleError(request: MutableChatRequestModel, error: Error): void {
|
|
223
|
+
console.error('Error handling chat interaction:', error);
|
|
221
224
|
request.response.response.addContent(new ErrorChatResponseContentImpl(error));
|
|
222
225
|
request.response.error(error);
|
|
223
226
|
}
|
|
@@ -252,23 +255,41 @@ export abstract class AbstractChatAgent implements ChatAgent {
|
|
|
252
255
|
const requestMessages = model.getRequests().flatMap(request => {
|
|
253
256
|
const messages: LanguageModelMessage[] = [];
|
|
254
257
|
const text = request.message.parts.map(part => part.promptText).join('');
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
258
|
+
if (text.length > 0) {
|
|
259
|
+
messages.push({
|
|
260
|
+
actor: 'user',
|
|
261
|
+
type: 'text',
|
|
262
|
+
text: text,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
const imageMessages = request.context.variables
|
|
266
|
+
.filter(variable => ImageContextVariable.isResolvedImageContext(variable))
|
|
267
|
+
.map(variable => ImageContextVariable.parseResolved(variable))
|
|
268
|
+
.filter(content => content !== undefined)
|
|
269
|
+
.map(content => ({
|
|
270
|
+
actor: 'user' as const,
|
|
271
|
+
type: 'image' as const,
|
|
272
|
+
image: {
|
|
273
|
+
base64data: content!.data,
|
|
274
|
+
mimeType: content!.mimeType
|
|
264
275
|
}
|
|
276
|
+
}));
|
|
277
|
+
messages.push(...imageMessages);
|
|
265
278
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
279
|
+
if (request.response.isComplete || includeResponseInProgress) {
|
|
280
|
+
const responseMessages: LanguageModelMessage[] = request.response.response.content
|
|
281
|
+
.filter(c => !ErrorChatResponseContent.is(c))
|
|
282
|
+
.flatMap(c => {
|
|
283
|
+
if (ChatResponseContent.hasToLanguageModelMessage(c)) {
|
|
284
|
+
return c.toLanguageModelMessage();
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
actor: 'ai',
|
|
289
|
+
type: 'text',
|
|
290
|
+
text: c.asString?.() ?? c.asDisplayString?.() ?? '',
|
|
291
|
+
} as TextMessage;
|
|
292
|
+
});
|
|
272
293
|
messages.push(...responseMessages);
|
|
273
294
|
}
|
|
274
295
|
return messages;
|